DB developers channel
804 subscribers
2 photos
46 files
102 links
💡 Канал для разработчиков баз данных: Oracle, PostgreSQL
📌 Интересные задачи, фрагменты кода, лучшие практики, архитектура, оптимизация
🔄 Присоединяйся к сообществу — развивайся вместе с нами!
#SQL #Oracle #PostgreSQL #PL/SQL #PL/pgSQL #DB
Download Telegram
🎵 «Ах! Крутится, вертится шар голубой,
Крутится, вертится над головой,
Крутится, вертится, хочет упасть.
Кавалер барышню хочет украсть.» — приписывается Ф. Садовскому

📌 Разбор задачи: Поворот матрицы N×N на 90 градусов по часовой стрелке процедурными методами

Представьте, что матрица — это фотография.
Вы берёте её в руки и поворачиваете на 90 градусов.

Компьютер, конечно, руками не умеет, поэтому он делает так:
– сначала кладёт фотографию на бок (транспонирование),
– потом переворачивает каждую строку, как будто листает страницы книги.

Было:        Стало:
1 2 3 7 4 1
4 5 6 → 8 5 2
7 8 9 9 6 3


DECLARE
TYPE row_tab IS TABLE OF NUMBER;

TYPE matrix_tab IS TABLE OF row_tab;

matrix matrix_tab
:= matrix_tab (row_tab (1, 2, 3),
row_tab (4, 5, 6),
row_tab (7, 8, 9));

newMatrix matrix_tab := matrix_tab ();
BEGIN
<<flip_matrix>>
DECLARE
BEGIN
newMatrix := matrix;

FOR i IN 1 .. matrix.COUNT
LOOP
FOR j IN 1 .. matrix (1).COUNT
LOOP
newMatrix (j) (matrix (i).COUNT - i + 1) := matrix (i) (j);
END LOOP;
END LOOP;
END;

<<print_matrix>>
DECLARE
str VARCHAR2 (4000);
BEGIN
FOR i IN 1 .. newMatrix.COUNT
LOOP
str := '';

FOR j IN 1 .. newMatrix (1).COUNT
LOOP
str := str || ' ' || TO_CHAR (newMatrix (i) (j));
END LOOP;

DBMS_OUTPUT.put_line (str);
END LOOP;
END;
END;

👍 Палец вверх — если такие алгоритмы заходят.
👎 Палец вниз — если это всё несерьезно.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Прошу высказываться смело, прямо, грубо и дерзко, но с уважением.
#️⃣ #SQL #Oracle #PLSQL
👍7👎1
Иногда требуется узнать сколько то или иное выражение занимает в байтах в ORACLE/PostgreSql. А знаете ли Вы функцию, которая такое количество вычисляет?
Anonymous Poll
25%
Да
75%
Нет
🎵 «На линии огня
Пустые города
В которых никогда
Ты раньше не бывала.» — БИ2

Сколько байт занимает выражение? Oracle vs PostgreSQL

Часть 1: ORACLE

Иногда разработчику нужно понять: сколько реально памяти занимает то или иное значение в базе?
Например — строка, число или дата.

📍 В Oracle для этого есть функции VSIZE и LENGTHB:
SELECT 
-- strings
vsize(CAST('Y' AS VARCHAR2(1))) AS single_vchar_bytes,
vsize(CAST('Hello' AS VARCHAR2(4000))) AS vchar_bytes,
vsize(CAST('Привет' AS VARCHAR2(4000))) AS russian_vchar_bytes,
vsize(CAST('Hello' AS CHAR(20))) AS char_bytes,

-- numbers
vsize(CAST(1 AS NUMBER)) AS number_bytes,
vsize(CAST(100000000000000000000000000000000000000 AS NUMBER)) AS big_simple_number_bytes,
vsize(CAST(112312312312312312312312312312312312312 AS NUMBER)) AS big_number_bytes,
vsize(CAST(1 AS INTEGER)) AS integer_bytes,
vsize(CAST(100000000000000000000000000000000000000 AS INTEGER)) AS big_simple_integer_bytes,
vsize(CAST(112312312312312312312312312312312312312 AS INTEGER)) AS big_integer_bytes,

-- dates
vsize(SYSDATE) AS date_bytes,
vsize(SYSTIMESTAMP) AS ts_bytes,

-- others
vsize(CAST(HEXTORAW('DEADBEEF') AS RAW(32))) AS raw_bytes
FROM DUAL;

VARCHAR2(1) = 'Y' → 1 байт.
Ровно один символ.
VARCHAR2(20) = 'Hello' → 5 байт.
Количество байт совпадает с числом символов (латиница в AL32UTF8 = 1 байт).
VARCHAR2(20) = 'Привет' → 12 байт.
В AL32UTF8 каждая кириллическая буква кодируется в 2 байта.
CHAR(20) = 'Hello' → 20 байт.
Фиксированная длина, строка добивается пробелами.
NUMBER = 1 → 2 байта.
Маленькое число хранится очень компактно.

NUMBER = 10^38 → 2 байта.
«Простое» большое число, Oracle хранит его в виде степени 10.
NUMBER = 1123123... (38 знаков) → 21 байт.
«Сложное» большое число, почти максимум возможного.
INTEGER = 1 → 2 байта.
Это синоним NUMBER, поэтому хранится так же.
INTEGER = 10^38 → 2 байта.
Поведение то же самое, что у NUMBER.
INTEGER = 1123123... (38 знаков) → 21 байт.
Аналогично NUMBER с максимальной длиной.
DATE = SYSDATE → 7 байт.
Фиксированный внутренний формат: год, месяц, день, час, минута, секунда.
TIMESTAMP = SYSTIMESTAMP → 13 байт.
7 байт как у DATE + до 6 байт для дробных секунд и таймзоны.
RAW(32) = HEXTORAW('DEADBEEF') → 4 байта.
Ровно столько, сколько передано (байты напрямую).

Вот официальная ссылка на документацию: VSIZE
— что делает функция, синтаксис и примеры

💡 Выводы
1️⃣ Для полей-флагов (is_active, is_actual, is_admin) выгоднее использовать VARCHAR2(1) ('Y' / 'N').
2️⃣ Для целых чисел нет разницы между NUMBER и INTEGER. INTEGER — это подтип NUMBER (38,0), просто подчёркивает целочисленный характер поля.
3️⃣ Для полей типа VARCHAR2(4000) хранятся только фактические данные.

По умолчанию, Oracle резервирует 10% свободного пространства блока, чтобы при UPDATE не создавались дополнительные "ссылочные" блоки.
Если таблица статична (строки не изменяются), можно установить модификатор PTCFREE на 0, и без каких-либо последствий можно использовать максимальный размер VARCHAR2 (4000) при объявлении полей.
Если же таблица нестатична, то размер строковых полей требуется делать с запасом, но не более.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Пишите и Ваше мнение будет услышано.
#️⃣ #SQL #Oracle #PLSQL #CodeArchitecture
👍8👎1
🎵 «Кто сказал что безполезно биться головой об стену
Хлоп на лоб, глаза полезли, лоб становиться кpеменным.» — Вячеслав Бутусов

Сколько байт занимает выражение? Oracle vs PostgreSQL

Часть 2: PostgreSQL

📍 В PostgreSQL для этого есть функция pg_column_size:
SELECT
-- strings
pg_column_size(CAST('Y' AS VARCHAR(1)))
AS single_vchar_bytes,
pg_column_size(CAST('Hello' AS VARCHAR(4000)))
AS vchar_bytes,
pg_column_size(CAST('Привет' AS VARCHAR(4000)))
AS russian_vchar_bytes,
pg_column_size(CAST('Hello' AS CHAR(20)))
AS char_bytes,

-- numbers
pg_column_size(CAST(1 AS NUMERIC))
AS number_bytes,
pg_column_size(CAST(9000000000000000000 AS NUMERIC))
AS big_simple_number_bytes,
pg_column_size(CAST(9223372036854775807 AS NUMERIC))
AS big_number_bytes,
pg_column_size(CAST(1 AS BIGINT))
AS bigint_bytes,
pg_column_size(CAST(9000000000000000000 AS BIGINT))
AS big_simple_bigint_bytes,
pg_column_size(CAST(9223372036854775807 AS BIGINT))
AS big_bigint_bytes,

-- date
pg_column_size(CURRENT_DATE)
AS date_bytes,
pg_column_size(CURRENT_TIMESTAMP)
AS ts_bytes,

-- others
pg_column_size(E'\\xDEADBEEF'::bytea)
AS raw_bytes;


'Y'::VARCHAR(1) → 5 байт.
1 символ + служебные 4 байта varlena (хранение длины).
'Hello'::VARCHAR(4000) → 9 байт.
5 ASCII-символов + 4 байта служебной информации.
'Привет'::VARCHAR(4000) → 16 байт.
6 кириллических символов UTF-8 (по 2 байта) + 4 байта служебной информации.
'Hello'::CHAR(20) → 24 байта.
CHAR хранится как фиксированная длина, плюс 4 байта overhead.
1::NUMERIC → 8 байт.
Минимальное число в NUMERIC + служебные байты.
1e10::NUMERIC → 8 байт.
Большое число в NUMERIC, но умещается в минимальном формате.
1123123... (38 знаков)::NUMERIC → 16 байт.
«Сложное» большое число: больше блоков хранения и служебных байт.
1::BIGINT → 8 байт.
BIGINT всегда занимает 8 байт.
9000000000000000000::BIGINT → 8 байт.
Большое число в диапазоне BIGINT → 8 байт.
9223372036854775807::BIGINT → 8 байт.
Максимум для BIGINT, но размер фиксированный.
CURRENT_DATE (DATE) → 4 байта.
Хранится как число дней с 2000-01-01.
CURRENT_TIMESTAMP (TIMESTAMP) → 8 байт.
Хранится как количество микросекунд с 2000-01-01.
E'\\xDEADBEEF'::BYTEA → 8 байт.
4 байта данных + 4 байта служебной информации (varlena).

Вот официальная ссылка на документацию: pg_column_size
— что делает функция, синтаксис.

Почему для каждого значения требуется на 4 байта больше, чем в ORACLE, мне не ясно.
В документации нет понятного объяснения. Но это и не важно, главное, что выводы аналогичные!

💡 Выводы
1) Для полей-флагов (is_active, is_actual, is_admin) выгоднее использовать тип VARCHAR(1) ('Y', 'N')
2) Если использовать целые числа, нет разницы между объявлении NUMERIC и BIGINT в пределах допустимого, просто подчёркивает целочисленный характер поля.
3) Если поля объявлять как VARCHAR(4000), будут храниться, только то что по факту есть.
По умолчанию PostgreSQL ничего не резервирует, т.е. модификатор fillfactor равен 0. Фактически поведение PostgreSQL не зависит от заявленного предела строковых типов.
Если же fillfactor не равен 0, то размер строковых полей требуется делать с запасом, но не более.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Пишите и Ваше мнение будет прочитано.
#SQL #PostgreSQL #PLpgSQL #CodeArchitecture
👍3👎1
Вы используете поле - битовая маска у себя в коде?
Anonymous Poll
24%
Да. Использую
24%
Да. Не использую
51%
Нет. Не знаю
🎵 «Не дорого ценю я громкие слова,
От коих не одна кружится голова.
Я не ропщу о том, что отказали боги
Мне в сладкой участи оспаривать налоги» — Александр Пушкин

📌 Разбор задачи: Проверка на сбалансированные скобки процедурными методами

🧩 Условие:
Нужно проверить, сбалансированы ли скобки в строке.
Скобки считаются сбалансированными, если:
каждой открывающей соответствует закрывающая;
порядок правильный (( { [ ] } )).

⚙️ Алгоритм (кратко):
1️⃣ Создаём стек для хранения открывающихся скобок.
2️⃣ Идём по строке символ за символом.
3️⃣ Если встречаем '(', '{', '[' → кладём в стек.
4️⃣ Если встречаем ')', '}', ']':
• если стек пустой → ошибка (несбалансировано),
• иначе сверяем, что верх стека — соответствующая открывающая скобка.
Если совпадает → убираем её из стека.
5️⃣ После обхода строки стек должен быть пуст.
6️⃣ Если пуст — всё сбалансировано , иначе — нет .

🔢 Пример:
Строка: (124(){}[[] {123()}  ])
Результат: BALANCED

`SQL
DECLARE
str VARCHAR2(4000) := ' (124(){}[[] {123()} ]) ';

TYPE stack_tab IS TABLE OF VARCHAR2 (1);
stack stack_tab := stack_tab();

indexChar VARCHAR2 (1);
isBalanced VARCHAR2 (1) := 'Y';
BEGIN
FOR index# IN 1..LENGTH(str)
LOOP
indexChar := SUBSTR (str, index#, 1);

IF indexChar NOT IN ('(', '{', '[', ')', '}', ']') THEN
CONTINUE;
END IF;

IF indexChar IN ('(', '{', '[') THEN
stack.extend;
stack(stack.COUNT) := indexChar;
CONTINUE;
END IF;

IF indexChar IN (')', '}', ']') AND stack IS EMPTY THEN
isBalanced := 'N';
EXIT;
END IF;

IF indexChar = ')' THEN
IF stack (stack.COUNT) = '(' THEN
stack.delete(stack.COUNT);
CONTINUE;
ELSE
isBalanced := 'N';
EXIT;
END IF;
END IF;

IF indexChar = '}' THEN
IF stack (stack.COUNT) = '{' THEN
stack.delete(stack.COUNT);
CONTINUE;
ELSE
isBalanced := 'N';
EXIT;
END IF;
END IF;

IF indexChar = ']' THEN
IF stack (stack.COUNT) = '[' THEN
stack.delete(stack.COUNT);
CONTINUE;
ELSE
isBalanced := 'N';
EXIT;
END IF;
END IF;
END LOOP;

IF stack IS NOT EMPTY THEN
isBalanced := 'N';
END IF;

stack.delete;

IF isBalanced = 'N' THEN
dbms_output.put_line ('UNBALANCED');
ELSE
dbms_output.put_line ('BALANCED');
END IF;
EXCEPTION
WHEN OTHERS THEN
IF stack IS NOT NULL AND stack IS NOT EMPTY THEN
stack.delete;
RAISE;
END IF;
END;
`


👍 Палец вверх — если такие алгоритмы полезны.
👎 Палец вниз — если тема себя изжила.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Эльвира свой вариант выставила. А остальные? Что скажет "купечество"?
#️⃣ #SQL #Oracle #PLSQL
👍9👎2
image.png
1 MB
🎵 «Мои друзья — хоть не в болонии
Зато не тащут из семьи
А гадость пьют — из экономии
Хоть поутру — да на свои!» — Владимир Высоцкий

Поле как битовая маска

Представьте: у вас есть таблица с кучей полей-флагов.

Например:
is_internal
is_main
is_admin
is_resident
и т.д.

Все эти поля принимают только два значения: 0/1, 'Y'/'N' или аналогичные.

Но проблема в том, что таких флагов может быть десятки, и аналитики каждую неделю просят добавлять новые.
А это значит — переписывать код, менять десятки объектов в БД и заново накатывать на прод. 😩

Чтобы избежать этого ада, используют битовые маски.

🛠 Способы реализации битовых масок
1️⃣ Через INTEGER (NUMBER(38,0))

Каждый бит отвечает за конкретный флаг.

bit_mask =>
POWER(2,0) * CASE WHEN is_internal = 'Y' THEN 1 ELSE 0 END +
POWER(2,1) * CASE WHEN is_main = 'Y' THEN 1 ELSE 0 END +
POWER(2,2) * CASE WHEN is_admin = 'Y' THEN 1 ELSE 0 END +
POWER(2,3) * CASE WHEN is_resident = 'Y' THEN 1 ELSE 0 END

Пример:
is_internal = Y, остальные = N
👉 маска = 0001₂ = 1

Если is_internal=Y и is_resident=Y:
👉 маска = 1001₂ = 9

⚠️ Ограничение: максимум 38 бит.

2️⃣ Через RAW
Можно хранить как двоичные данные.
Пример для is_internal=Y и is_resident=Y:

1001₂ = 09 (HEX)

SQL-пример:
SELECT UTL_RAW.CAST_TO_RAW(CHR(BIN_TO_NUM(1,0,0,1)))
FROM dual;

3️⃣ Через VARCHAR2

Экзотичную битовую маску в виде VARCHAR2:
bit_mask = 'YNNY'
Каждый символ обозначает состояние флага.

⚖️ Баланс
Идея простая: вместо десятков двоичных полей мы храним одно поле с маской.

Но универсальность стоит ресурсов:
придётся нагружать CPU для декодирования маски
возможно нужен справочник, какой бит за что отвечает

🔥 Если тема интересна — далее покажу имплементацию.
Наберём хотя бы 20 🤓.

👍 Палец вверх — тема зашла.
👎 Палец вниз — не зашла.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Справедливую и не очень критику туда. 👇
#SQL #Oracle #PLSQL #CodeArchitecture
👍162
🎵 «Деньги бывают — такое бывает…» — Ленинград

📊 Вычисление дней просрочки по клиентам и счетам

Эта задача встречается на собеседованиях и в банковской аналитике.
Количество дней просрочки — ключевой показатель для расчёта резервов.

🔹 Условие задачи

Таблица движения средств по просроченной задолженности:
amount > 0 → долг возник, день считается первым днём просрочки
amount < 0 → долг уменьшается
Когда накопленный долг = 0 → задолженность погашена, первый и последний день включаются

💡 Важно: погашение происходит по принципу FIFO — сначала гасим старые долги, потом новые,
т.е. если клиент вовремя не погасил долг, то с этого дня начинается рассчет количества дней просрочки.
Если клиент вносил деньги для уплаты долга, то банк обязан сначала эти деньги учесть в самых старых просроченных платежах,
а потом, если остается остаток, то пробует погасить более новые долги.

Возможно, у разработчиков, не знакомых с кредитными задачами, возникнет сложность с логикой.
Во-первых, это нормально! Я только к банковским терминам привыкал полгода (План счетов, Дебет, Кредит, Просрочка, Вынос на просрочку, Погашение, Комиссии, Пенни, и прочий ужас).
Во-вторых, никто не обещал легкой жизни.

🔹 Таблица движения средств
CREATE TABLE account_movements (
client_id NUMBER,
account_id NUMBER,
payment_date DATE,
amount NUMBER
);


🔹 Пример данных
-- Клиент 1 — ежемесячные долги с частичными погашениями, полное погашение
INSERT INTO account_movements VALUES (1, 101, DATE '2024-01-01', 100);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-02-01', 100);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-03-01', 100);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-04-01', 100);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-05-01', 100);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-06-01', 100);

INSERT INTO account_movements VALUES (1, 101, DATE '2024-01-15', -99);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-02-15', -99);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-03-15', -99);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-04-15', -99);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-05-15', -99);
INSERT INTO account_movements VALUES (1, 101, DATE '2024-06-15', -99);

INSERT INTO account_movements VALUES (1, 101, DATE '2024-07-01', -6);

-- Клиент 2 — медленные погашения
INSERT INTO account_movements VALUES (2, 201, DATE '2024-01-01', 50);
INSERT INTO account_movements VALUES (2, 201, DATE '2024-02-01', 50);
INSERT INTO account_movements VALUES (2, 201, DATE '2024-03-01', 50);
INSERT INTO account_movements VALUES (2, 201, DATE '2024-04-01', 50);
INSERT INTO account_movements VALUES (2, 201, DATE '2024-05-01', 50);
INSERT INTO account_movements VALUES (2, 201, DATE '2024-06-01', 50);

INSERT INTO account_movements VALUES (2, 201, DATE '2024-02-15', -20);
INSERT INTO account_movements VALUES (2, 201, DATE '2024-03-20', -30);
INSERT INTO account_movements VALUES (2, 201, DATE '2024-05-10', -50);

INSERT INTO account_movements VALUES (2, 201, DATE '2024-08-01', -150);

-- Клиент 3 — не погашения
INSERT INTO account_movements VALUES (3, 301, DATE '2024-01-01', 200);
INSERT INTO account_movements VALUES (3, 301, DATE '2024-02-01', 200);
INSERT INTO account_movements VALUES (3, 301, DATE '2024-03-01', 200);
INSERT INTO account_movements VALUES (3, 301, DATE '2024-04-01', 200);
INSERT INTO account_movements VALUES (3, 301, DATE '2024-05-01', 200);
INSERT INTO account_movements VALUES (3, 301, DATE '2024-06-01', 200);

💡 Ожидаемый результат на 15.05.2024

Клиент Задолженность Дней просрочки Период
1 5 15 2024-05-01 – 2024-05-15
2 150 76 2024-03-01 – 2024-05-15
3 1000 136 2024-01-01 – 2024-05-15

В реальной жизни долги бывают разных видов, но принцип одинаков.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

👍 Банковская тема интересна
👎 Такой ужас видеть больше не хочу
#️⃣ #RealInterviewTasks #SQL #Oracle #PLSQL #PostgreSQL
👍5👎1
🎵 «Если друг оказался вдруг
И не друг, и не враг, а — так,
Если сразу не разберёшь,
Плох он или хорош...» — Владимир Высоцкий

Сегодня суббота — разбор задач оставим на будущее.
Продолжаем рубрику «Полезные ресурсы».

🖥️ Тема: Работа с Git и анализ кода

Раньше для работы с Git я использовал Git Extensions
для управления репозиториями, а для поиска и анализа файлов — Total Commander и Notepad++.

Но коллеги помогли (или слегка заставили 😉) перейти на JetBrains IntelliJ IDEA.
Сложная и мощная программа, к ней нужно привыкнуть, но она того стоит!
Особенно мне нравится поиск по ключевым словам и файлам, а также встроенные инструменты для анализа кода.
Вообще, если вы работаете с проектами в Git и большими кодовыми базами — рекомендую попробовать.

💎 Поддержка канала⁉️

👨‍💻 А вы используете Git? Какими инструментами для анализа файлов пользуетесь?
Делитесь в комментариях 👇
#️⃣ #Tools
1👍1🔥1
🎵 «Две звезды - две светлых повести,
В своей любви, как в невесомости.
Два голоса среди молчания,
В небесном храме звезд венчание.» — Алла Пугачева

📌 Разбор задачи: Два указателя — проверить, содержит ли отсортированный массив два числа, сумма которых равна заданному числу target.

Представьте, что у вас есть ряд чисел, и вам нужно найти пару, которая даёт нужную сумму.
Алгоритм (кратко):
– ставит «левый» указатель на начало массива,
– «правый» указатель — в конец,
– двигает их навстречу друг другу в зависимости от суммы текущей пары.

DECLARE
TYPE set_tab IS TABLE OF INTEGER;

"set" set_tab := set_tab (0,1,2,3,4,5,6,7,8,10);
target INTEGER := 15;
currentTarget INTEGER := 0;
leftIndex INTEGER := 1;
rightIndex INTEGER := "set".LAST;
isTargetExist VARCHAR2(1) := 'N';
BEGIN
WHILE leftIndex != rightIndex LOOP
currentTarget := "set"(leftIndex) + "set"(rightIndex);

IF currentTarget = target THEN
isTargetExist := 'Y';
EXIT;
ELSIF currentTarget > target THEN
rightIndex := rightIndex - 1;
ELSE
leftIndex := leftIndex + 1;
END IF;
END LOOP;

IF isTargetExist = 'Y' THEN
DBMS_OUTPUT.put_line('exist');
ELSE
DBMS_OUTPUT.put_line('not exist');
END IF;
END;

💡 Логика проста:
– Если сумма больше нужной — двигаем правый указатель влево;
– Если сумма меньше — двигаем левый указатель вправо;
– Если нашли — выход!

👍 Палец вверх — если нравится.
👎 Палец вниз — если не нравится.

⚠️ Хотите проверять скрипты без базы — используйте онлайн-песочницу.
💎 Поддержка канала⁉️

💬 Есть что добавить? 👇
#️⃣ #SQL #Oracle #PLSQL
👍41👎1
«Вы, конечно, стоите на краю финансовой пропасти? — спросил он Балаганова.
Это вы насчет денег? — сказал Шура. — Денег у меня нет уже целую неделю.
В таком случае вы плохо кончите, молодой, человек, — наставительно сказал Остап.
Финансовая пропасть-самая глубокая из всех пропастей, в нее можно падать всю жизнь.»
— Илья Ильф и Евгений Петров.

📊 Разбор задачи «Вычисление дней просрочки по клиентам и счетам»

"Пара слов без протокола"...
В банках подготовка данных для получения сводной таблицы долгов и погашений занимает приличное время.
Дело в том, что есть разные виды задолженностей и разный приоритет погашений для каждого вида задолженности.
А также есть корректирующие операции, которые проводятся задним числом.
Вообщем, это демо модель и я не претендую, что она оптимальная, так как у неё есть недостатки.
Недостаток в том, что приходится считать FIFO баланс даже тогда, когда уже в этом нет необходимости.
Из-за этого, я видел и процедурную реализацию данной задачи.
Какое решение подойдет именно Вам, нужно тестировать и разбираться.

Теперь разберем решение на SQL.

🔹 Шаг 1. Движение и накопленный FIFO-баланс
Сначала нам нужно посчитать «накопленный долг» на каждый день с учётом FIFO.
Для этого мы суммируем все возникшие долги (amount > 0) по дате, и отдельно все погашения (amount < 0) по счёту:
SELECT am.*,
SUM(CASE WHEN am.amount > 0 THEN am.amount ELSE 0 END)
OVER (PARTITION BY am.client_id, am.account_id
ORDER BY am.payment_date)
+ SUM(CASE WHEN am.amount < 0 THEN am.amount ELSE 0 END)
OVER (PARTITION BY am.client_id, am.account_id) AS fifo_balance
FROM account_movements am
WHERE am.payment_date <= DATE '2024-05-15'


👉 Здесь ключевая идея:
Долги мы считаем, как накопительную сумму.
Погашения долга считаем, как общую сумму по клиенту и по договору (счету)

🔹 Шаг 2. Определяем дату начала просрочки
Теперь для каждого клиента/счёта нужно найти первую дату, когда долг стал больше 0:
MIN(CASE WHEN am1.fifo_balance > 0 THEN am1.payment_date END) AS first_overdue_date

Если долг никогда не возникал → просрочки нет.

🔹 Шаг 3. Рассчитываем количество дней просрочки

Если у клиента есть дата первой просрочки, то количество дней = разница между контрольной датой и этой датой (оба дня включаются):
CASE
WHEN first_overdue_date IS NULL THEN 0
ELSE DATE '2024-05-15' - first_overdue_date + 1
END AS overdue_day_amount


Итого:
SELECT am2.client_id,
am2.account_id,
CASE
WHEN first_overdue_date IS NULL THEN 0
ELSE DATE '2024-05-15' - first_overdue_date + 1
END AS overdue_day_amount
FROM (
SELECT am1.client_id,
am1.account_id,
MIN(CASE WHEN am1.fifo_balance > 0 THEN am1.payment_date END) AS first_overdue_date
FROM (
SELECT am.*,
SUM(CASE WHEN am.amount > 0 THEN am.amount ELSE 0 END)
OVER (PARTITION BY am.client_id, am.account_id
ORDER BY am.payment_date)
+ SUM(CASE WHEN am.amount < 0 THEN am.amount ELSE 0 END)
OVER (PARTITION BY am.client_id, am.account_id) AS fifo_balance
FROM account_movements am
WHERE am.payment_date <= DATE '2024-05-15'
) am1
GROUP BY am1.client_id, am1.account_id
) am2;

💡 Что получилось
Клиент Количество дней просрочки
1 15
2 76
3 136

Есть еще интересней задачка на эту тему - поиск максимального количества дней просрочки по договору.
Задача не про заданную дату, а ретроспективно на всё время жизни договора.
Клиент мог выходить на просрочку несколько раз и в данный момент её не иметь.

👍 Палец вверх — банковская тема интересна.
👎 Палец вниз — Какая муть! Мне это категорически не интересно.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Хотите что-то добавить. 👇
#️⃣ #RealInterviewTasks #SQL #Oracle #PLSQL #PostgreSQL
👍12👎1
🎵 «Пусть все будет так, как ты захочешь.
Пусть твои глаза, как пpежде, гоpят.
Я с тобой опять сегодня этой ночью.
Hу а впpочем, следующей ночью,
Если захочешь, я опять у тебя.» — Чайф.

📌 Случай из практики

Допустим, у нас есть таблица:
CREATE TABLE something (
id NUMBER,
col1 VARCHAR2(20),
col2 VARCHAR2(10),
col3 VARCHAR2(500),
created DATE,
created_by NUMBER,
updated DATE,
updated_by NUMBER
);

🔹 Таблица небольшая, но часто читаемая. Записи в ней редактируются, но не слишком активно.

Требование: изменить поле col1 → нужно перевести его в тип NUMBER и пересчитать значения.
Например:
CASE 
WHEN col1 = 'value1' THEN 1
WHEN col1 = 'value2' THEN 2
ELSE 3
END

Казалось бы, задача простая:

1. Добавляем новое поле col1_new.
2. Заполняем его как нужно.
3. Переименовываем старое поле в col1_old, а новое — в col1.
4. Если всё прошло успешно — col1_old можно убрать или пометить как неиспользуемое.

Но есть дополнительное условие:
Новое поле не должно сместиться в конец списка, а должно остаться на своём месте — сразу после id и перед col2.

🤔 Как бы вы справились с такой задачей?

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Ваши мысли и Ваши решения туда. 👇
#️⃣ #Cases #SQL #Oracle #PLSQL #PostgreSQL #PLpgSQL
👍2👎1
DB developers channel
🎵 «Пусть все будет так, как ты захочешь. Пусть твои глаза, как пpежде, гоpят. Я с тобой опять сегодня этой ночью. Hу а впpочем, следующей ночью, Если захочешь, я опять у тебя.» — Чайф. 📌 Случай из практики Допустим, у нас есть таблица: CREATE TABLE something…
🎵 «Дорога, а в дороге МАЗ
Который по уши увяз
В кабине тьма, напарник третий час молчит
Хоть бы кричал, аж зло берёт
Назад пятьсот, вперёд пятьсот
А он зубами танец с саблями стучит.» — Владимир Высоцкий

🛠 Как аккуратно поменять тип колонки в Oracle и сохранить порядок полей

Напомню первоначальную постановку:
есть таблица something (небольшая, но часто читаемая), где поле col1 хранится как VARCHAR2(20), но его нужно привести к типу NUMBER и при этом пересчитать значения по правилу:

CASE 
WHEN col1 = 'value1' THEN 1
WHEN col1 = 'value2' THEN 2
ELSE 3
END

На первый взгляд — простая операция: добавил новую колонку, заполнил, потом переименовал. Но тут есть важное условие:
👉 новая колонка должна остаться на своём месте — сразу после id, а не уехать в конец таблицы.

В Oracle нет встроенного механизма «переместить колонку», поэтому решение чуть хитрее.

🔑 Подход
Ставим таблицу в READ ONLY, чтобы на момент миграции гарантировать отсуствие изменений на источнике.
Переименовываем индексы и констрейны, триггеры на старой таблице.
Создаём новую таблицу с нужной структурой — в ней уже определяем col1 как NUMBER.
Переносим данные из старой таблицы в новую с пересчётом поля col1:
Восстанавливаем объекты на новой таблице (индексы, констрейны, триггеры).
Переименовываем таблицы: старая уходит в архив (something_old), новая становится боевой (something).

📋 Полный скрипт
-- lock the table to prevent modifications
ALTER TABLE something READ ONLY;

-- rename all dependent objects on the table, if any (indexes, constraints, triggers)

-- create a new table (can be done via CTAS if there are no virtual columns)
CREATE TABLE something_new (
id NUMBER,
col1 NUMBER,
col2 VARCHAR2(10),
col3 VARCHAR2(500),
created DATE,
created_by NUMBER,
updated DATE,
updated_by NUMBER
);

-- populate the new table with data from the old one
INSERT INTO something_new (id, col1, col2, col3, created, created_by, updated, updated_by)
SELECT
s.id,
CASE
WHEN s.col1 = 'value1' THEN 1
WHEN s.col1 = 'value2' THEN 2
ELSE 3
END AS col1,
s.col2,
s.col3,
s.created,
s.created_by,
s.updated,
s.updated_by
FROM something s;

-- recreate dependent objects on new table (indexes, constraints, triggers, grants)
-- drop dependent objects on the old table

-- rename the tables
ALTER TABLE something RENAME TO something_old;
ALTER TABLE something_new RENAME TO something;

-- keep something_old for rollback scripts or to preserve original data
-- later it can be safely dropped if no longer needed

🧩 Нюансы
Таблица доступна для чтения, почти всё время, за исключением времени переименования таблиц, но это быстрая операция.
Такой способ гарантирует правильный порядок колонок.
Старую таблицу (something_old) лучше оставить как «бэкап» на время. Если всё прошло успешно — можно дропнуть.

Конечно, такую задачу можно сделать через доп. поле и модификацию текущего поля.
Но таблица будет не доступна больше времени и залочить таблицу не получится.
И тут надо выбирать метод исходя из Ваших обстоятельств.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Если есть другое решение задачи. Поделитесь! 👇
#️⃣ #Cases #SQL #Oracle #PLSQL #PostgreSQL #PLpgSQL
👍5👎1
🎵 «Ну, и меня, конечно, Зин
Всё время тянет в магазин
А там — друзья, я ж, Зин
Не пью один!» - Владимир Высоцкий

📌 Разбор задачи: Удаление N-го узла с конца связного списка
Я продолжаю разбирать задачи и решаю их исключительно процедурными методами.

Представьте себе очередь в магазин.
Все стоят один за другим, и каждый знает только, кто следующий.
И вот приходит администратор и говорит: «Уберите шестого с конца!»

Что делает скрипт?
Сначала пересчитывает всю очередь — сколько людей стоит.
Определяет, кто находится перед тем самым шестым с конца.
Просит этого человека «пропустить следующего» и смотреть уже на того, кто идёт после удаляемого.
А сам шестой — тихо уходит из очереди.
Вот так список сокращается.

CREATE OR REPLACE TYPE node IS OBJECT (VALUE NUMBER, NEXT REF node);
/
CREATE TABLE nodes OF node;
/

DECLARE
stack REF node;

PROCEDURE init_stack (stack OUT REF node, amount IN INTEGER) IS
current1 REF node;
current2 REF node;
BEGIN
FOR i IN 1 .. amount LOOP
IF current1 IS NULL THEN
INSERT INTO nodes n
VALUES (node ("VALUE" => i, NEXT => NULL))
RETURNING REF (n)
INTO current1;

stack := current1;
ELSE
INSERT INTO nodes n
VALUES (node ("VALUE" => i, NEXT => NULL))
RETURNING REF (n)
INTO current2;

UPDATE nodes n
SET n.NEXT = current2
WHERE REF (n) = current1;

current1 := current2;
current2 := NULL;
END IF;
END LOOP;
END init_stack;

PROCEDURE print_stack (stack IN REF node) IS
current REF node;
currentNode node;
i INTEGER := 1;
BEGIN
current := stack;

IF current IS NULL THEN
RETURN;
END IF;

WHILE current IS NOT NULL LOOP
SELECT DEREF (current) INTO currentNode FROM DUAL;

DBMS_OUTPUT.put_line (
'['
|| TO_CHAR (i)
|| '] value '
|| TO_CHAR (currentNode."VALUE")
|| ', '
|| CASE WHEN currentNode.NEXT IS NULL THEN 'next is null' ELSE 'next is not null' END);

i := i + 1;
current := currentNode.next;
END LOOP;
END print_stack;

PROCEDURE delete_node_from_end (stack IN REF node, index# IN INTEGER)
IS
current REF node;
currentNode node;
nodeCounter INTEGER := 0;
properFromBeginIndex# INTEGER := 0;
previousDeletedNode REF node;
deleted REF node;
deletedNode node;
BEGIN
-- считаем количество элементов
current := stack;
WHILE current IS NOT NULL
LOOP
SELECT DEREF (current) INTO currentNode FROM DUAL;
current := currentNode.next;
nodeCounter := nodeCounter + 1;
END LOOP;

properFromBeginIndex# := nodeCounter - index#;

current := stack;
nodeCounter := 0;
previousDeletedNode := NULL;
WHILE current IS NOT NULL AND nodeCounter < properFromBeginIndex#
LOOP
previousDeletedNode := current;
SELECT DEREF (current) INTO currentNode FROM DUAL;
current := currentNode.next;
nodeCounter := nodeCounter + 1;
END LOOP;

SELECT DEREF (previousDeletedNode) INTO currentNode FROM DUAL;
deleted := currentNode.next;

SELECT DEREF (deleted) INTO deletedNode FROM DUAL;

UPDATE nodes n
SET n.next = deletedNode.next
WHERE REF (n) = previousDeletedNode;

DELETE FROM nodes n WHERE REF (n) = deleted;
END delete_node_from_end;
BEGIN
init_stack (stack => stack, amount => 10);

DBMS_OUTPUT.put_line ('stack');
print_stack (stack => stack);

delete_node_from_end (stack => stack, index# => 6);

DBMS_OUTPUT.put_line ('updatedStack');
print_stack (stack => stack);

COMMIT;
END;

👍 Палец вверх — если цикл нужно продолжать.
👎 Палец вниз — если лучше вернуться к DB.

⚠️ Хотите проверить скрипты, но базы нет под рукой — онлайн-песочница вам в помощь.
💎 Поддержка канала⁉️

💬 Обратную связь туда 👇
#️⃣ #SQL #Oracle #PLSQL
👍6👎2
🔆«Тишина должна быть в библиотеке...» — Уральские пельмени

📌 Серия «Оптимизация SQL-запросов»
Этот пост — для тех, у кого возникают проблемы с пониманием плана запроса как такового.

Прежде чем разбирать конкретные планы запросов и их оптимизацию, давайте уясним, что такое план запроса.
Почему СУБД не всегда может гарантировать оптимальный план? И зачем нужны хинты (подсказки)?

📚 До изобретения баз данных человечество уже умело хранить и обрабатывать большие объёмы информации.
Где же? — спросите вы. Конечно же, в библиотеках!
Те, кто разрабатывал модели баз данных, имели опыт работы с библиотеками, и многие термины были заимствованы именно оттуда: индекс, ключевые слова и пр.

Аналогия с библиотекарем
Представим, что мы в самой большой библиотеке мира — Библиотеке Конгресса США в Вашингтоне.
Вы библиотекарь, и у вас нет никаких электронных устройств для помощи. Только:

📦 хранилище книг,
📑 индекс по названию,
✍️ индекс по автору,
🔑 индекс по ключевым словам.

Теперь рассмотрим примеры:

1️⃣ Самый простой запрос — «выдать все книги».
Подвозим вагоны и сгружаем в любом порядке.
Аналог: TABLE ACCESS FULL

2️⃣ Самый лёгкий запрос — найти книгу с известным местом хранения.
Просто идём и берём её, даже индекс не нужен.
Аналог: TABLE ACCESS BY ROWID

3️⃣ Запрос на книги автора — например, «Александр Сергеевич Пушкин».
Идём в индекс по авторам и получаем ссылки на его книги.
Аналог: INDEX RANGE SCAN (по диапазону).

Но что, если запрос будет таким:
автор пишет на индоевропейском языке,
фамилия начинается на «П»,
имя заканчивается на «р»,
жанр книги — сказка,
книга про рыбу.
Как быть?

Тут уже возникает несколько вариантов (планов) поиска:
искать авторов по первой букве,
искать книги по ключевым словам «сказка» и «рыба»,
пробовать разные комбинации,
или вовсе перебрать все книги.
👉 У вас уже 4 возможных плана запроса! И оптимизатору тоже приходится выбирать, какой путь будет наименее затратным.

⚙️ Оптимизатор:
каждому плану присваивает «стоимость»,
опирается на статистику (собранную заранее или из прошлых запросов),
иногда ошибается, если неправильно оценил путь поиска.

📊 Именно поэтому один и тот же запрос на разных базах может выполняться разными способами.

Если запрос сложный, нужно убедиться, что план не отличается от того, что был в тестовой среде.
Если отличается — применяем хинт или переписываем запрос, чтобы сделать его «понятнее» для оптимизатора.

💡 Главная мысль:
Чтобы понять, что такое план запроса, поставьте себя на место библиотекаря.
Продумайте, как бы вы искали данные вручную.
Именно так «думает» оптимизатор.

Дальше уже техника:
как подсказать оптимизатору, что он ошибается,
или как переписать запрос, чтобы сделать его проще для анализа.

Итого:
План запроса — это последовательность шагов.
Набор приёмов ограничен и часто повторяется.
Главное — понимать логику поиска.

Надеюсь, с понятием «план запроса» мы разобрались.
Дальше будем разбирать конкретные шаги и реальные примеры.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

👍 Палец вверх — продолжаем!
👎 Палец вниз — скучно и неинтересно.

Если есть вопросы, задавайте.
Если Вы опытный и заметили ошибку и/или неточность пишите тоже.
Если же ни то, ни другое, то всё равно пишите. 👇
#️⃣ #SQLOptimization
👍27
🎵 «Иду с дружком, гляжу — стоят
Они стояли молча в ряд
Они стояли молча в ряд
Их было восемь.
Со мною — нож, решил я: что ж
Меня так просто не возьмёшь.
Держитесь, гады! Держитесь, гады!» — Владимир Высоцкий

Сегодня суббота — задачи разбирать будем позже.
А пока продолжаем рубрику «Полезные ресурсы».

🖥️ Тема выпуска: Телеграм-каналы и чаты

Поскольку я блогер, то волей-неволей приходится изучать «конкурирующие фирмы».
Каналы, как и люди, — все разные:
кто-то с отличными постами, но навязчивой рекламой;
кто-то — с классным оформлением, но скучным контентом.

📢 Уважаемые читатели!
Делюсь подборкой профильных каналов на русском языке,
а также списком чатов, которые я лично не читаю,
поэтому оценить их качество не берусь — чаты просто не моё 🙂

🔧 Каналы
https://t.me/dbbooks (16.3k 👨‍💻) — крутой ресурс с множеством профильных книг
https://t.me/seniorsql (15.8k) — образовательный канал с ненавязчивой рекламой
https://t.me/sql_ready (12.4k) — профильный канал с интересным оформлением
https://t.me/sqlquestions (10.2k) — канал с интересной подачей и умеренной рекламой
https://t.me/sqlacademyofficial (8.9k) — образовательный канал с приятной подачей
https://t.me/db_in_it (8.2k) — инфо-канал с хорошим дизайном и лёгкой рекламой
https://t.me/database_info (8.1k) — образовательный канал с аккуратным оформлением
https://t.me/sqlprofi (5.2k) — интересный канал с оригинальной подачей
https://t.me/oracle_dbd (3.2k) — полезный контент, но реклама назойлива
https://t.me/pg_guru (2.8k) — профильный канал по PostgreSQL
https://t.me/sql_oracle_databases (1.8k) — образовательный ресурс по SQL и Oracle

💬 Чаты
https://t.me/sql_beginner (4.8k)
https://t.me/PostgreSQL_1C_Linux (4.1k)
https://t.me/sql_ninja (4.1k)
https://t.me/oracle_ru (1.8k)
https://t.me/oracle_dba_ru (1.5k)
https://t.me/pg_probackup (564)
https://t.me/databaselabru (273)
https://t.me/postgresprochat (79)

📚 Предлагаю всем желающим ознакомиться и найти себе контент по душе.

💎 Поддержка канала⁉️

👨‍💻 Скорее всего, я что-то пропустил — какие интересные профильные ресурсы на русском знаете Вы? А может, есть классные источники на других языках? Делитесь в комментариях. 👇
#️⃣ #Tools
👍8🔥1
Затравка для следующего поста.😊 Знаете ли функцию, которая безопасно перекомпилирует инвалидные объекты🩼 всей базы Oracle в параллели🚀?
Anonymous Poll
26%
Да, знаю.
49%
Нет, не знаю.
26%
Инвалиды!? А что это?
🎵 «И посредине этого разгула
Я прошептал на ухо жениху -
И жениха, как будто ветром сдуло,-
Невеста, вон, рыдает наверху.» — Владимир Высоцкий

Накат и откат изменений в Oracle

Для управления изменениями в базе данных Oracle важно иметь чёткую стратегию наката и отката.
Ниже — модель, как это можно организовать на любой базе: тестовой или боевой.

🔹 Модель действий

1. Считаем количество инвалидных объектов до наката
2. Запускаем скрипт наката/отката изменений
3. Перекомпилируем всех "инвалидов"
4. Считаем количество инвалидов после наката и сравниваем
5. Если есть ошибки — протоколируем их в отдельный журнал, например: INVALID_ERRORS$TASK_NUMBER

📌 Эта модель подходит как для наката изменений, так и для отката.
Таблица фиксирует все текущие ошибки и позволяет легко отслеживать изменения.

🔹 Создание таблицы ошибок
CREATE TABLE INVALID_ERRORS$TASK_NUMBER AS 
SELECT
o.owner,
o.object_type,
o.object_name,
e.sequence,
e.line,
e.position,
e.text
FROM
dba_objects o
INNER JOIN
dba_errors e
ON o.object_name = e.name AND o.owner = e.owner
WHERE
o.status = 'INVALID'
AND o.object_type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'VIEW', 'TRIGGER')
ORDER BY
o.owner,
o.object_type,
o.object_name,
e.sequence;

🔹 Параллельная перекомпиляция объектов
После наката/отката рекомендуется использовать UTL_RECOMP для перекомпиляции всех объектов в параллели, чтобы база была в рабочем состоянии:
BEGIN
SYS.UTL_RECOMP.recomp_parallel(
threads => 4
);
END;
/

Вот ссылка на официальную документацию UTL_RECOMP.

🔹 Итог
Всегда фиксируйте текущее состояние базы перед накатом.
Используйте таблицу ошибок для логирования ошибок.
Имейте скрипт отката на случай непредвиденных проблем.
После наката/отката запускайте параллельную перекомпиляцию объектов, чтобы минимизировать INVALID.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

👍 Палец вверх — интересно!
👎 Палец вниз — мне это не нужно, и потому скучно!

Интересна Ваша обратная связь - пишите тут 👇.
#️⃣ #Cases #SQL #Oracle #PLSQL
👍91👎1
🚀 Хотите прокачать SQL, но базы под рукой нет?
У моего коллеги Славы Рожнева — автора курса SQL в Яндексе — есть два ресурса:

🔹 sqltest.online — готовые задачи и тесты для практики.
🔹 sqlize.online — онлайн-песочница для запросов прямо в браузере.

📊 Отличный вариант, чтобы тренироваться в любом месте и в любое время!
👍5🔥1
🎵 «Один говорил: "Нам свобода - награда:
Мы поезд, куда надо ведем".
Другой говорил: "Задаваться не надо.
Как сядем в него, так и сойдем".» — Андрей Макаревич

📌 Разбор задачи: Вычисление n-го числа Фибоначчи через динамическое программирование
Представьте себе, что вы строите лестницу чисел.
Каждый новый шаг — это сумма двух предыдущих.
И вот приходит администратор и говорит: «Посчитайте десятый Фибоначчи!»

Что делает скрипт?
1. Создаёт «лестницу» чисел (массив).
2. Заполняет первые два шага единицами.
3. Каждый следующий шаг — это сумма двух предыдущих.
4. В конце вы получаете нужное число и всю последовательность целиком.

DECLARE
TYPE matrix_tab IS TABLE OF INTEGER;

fibMatrix matrix_tab := matrix_tab ();

fibonachiAmount INTEGER;

PROCEDURE get_fibonachi_matrix (index# IN INTEGER, matrix IN OUT NOCOPY matrix_tab, fibonachi_amount OUT INTEGER) IS
BEGIN
IF matrix.COUNT >= index# + 1 THEN
fibonachi_amount := matrix (index# + 1);
RETURN;
END IF;

IF index# >= 0 THEN
matrix.EXTEND;
matrix (matrix.COUNT) := 1;

IF index# = 0 THEN
fibonachi_amount := 1;
RETURN;
END IF;
END IF;

IF index# >= 1 THEN
matrix.EXTEND;
matrix (matrix.COUNT) := 1;

IF index# = 1 THEN
fibonachi_amount := 1;
RETURN;
END IF;
END IF;

FOR i IN 3 .. index# + 1 LOOP
matrix.EXTEND;
matrix (matrix.COUNT) := matrix (matrix.COUNT - 1) + matrix (matrix.COUNT - 2);
END LOOP;

fibonachi_amount := matrix (index# + 1);
END get_fibonachi_matrix;

PROCEDURE print_matrix (matrix IN OUT NOCOPY matrix_tab) IS
str VARCHAR2 (100);
BEGIN
FOR i IN 1 .. matrix.COUNT LOOP
str := str || ' ' || TO_CHAR (matrix (i));
END LOOP;

DBMS_OUTPUT.put_line (str);
END print_matrix;
BEGIN
get_fibonachi_matrix (index# => 10, matrix => fibMatrix, fibonachi_amount => fibonachiAmount);

DBMS_OUTPUT.put_line ('fibonachiAmount = ' || TO_CHAR (fibonachiAmount));

print_matrix (matrix => fibMatrix);
END;

💡 Как работает:
Первые два числа всегда 1.
Каждый новый элемент — это сумма двух предыдущих.
В итоге получаем как конкретное число, так и всю последовательность.

👍 Палец вверх — есть желание изучать алгоритмы.
👎 Палец вниз — хватит.

⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️

💬 Что скажет "купечество"?
#️⃣ #SQL #Oracle #PLSQL
👍7👎3