🎵 «Не дорого ценю я громкие слова,
От коих не одна кружится голова.
Я не ропщу о том, что отказали боги
Мне в сладкой участи оспаривать налоги» — Александр Пушкин
📌 Разбор задачи: Проверка на сбалансированные скобки процедурными методами
🧩 Условие:
Нужно проверить, сбалансированы ли скобки в строке.
Скобки считаются сбалансированными, если:
каждой открывающей соответствует закрывающая;
порядок правильный (( { [ ] } )).
⚙️ Алгоритм (кратко):
1️⃣ Создаём стек для хранения открывающихся скобок.
2️⃣ Идём по строке символ за символом.
3️⃣ Если встречаем '(', '{', '[' → кладём в стек.
4️⃣ Если встречаем ')', '}', ']':
• если стек пустой → ошибка (несбалансировано),
• иначе сверяем, что верх стека — соответствующая открывающая скобка.
Если совпадает → убираем её из стека.
5️⃣ После обхода строки стек должен быть пуст.
6️⃣ Если пуст — всё сбалансировано ✅, иначе — нет ❌.
🔢 Пример:
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
От коих не одна кружится голова.
Я не ропщу о том, что отказали боги
Мне в сладкой участи оспаривать налоги» — Александр Пушкин
📌 Разбор задачи: Проверка на сбалансированные скобки процедурными методами
🧩 Условие:
Нужно проверить, сбалансированы ли скобки в строке.
Скобки считаются сбалансированными, если:
каждой открывающей соответствует закрывающая;
порядок правильный (( { [ ] } )).
⚙️ Алгоритм (кратко):
1️⃣ Создаём стек для хранения открывающихся скобок.
2️⃣ Идём по строке символ за символом.
3️⃣ Если встречаем '(', '{', '[' → кладём в стек.
4️⃣ Если встречаем ')', '}', ']':
• если стек пустой → ошибка (несбалансировано),
• иначе сверяем, что верх стека — соответствующая открывающая скобка.
Если совпадает → убираем её из стека.
5️⃣ После обхода строки стек должен быть пуст.
6️⃣ Если пуст — всё сбалансировано ✅, иначе — нет ❌.
🔢 Пример:
Строка: (124(){}[[] {123()} ])
Результат: BALANCED`SQLDECLARE
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-пример:
3️⃣ Через VARCHAR2
Экзотичную битовую маску в виде VARCHAR2:
bit_mask = 'YNNY'
Каждый символ обозначает состояние флага.
⚖️ Баланс
Идея простая: вместо десятков двоичных полей мы храним одно поле с маской.
Но ✋ универсальность стоит ресурсов:
придётся нагружать CPU для декодирования маски
возможно нужен справочник, какой бит за что отвечает
🔥 Если тема интересна — далее покажу имплементацию.
Наберём хотя бы 20 🤓.
👍 Палец вверх — тема зашла.
👎 Палец вниз — не зашла.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Справедливую и не очень критику туда. 👇
#SQL #Oracle #PLSQL #CodeArchitecture
Зато не тащут из семьи
А гадость пьют — из экономии
Хоть поутру — да на свои!» — Владимир Высоцкий
⚡ Поле как битовая маска
Представьте: у вас есть таблица с кучей полей-флагов.
Например:
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
👍16❤2
🎵 «Деньги бывают — такое бывает…» — Ленинград
📊 Вычисление дней просрочки по клиентам и счетам
Эта задача встречается на собеседованиях и в банковской аналитике.
Количество дней просрочки — ключевой показатель для расчёта резервов.
🔹 Условие задачи
Таблица движения средств по просроченной задолженности:
amount > 0 → долг возник, день считается первым днём просрочки
amount < 0 → долг уменьшается
Когда накопленный долг = 0 → задолженность погашена, первый и последний день включаются
💡 Важно: погашение происходит по принципу FIFO — сначала гасим старые долги, потом новые,
т.е. если клиент вовремя не погасил долг, то с этого дня начинается рассчет количества дней просрочки.
Если клиент вносил деньги для уплаты долга, то банк обязан сначала эти деньги учесть в самых старых просроченных платежах,
а потом, если остается остаток, то пробует погасить более новые долги.
Возможно, у разработчиков, не знакомых с кредитными задачами, возникнет сложность с логикой.
Во-первых, это нормально! Я только к банковским терминам привыкал полгода (План счетов, Дебет, Кредит, Просрочка, Вынос на просрочку, Погашение, Комиссии, Пенни, и прочий ужас).
Во-вторых, никто не обещал легкой жизни.
🔹 Таблица движения средств
🔹 Пример данных
💡 Ожидаемый результат на 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
📊 Вычисление дней просрочки по клиентам и счетам
Эта задача встречается на собеседованиях и в банковской аналитике.
Количество дней просрочки — ключевой показатель для расчёта резервов.
🔹 Условие задачи
Таблица движения средств по просроченной задолженности:
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
И не друг, и не враг, а — так,
Если сразу не разберёшь,
Плох он или хорош...» — Владимир Высоцкий
✨ Сегодня суббота — разбор задач оставим на будущее.
Продолжаем рубрику «Полезные ресурсы».
🖥️ Тема: Работа с Git и анализ кода
Раньше для работы с Git я использовал Git Extensions
для управления репозиториями, а для поиска и анализа файлов — Total Commander и Notepad++.
Но коллеги помогли (или слегка заставили 😉) перейти на JetBrains IntelliJ IDEA.
Сложная и мощная программа, к ней нужно привыкнуть, но она того стоит!
Особенно мне нравится поиск по ключевым словам и файлам, а также встроенные инструменты для анализа кода.
Вообще, если вы работаете с проектами в Git и большими кодовыми базами — рекомендую попробовать.
💎 Поддержка канала⁉️
👨💻 А вы используете Git? Какими инструментами для анализа файлов пользуетесь?
Делитесь в комментариях 👇
#️⃣ #Tools
❤1👍1🔥1
🎵 «Две звезды - две светлых повести,
В своей любви, как в невесомости.
Два голоса среди молчания,
В небесном храме звезд венчание.» — Алла Пугачева
📌 Разбор задачи: Два указателя — проверить, содержит ли отсортированный массив два числа, сумма которых равна заданному числу target.
Представьте, что у вас есть ряд чисел, и вам нужно найти пару, которая даёт нужную сумму.
Алгоритм (кратко):
– ставит «левый» указатель на начало массива,
– «правый» указатель — в конец,
– двигает их навстречу друг другу в зависимости от суммы текущей пары.
💡 Логика проста:
– Если сумма больше нужной — двигаем правый указатель влево;
– Если сумма меньше — двигаем левый указатель вправо;
– Если нашли — выход!
👍 Палец вверх — если нравится.
👎 Палец вниз — если не нравится.
⚠️ Хотите проверять скрипты без базы — используйте онлайн-песочницу.
💎 Поддержка канала⁉️
💬 Есть что добавить? 👇
#️⃣ #SQL #Oracle #PLSQL
В своей любви, как в невесомости.
Два голоса среди молчания,
В небесном храме звезд венчание.» — Алла Пугачева
📌 Разбор задачи: Два указателя — проверить, содержит ли отсортированный массив два числа, сумма которых равна заданному числу 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
👍4❤1👎1
✨ «Вы, конечно, стоите на краю финансовой пропасти? — спросил он Балаганова.
Это вы насчет денег? — сказал Шура. — Денег у меня нет уже целую неделю.
В таком случае вы плохо кончите, молодой, человек, — наставительно сказал Остап.
Финансовая пропасть-самая глубокая из всех пропастей, в нее можно падать всю жизнь.»
— Илья Ильф и Евгений Петров.
📊 Разбор задачи «Вычисление дней просрочки по клиентам и счетам»
"Пара слов без протокола"...
В банках подготовка данных для получения сводной таблицы долгов и погашений занимает приличное время.
Дело в том, что есть разные виды задолженностей и разный приоритет погашений для каждого вида задолженности.
А также есть корректирующие операции, которые проводятся задним числом.
Вообщем, это демо модель и я не претендую, что она оптимальная, так как у неё есть недостатки.
Недостаток в том, что приходится считать FIFO баланс даже тогда, когда уже в этом нет необходимости.
Из-за этого, я видел и процедурную реализацию данной задачи.
Какое решение подойдет именно Вам, нужно тестировать и разбираться.
Теперь разберем решение на SQL.
🔹 Шаг 1. Движение и накопленный FIFO-баланс
Сначала нам нужно посчитать «накопленный долг» на каждый день с учётом FIFO.
Для этого мы суммируем все возникшие долги (amount > 0) по дате, и отдельно все погашения (amount < 0) по счёту:
👉 Здесь ключевая идея:
Долги мы считаем, как накопительную сумму.
Погашения долга считаем, как общую сумму по клиенту и по договору (счету)
🔹 Шаг 2. Определяем дату начала просрочки
Теперь для каждого клиента/счёта нужно найти первую дату, когда долг стал больше 0:
Если долг никогда не возникал → просрочки нет.
🔹 Шаг 3. Рассчитываем количество дней просрочки
Если у клиента есть дата первой просрочки, то количество дней = разница между контрольной датой и этой датой (оба дня включаются):
Итого:
💡 Что получилось
Есть еще интересней задачка на эту тему - поиск максимального количества дней просрочки по договору.
Задача не про заданную дату, а ретроспективно на всё время жизни договора.
Клиент мог выходить на просрочку несколько раз и в данный момент её не иметь.
👍 Палец вверх — банковская тема интересна.
👎 Палец вниз — Какая муть! Мне это категорически не интересно.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Хотите что-то добавить. 👇
#️⃣ #RealInterviewTasks #SQL #Oracle #PLSQL #PostgreSQL
Это вы насчет денег? — сказал Шура. — Денег у меня нет уже целую неделю.
В таком случае вы плохо кончите, молодой, человек, — наставительно сказал Остап.
Финансовая пропасть-самая глубокая из всех пропастей, в нее можно падать всю жизнь.»
— Илья Ильф и Евгений Петров.
📊 Разбор задачи «Вычисление дней просрочки по клиентам и счетам»
"Пара слов без протокола"...
В банках подготовка данных для получения сводной таблицы долгов и погашений занимает приличное время.
Дело в том, что есть разные виды задолженностей и разный приоритет погашений для каждого вида задолженности.
А также есть корректирующие операции, которые проводятся задним числом.
Вообщем, это демо модель и я не претендую, что она оптимальная, так как у неё есть недостатки.
Недостаток в том, что приходится считать 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очем, следующей ночью,
Если захочешь, я опять у тебя.» — Чайф.
📌 Случай из практики
Допустим, у нас есть таблица:
🔹 Таблица небольшая, но часто читаемая. Записи в ней редактируются, но не слишком активно.
Требование: изменить поле col1 → нужно перевести его в тип NUMBER и пересчитать значения.
Например:
Казалось бы, задача простая:
1. Добавляем новое поле col1_new.
2. Заполняем его как нужно.
3. Переименовываем старое поле в col1_old, а новое — в col1.
4. Если всё прошло успешно — col1_old можно убрать или пометить как неиспользуемое.
❗ Но есть дополнительное условие:
Новое поле не должно сместиться в конец списка, а должно остаться на своём месте — сразу после id и перед col2.
🤔 Как бы вы справились с такой задачей?
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Ваши мысли и Ваши решения туда. 👇
#️⃣ #Cases #SQL #Oracle #PLSQL #PostgreSQL #PLpgSQL
Пусть твои глаза, как п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 и при этом пересчитать значения по правилу:
На первый взгляд — простая операция: добавил новую колонку, заполнил, потом переименовал. Но тут есть важное условие:
👉 новая колонка должна остаться на своём месте — сразу после id, а не уехать в конец таблицы.
В Oracle нет встроенного механизма «переместить колонку», поэтому решение чуть хитрее.
🔑 Подход
Ставим таблицу в READ ONLY, чтобы на момент миграции гарантировать отсуствие изменений на источнике.
Переименовываем индексы и констрейны, триггеры на старой таблице.
Создаём новую таблицу с нужной структурой — в ней уже определяем col1 как NUMBER.
Переносим данные из старой таблицы в новую с пересчётом поля col1:
Восстанавливаем объекты на новой таблице (индексы, констрейны, триггеры).
Переименовываем таблицы: старая уходит в архив (something_old), новая становится боевой (something).
📋 Полный скрипт
🧩 Нюансы
Таблица доступна для чтения, почти всё время, за исключением времени переименования таблиц, но это быстрая операция.
Такой способ гарантирует правильный порядок колонок.
Старую таблицу (something_old) лучше оставить как «бэкап» на время. Если всё прошло успешно — можно дропнуть.
Конечно, такую задачу можно сделать через доп. поле и модификацию текущего поля.
Но таблица будет не доступна больше времени и залочить таблицу не получится.
И тут надо выбирать метод исходя из Ваших обстоятельств.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Если есть другое решение задачи. Поделитесь! 👇
#️⃣ #Cases #SQL #Oracle #PLSQL #PostgreSQL #PLpgSQL
Который по уши увяз
В кабине тьма, напарник третий час молчит
Хоть бы кричал, аж зло берёт
Назад пятьсот, вперёд пятьсот
А он зубами танец с саблями стучит.» — Владимир Высоцкий
🛠 Как аккуратно поменять тип колонки в 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-го узла с конца связного списка
Я продолжаю разбирать задачи и решаю их исключительно процедурными методами.
Представьте себе очередь в магазин.
Все стоят один за другим, и каждый знает только, кто следующий.
И вот приходит администратор и говорит: «Уберите шестого с конца!»
Что делает скрипт?
Сначала пересчитывает всю очередь — сколько людей стоит.
Определяет, кто находится перед тем самым шестым с конца.
Просит этого человека «пропустить следующего» и смотреть уже на того, кто идёт после удаляемого.
А сам шестой — тихо уходит из очереди.
Вот так список сокращается.
👍 Палец вверх — если цикл нужно продолжать.
👎 Палец вниз — если лучше вернуться к DB.
⚠️ Хотите проверить скрипты, но базы нет под рукой — онлайн-песочница вам в помощь.
💎 Поддержка канала⁉️
💬 Обратную связь туда 👇
#️⃣ #SQL #Oracle #PLSQL
Всё время тянет в магазин
А там — друзья, я ж, Зин
Не пью один!» - Владимир Высоцкий
📌 Разбор задачи: Удаление 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
📌 Серия «Оптимизация 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
Они стояли молча в ряд
Они стояли молча в ряд
Их было восемь.
Со мною — нож, решил я: что ж
Меня так просто не возьмёшь.
Держитесь, гады! Держитесь, гады!» — Владимир Высоцкий
✨ Сегодня суббота — задачи разбирать будем позже.
А пока продолжаем рубрику «Полезные ресурсы».
🖥️ Тема выпуска: Телеграм-каналы и чаты
Поскольку я блогер, то волей-неволей приходится изучать «конкурирующие фирмы».
Каналы, как и люди, — все разные:
кто-то с отличными постами, но навязчивой рекламой;
кто-то — с классным оформлением, но скучным контентом.
📢 Уважаемые читатели!
Делюсь подборкой профильных каналов на русском языке,
а также списком чатов, которые я лично не читаю,
поэтому оценить их качество не берусь — чаты просто не моё 🙂
🔧 Каналы
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
📌 Эта модель подходит как для наката изменений, так и для отката.
Таблица фиксирует все текущие ошибки и позволяет легко отслеживать изменения.
🔹 Создание таблицы ошибок
🔹 Параллельная перекомпиляция объектов
После наката/отката рекомендуется использовать UTL_RECOMP для перекомпиляции всех объектов в параллели, чтобы база была в рабочем состоянии:
Вот ссылка на официальную документацию UTL_RECOMP.
🔹 Итог
Всегда фиксируйте текущее состояние базы перед накатом.
Используйте таблицу ошибок для логирования ошибок.
Имейте скрипт отката на случай непредвиденных проблем.
После наката/отката запускайте параллельную перекомпиляцию объектов, чтобы минимизировать INVALID.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — интересно!
👎 Палец вниз — мне это не нужно, и потому скучно!
Интересна Ваша обратная связь - пишите тут 👇.
#️⃣ #Cases #SQL #Oracle #PLSQL
Я прошептал на ухо жениху -
И жениха, как будто ветром сдуло,-
Невеста, вон, рыдает наверху.» — Владимир Высоцкий
⚡ Накат и откат изменений в 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
👍9❤1👎1
🚀 Хотите прокачать SQL, но базы под рукой нет?
У моего коллеги Славы Рожнева — автора курса SQL в Яндексе — есть два ресурса:
🔹 sqltest.online — готовые задачи и тесты для практики.
🔹 sqlize.online — онлайн-песочница для запросов прямо в браузере.
📊 Отличный вариант, чтобы тренироваться в любом месте и в любое время!
У моего коллеги Славы Рожнева — автора курса SQL в Яндексе — есть два ресурса:
🔹 sqltest.online — готовые задачи и тесты для практики.
🔹 sqlize.online — онлайн-песочница для запросов прямо в браузере.
📊 Отличный вариант, чтобы тренироваться в любом месте и в любое время!
👍5🔥1
🎵 «Один говорил: "Нам свобода - награда:
Мы поезд, куда надо ведем".
Другой говорил: "Задаваться не надо.
Как сядем в него, так и сойдем".» — Андрей Макаревич
📌 Разбор задачи: Вычисление n-го числа Фибоначчи через динамическое программирование
Представьте себе, что вы строите лестницу чисел.
Каждый новый шаг — это сумма двух предыдущих.
И вот приходит администратор и говорит: «Посчитайте десятый Фибоначчи!»
Что делает скрипт?
1. Создаёт «лестницу» чисел (массив).
2. Заполняет первые два шага единицами.
3. Каждый следующий шаг — это сумма двух предыдущих.
4. В конце вы получаете нужное число и всю последовательность целиком.
💡 Как работает:
Первые два числа всегда 1.
Каждый новый элемент — это сумма двух предыдущих.
В итоге получаем как конкретное число, так и всю последовательность.
👍 Палец вверх — есть желание изучать алгоритмы.
👎 Палец вниз — хватит.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Что скажет "купечество"?
#️⃣ #SQL #Oracle #PLSQL
Мы поезд, куда надо ведем".
Другой говорил: "Задаваться не надо.
Как сядем в него, так и сойдем".» — Андрей Макаревич
📌 Разбор задачи: Вычисление 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
Продолжим или закончим 5 часть "Мерлезонского балета" - решение задач методами процедурного языка. Я выбрал очередные 5 задач из 5 тем: работа с массивами, работа со строками, работа с матрицами, структуры данных, динамическое программирование.
Anonymous Poll
11%
Сжатие строки: Выполните базовое сжатие строки, используя счётчик повторяющихся символов.
4%
Максимальная разница: Найдите максимальный абсолютный модуль разности между 2 соседними элементами в
7%
Сапёр: В матрице сапёра, где -1 - бомба, а 0 — пустая, вычислите кол-во бомб, смежных с каждой.
7%
Проверка на BST: Проверьте, является ли бинарное дерево деревом поиска (Binary Search Tree).
4%
Разбиение на слова: Дана строка и словарь слов. Разбейте строку на слова из словаря.
14%
Я разработчик DB. Всё остальное лишнее.
54%
Мне интересно всё.
👍3
Опрос — где вы определяете константы в PL/SQL-проектах?
Anonymous Poll
61%
Определяю константы в тех же спецификациях пакетов, что и методы.
18%
Нет. Держу константы в отдельном пакете.
21%
Что это — «константы в пакете»? Хочу пояснения.
🎵 «А я иду, шагаю по Москве,
И я ещё пройти смогу —
Солёный Тихий океан
И тундру, и тайгу…» — Сергей Никитин
📌 Разбор задачи: Разделение строки на слова
Иногда жизнь — как длинная дорога,
и каждое слово в строке — это шаг.
Одни шаги короткие, другие — длинные,
а между ними бывают остановки — пробелы.
🧠 Что делает скрипт?
1. Берёт исходную строку с кучей пробелов: ' 1 123 333 4567 '.
2. Проходит по ней символ за символом, как по дороге.
3. Когда встречает пробел — понимает: «О! шаг закончился».
4. Запоминает всё, что было между пробелами, — это и есть слово.
5. В конце выводит, сколько «шагов» пройдено и какие именно.
💡 Как работает:
Код идёт по строке, как путешественник по маршруту.
Каждый пробел — это остановка,
а всё между остановками — очередное слово.
Из ' 1 123 333 4567 ' получается маршрут:
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — если есть контакт
👎 Палец вниз — если это "сало" достало.
💬 Пишите, и Ваше слово будет прочитано! 👇.
#️⃣ #SQL #Oracle #PLSQL
И я ещё пройти смогу —
Солёный Тихий океан
И тундру, и тайгу…» — Сергей Никитин
📌 Разбор задачи: Разделение строки на слова
Иногда жизнь — как длинная дорога,
и каждое слово в строке — это шаг.
Одни шаги короткие, другие — длинные,
а между ними бывают остановки — пробелы.
🧠 Что делает скрипт?
1. Берёт исходную строку с кучей пробелов: ' 1 123 333 4567 '.
2. Проходит по ней символ за символом, как по дороге.
3. Когда встречает пробел — понимает: «О! шаг закончился».
4. Запоминает всё, что было между пробелами, — это и есть слово.
5. В конце выводит, сколько «шагов» пройдено и какие именно.
DECLARE
str VARCHAR2 (4000) := ' 1 123 333 4567 ';
TYPE str_tab IS TABLE OF VARCHAR2 (4000);
strs str_tab := str_tab ();
indexStartWord INTEGER := 0;
BEGIN
FOR index# IN 1 .. LENGTH (str)
LOOP
IF SUBSTR (str, index#, 1) = ' ' AND indexStartWord > 0
THEN
--DBMS_OUTPUT.put_line ('HERE');
strs.EXTEND;
strs (strs.COUNT) :=
SUBSTR (str, indexStartWord, index# - indexStartWord);
indexStartWord := 0;
END IF;
IF SUBSTR (str, index#, 1) != ' '
AND SUBSTR (str, index# - 1, 1) = ' '
THEN
indexStartWord := index#;
END IF;
--DBMS_OUTPUT.put_line (TO_CHAR (indexStartWord));
END LOOP;
-- print result
DBMS_OUTPUT.put_line (' ');
DBMS_OUTPUT.put_line ('word amount = ' || TO_CHAR (strs.COUNT));
FOR i IN 1 .. strs.COUNT
LOOP
DBMS_OUTPUT.put_line (strs (i));
END LOOP;
strs.delete;
END;
💡 Как работает:
Код идёт по строке, как путешественник по маршруту.
Каждый пробел — это остановка,
а всё между остановками — очередное слово.
Из ' 1 123 333 4567 ' получается маршрут:
1
123
333
4567
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — если есть контакт
👎 Палец вниз — если это "сало" достало.
💬 Пишите, и Ваше слово будет прочитано! 👇.
#️⃣ #SQL #Oracle #PLSQL
👍9❤1👎1
🎵 «А не спеть ли мне песню о любви
А не выдумать ли новый жанр
Попопсовей мотив и стихи
И всю жизнь получать гонорар» — Чиж
📚 Серия «Оптимизация SQL-запросов». Планы запросов (одна таблица): часть 1
Продолжим аналогию с библиотекой — ведь она интуитивно понятна.
Я создал таблицу BOOKS, спроектированную весьма плохо — и это намеренно.
Зачем? Чтобы показать, как работает оптимизатор и какие бывают планы выполнения запросов при обращении к одной таблице.
📖 Таблица денормализована, индексов слишком много, констрейнов нет — всё специально.
Цель — рассмотреть, как Oracle строит планы на одной таблице.
🔍 Что у нас есть
Мы — библиотекарь (оптимизатор).
Перед нами хранилище из миллиарда книг 📦
Созданы индексы:
Смотрите, в ORACLE много различных индексов и все они так или иначе применяются, но база это B-tree индексы (B - это не значит бинарный, а значит сбалансированный).
Абсолютное большинство индексов, которые Вы будете встречать, это нормальные B-tree индексы.
Экзотика полезна, но её надо изучать в контексте.
Заполним таблицу тестовыми данными:
#️⃣ #SQLOptimization #SQL #Oracle
А не выдумать ли новый жанр
Попопсовей мотив и стихи
И всю жизнь получать гонорар» — Чиж
📚 Серия «Оптимизация SQL-запросов». Планы запросов (одна таблица): часть 1
Продолжим аналогию с библиотекой — ведь она интуитивно понятна.
Я создал таблицу BOOKS, спроектированную весьма плохо — и это намеренно.
Зачем? Чтобы показать, как работает оптимизатор и какие бывают планы выполнения запросов при обращении к одной таблице.
CREATE TABLE books (
inventory_number NUMBER NOT NULL,
book_name VARCHAR2(500) NOT NULL,
language VARCHAR2(100) NOT NULL,
book_genre VARCHAR2(100) NOT NULL,
author_fullname VARCHAR2(100),
author_firstname VARCHAR2(100),
author_lastname VARCHAR2(100),
author_birthday DATE,
status_book VARCHAR2(50),
book_text CLOB NOT NULL
);
📖 Таблица денормализована, индексов слишком много, констрейнов нет — всё специально.
Цель — рассмотреть, как Oracle строит планы на одной таблице.
🔍 Что у нас есть
Мы — библиотекарь (оптимизатор).
Перед нами хранилище из миллиарда книг 📦
Созданы индексы:
CREATE UNIQUE INDEX books_u01 ON books (inventory_number); -- уникальный номер книги
CREATE INDEX books_i01 ON books (book_name); -- по названию
CREATE INDEX books_i02 ON books (language); -- по языку
CREATE INDEX books_i03 ON books (book_genre); -- по жанру
CREATE INDEX books_i04 ON books (book_name, book_genre, language); -- составной
CREATE INDEX books_i05 ON books (author_fullname); -- по ФИО автора
CREATE INDEX books_i06 ON books (author_lastname, author_firstname); -- по фамилии и имени
CREATE INDEX books_i07 ON books (author_birthday); -- по дате рождения
CREATE INDEX books_i08 ON books (status_book); -- по статусу (Available, Checked Out, Reserved, Repaired)
Смотрите, в ORACLE много различных индексов и все они так или иначе применяются, но база это B-tree индексы (B - это не значит бинарный, а значит сбалансированный).
Абсолютное большинство индексов, которые Вы будете встречать, это нормальные B-tree индексы.
Экзотика полезна, но её надо изучать в контексте.
Заполним таблицу тестовыми данными:
INSERT ALL
INTO books (inventory_number, book_name, language, book_genre, author_fullname, author_firstname, author_lastname, author_birthday, status_book, book_text)
VALUES (1, 'War and Peace', 'English', 'Historical Novel', 'Leo Tolstoy', 'Leo', 'Tolstoy',
TO_DATE('09-09-1828', 'DD-MM-YYYY'), 'Available',
'Long philosophical novel about war and human destiny.')
INTO books VALUES (2, 'Crime and Punishment', 'English', 'Psychological Fiction',
'Fyodor Dostoevsky', 'Fyodor', 'Dostoevsky',
TO_DATE('11-11-1821', 'DD-MM-YYYY'), 'Checked Out',
'Story of guilt, morality, and redemption.')
INTO books VALUES (3, 'Pride and Prejudice', 'English', 'Romance',
'Jane Austen', 'Jane', 'Austen',
TO_DATE('16-12-1775', 'DD-MM-YYYY'), 'Available',
'A classic story about manners, marriage, and social class.')
INTO books VALUES (4, 'Les Misérables', 'French', 'Historical Novel',
'Victor Hugo', 'Victor', 'Hugo',
TO_DATE('26-02-1802', 'DD-MM-YYYY'), 'Reserved',
'Epic tale of injustice, revolution, and redemption.')
INTO books VALUES (5, 'Faust', 'German', 'Tragedy',
'Johann Wolfgang von Goethe', 'Johann', 'Goethe',
TO_DATE('28-08-1749', 'DD-MM-YYYY'), 'Available',
'A scholar makes a pact with the devil in search of knowledge.')
INTO books VALUES (6, 'Don Quixote', 'Spanish', 'Adventure',
'Miguel de Cervantes', 'Miguel', 'Cervantes',
TO_DATE('29-09-1547', 'DD-MM-YYYY'), 'Checked Out',
'A nobleman loses his sanity and becomes a wandering knight.')
SELECT * FROM dual;
#️⃣ #SQLOptimization #SQL #Oracle
📚 Планы запросов (одна таблица): часть 2
1️⃣ Полное чтение таблицы
📄 План: TABLE ACCESS FULL — читаем всю таблицу от начала до конца.
Oracle оценивает объём, строки и стоимость выполнения (Cost = 3).
💡 Если нет статистики — оптимизатор делает dynamic sampling.
Чтобы помочь ему, собираем статистику:
После этого оценки становятся точнее - 6 записей против 1.
2️⃣ Покрытые запросы (INDEX FULL SCAN)
Нужно получить все инвентарные номера:
SELECT inventory_number FROM books;
📈 Oracle использует индекс BOOKS_U01, не трогая таблицу.
Такой запрос называют покрытым — все нужные данные уже есть в индексе.
⚠️ Поэтому избегайте SELECT * — всегда указывайте только нужные колонки!
3️⃣ Самый быстрый запрос 🚀
🪶 ROWID хранит физическое местоположение строки.
Oracle идёт прямо к нужному блоку, минуя индексы.
#️⃣ #SQLOptimization #SQL #Oracle
1️⃣ Полное чтение таблицы
SELECT * FROM books;
Plan Hash Value : 2688610195
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2563 | 3 | 00:00:01 |
| 1 | TABLE ACCESS FULL | BOOKS | 1 | 2563 | 3 | 00:00:01 |
---------------------------------------------------------------------
Notes
-----
- Dynamic sampling used for this statement ( level = 2 )
📄 План: TABLE ACCESS FULL — читаем всю таблицу от начала до конца.
Oracle оценивает объём, строки и стоимость выполнения (Cost = 3).
💡 Если нет статистики — оптимизатор делает dynamic sampling.
Чтобы помочь ему, собираем статистику:
BEGIN
DBMS_STATS.GATHER_TABLE_STATS (USER, 'BOOKS');
END;
После этого оценки становятся точнее - 6 записей против 1.
Plan Hash Value : 2688610195
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 2928 | 3 | 00:00:01 |
| 1 | TABLE ACCESS FULL | BOOKS | 6 | 2928 | 3 | 00:00:01 |
---------------------------------------------------------------------
2️⃣ Покрытые запросы (INDEX FULL SCAN)
Нужно получить все инвентарные номера:
SELECT inventory_number FROM books;
📈 Oracle использует индекс BOOKS_U01, не трогая таблицу.
Такой запрос называют покрытым — все нужные данные уже есть в индексе.
⚠️ Поэтому избегайте SELECT * — всегда указывайте только нужные колонки!
3️⃣ Самый быстрый запрос 🚀
SELECT * FROM books WHERE rowid = ...;
Plan Hash Value : 2667597667
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 244 | 1 | 00:00:01 |
| 1 | TABLE ACCESS BY USER ROWID | BOOKS | 1 | 244 | 1 | 00:00:01 |
------------------------------------------------------------------------------
🪶 ROWID хранит физическое местоположение строки.
Oracle идёт прямо к нужному блоку, минуя индексы.
#️⃣ #SQLOptimization #SQL #Oracle
❤2
📚 Планы запросов (одна таблица): часть 3
4️⃣ Доступ по уникальному индексу
📘 План показывает:
INDEX UNIQUE SCAN по BOOKS_U01
затем TABLE ACCESS BY INDEX ROWID
То есть сначала Oracle ищет ROWID в индексе, потом берёт саму книгу.
5️⃣ Доступ по неуникальному индексу
🧭 План: INDEX RANGE SCAN
Даже если значение одно, Oracle предполагает, что книг с таким названием может быть несколько.
💬 Разница между INDEX UNIQUE SCAN и INDEX RANGE SCAN колоссальная по скорости.
Если можете писать точные фильтры по уникальному индексу — делайте это!
Пример:
Результат этих запросов идентичен, но план запросов по скорости отличается "как небо и земля".
В одном случае будет INDEX UNIQUE SCAN, в другом INDEX RANGE SCAN.
Если используется INDEX UNIQUE SCAN — это значительно быстрее, чем диапазонное условие.
6️⃣ Доступ по неполному индексу
SELECT * FROM books
WHERE language = 'Russian' AND book_genre = 'Novel';
📊 План: INDEX SKIP SCAN
Oracle использует составной индекс (book_name, book_genre, language),
пропуская первое поле (book_name).
⚠️ Если видите в плане INDEX SKIP SCAN, это сигнал проверить:
Правильные ли созданы индексы на таблице?
Не в том ли порядке созданы поля составного индекса?
7⃣ Напоследок: есть ещё INDEX FAST FULL SCAN,
но о нём как-нибудь в другой раз. Это уже разбор не для новичков.
#️⃣ #SQLOptimization #SQL #Oracle
4️⃣ Доступ по уникальному индексу
SELECT * FROM books WHERE inventory_number = 1;
Plan Hash Value : 6300684
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 244 | 1 | 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | BOOKS | 1 | 244 | 1 | 00:00:01 |
| * 2 | INDEX UNIQUE SCAN | BOOKS_U01 | 1 | | 0 | 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 2 - access("INVENTORY_NUMBER"=1)
📘 План показывает:
INDEX UNIQUE SCAN по BOOKS_U01
затем TABLE ACCESS BY INDEX ROWID
То есть сначала Oracle ищет ROWID в индексе, потом берёт саму книгу.
5️⃣ Доступ по неуникальному индексу
SELECT * FROM books WHERE book_name = 'War and Peace';
Plan Hash Value : 480843298
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 244 | 2 | 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED | BOOKS | 1 | 244 | 2 | 00:00:01 |
| * 2 | INDEX RANGE SCAN | BOOKS_I01 | 1 | | 1 | 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 2 - access("BOOK_NAME"='War and Peace')
🧭 План: INDEX RANGE SCAN
Даже если значение одно, Oracle предполагает, что книг с таким названием может быть несколько.
💬 Разница между INDEX UNIQUE SCAN и INDEX RANGE SCAN колоссальная по скорости.
Если можете писать точные фильтры по уникальному индексу — делайте это!
Пример:
SELECT * FROM books
WHERE inventory_number IN (1, 2, 3);
SELECT * FROM books
WHERE inventory_number >= 1 AND inventory_number <= 3;
Результат этих запросов идентичен, но план запросов по скорости отличается "как небо и земля".
В одном случае будет INDEX UNIQUE SCAN, в другом INDEX RANGE SCAN.
Если используется INDEX UNIQUE SCAN — это значительно быстрее, чем диапазонное условие.
6️⃣ Доступ по неполному индексу
SELECT * FROM books
WHERE language = 'Russian' AND book_genre = 'Novel';
Plan Hash Value : 1417276700
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 244 | 2 | 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED | BOOKS | 1 | 244 | 2 | 00:00:01 |
| * 2 | INDEX SKIP SCAN | BOOKS_I04 | 1 | | 1 | 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 2 - access("BOOK_GENRE"='Novel' AND "LANGUAGE"='Russian')
* 2 - filter("LANGUAGE"='Russian' AND "BOOK_GENRE"='Novel')
📊 План: INDEX SKIP SCAN
Oracle использует составной индекс (book_name, book_genre, language),
пропуская первое поле (book_name).
⚠️ Если видите в плане INDEX SKIP SCAN, это сигнал проверить:
Правильные ли созданы индексы на таблице?
Не в том ли порядке созданы поля составного индекса?
7⃣ Напоследок: есть ещё INDEX FAST FULL SCAN,
но о нём как-нибудь в другой раз. Это уже разбор не для новичков.
#️⃣ #SQLOptimization #SQL #Oracle
👍2