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

🔍 Решение задачи поиска нужного маршрута среди комнат

Задача сводится к поиску пути в графе.
В Oracle для этого удобно использовать иерархический запрос (CONNECT BY).

 Трюк в том, что нужно:

Искать все возможные пути от стартовой комнаты 🚪
Отсекать зацикливания 🔄
Оставить только минимальную длину 📏

WITH paths AS (
SELECT room_out,
room_in,
LEVEL AS room_counter,
SYS_CONNECT_BY_PATH(room_out, ' => ') || ' => ' || room_in AS full_path
FROM room_links
START WITH room_out = 'room_2'
CONNECT BY NOCYCLE PRIOR room_in = room_out
AND room_out != 'room_9'
)
SELECT *
FROM (
SELECT room_out,
room_in,
full_path,
RANK() OVER (ORDER BY room_counter) AS room_counter_rank
FROM paths
WHERE room_in = 'room_9'
) p
WHERE p.room_counter_rank = 1;

 Что делает запрос

START WITH room_out = 'room_2' — начинаем из исходной комнаты 🏠
CONNECT BY NOCYCLE PRIOR room_in = room_out — двигаемся по дверям, не зацикливаясь 🔄
room_out != 'room_9' — если дошли до комнаты room_9, то дальше не идём 🚫
SYS_CONNECT_BY_PATH — собирает весь путь в строку 🛤️

В конце выбираем маршрут до нужной комнаты с минимальной длиной ✂️

📊 Такой подход работает и на сгенерированных тестовых данных — вы получите кратчайшие маршруты между любыми двумя комнатами.

👉 А ещё задачу можно решить через WITH RECURSIVE или даже с помощью MODEL 💡

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

💬 Есть что добавить? Не сдерживай себя — пиши 👇
#️⃣ #RealInterviewTasks #SQL #Oracle #PostgreSQL #PLSQL #PLpgSQL
👍21👎1
🎵 «Нагружать все больше нас
Стали почему-то,
Нынче в школе первый класс -
Вроде института.
Нам учитель задает
С иксами задачи,
Кандидат наук и тот -
Над задачей плачет.» — Алла Пугачева

Этим постом я начинаю (и, возможно, заканчиваю) серию «Оптимизация SQL-запросов».

📌 Сегодня вводная для новичков.

📊 Небольшой опрос среди 46 читателей канала показал:
11% — начинающие,
2% — испытывают трудности со сложными запросами.

Это вроде бы мало, чтобы разворачивать целый курс для новичков.
Но и много, чтобы игнорировать и не дать им «маршрут» обучения.

💡 Мои советы

0) SQL — навык, а не «родной язык»
Никто не рождается с пониманием SQL. Все проходят одинаковые стадии — от 🤯 недоумения до 🙌 принятия.
Главный путь — практика. В какой-то момент SQL станет для вас логичным и понятным. Дальше останется только накапливать трюки (и их не так уж много).

1) Настройте локальную среду ⚙️
Создайте место, где можно спокойно экспериментировать, создавать таблицы и объекты, не боясь «сломать прод».

Oracle и PostgreSQL отлично подходят для локальной установки.
Даже если что-то пошло не так — всегда можно всё переустановить.

2) Учитесь по книгам 📚
Для тех, кто не пишет SQL ежедневно, главная трудность — синтаксис. Он специфичный, и к нему нужно привыкнуть.
Но уже через 2–3 недели он станет естественным.

👉 Для старта советую:
«Секреты ORACLE SQL
» — но вообще любая понятная вам книга подойдёт.

3) Разбирайте сложные запросы по шагам 🪜
Многоуровневые вложенные SELECT’ы пугают новичков.
Для анализа используйте конструкцию WITH.

Как обычно пишут:
SELECT ...
FROM (
SELECT ...
FROM (
SELECT ...
FROM a
WHERE ...
) aa
WHERE ...
) aaa
WHERE ...


А вот так понятнее:
WITH a_add AS (
SELECT ...
FROM a
WHERE ...
),
aa_add AS (
SELECT ...
FROM a_add
WHERE ...
),
aaa_add AS (
SELECT ...
FROM aa_add
WHERE ...
)
SELECT * FROM aaa_add;

Так запрос становится прозрачным и разбирается по шагам.
(Но, конечно, не стоит в таком стиле писать всё подряд 😉).

4) Думайте множествами 🔢
Главный совет «на миллион». Даже опытные разработчики часто пишут запросы «по синтаксису». Но SQL проще понимать, если мыслить категориями множеств:

Таблицы = множества строк.
Строки = элементы множества.
Поля = атрибуты элементов.

👉 JOIN — это сопоставление элементов двух множеств по условию.
👉 WHERE — фильтр, выбирающий нужные элементы.
👉 GROUP BY — разбиение множества на подмножества.

Пример: есть таблица «Люди». Нужно соединить их в пары.

Берём множество людей,
накладываем фильтр для первой группы,
накладываем фильтр для второй группы,
соединяем по условию (например, одинаковый возраст, рост или период рождения).

Если приучить себя мыслить множествами, SQL станет в разы понятнее.

⚡️ И главное: лучше решить 1 сложную задачу, чем 1000 лёгких.
Всегда поднимайте планку — только так прокачка идёт быстрее.

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

👍 Палец вверх — продолжаем и перейдем уже на уровень выше.
👎 Палец вниз — "Вода! Вода! Кругом вода!"

👨‍💻 Эти советы я бы дал самому себе из будущего.
А Вы, уважаемые мидлы, синьоры, эксперты — что бы посоветовали самим себе в прошлом? 👇
#️⃣ #SQLOptimization
👍13👎1
🎵 «Хоть у неё ни черта не осталось!
У неё в кошельке три рубля,
Моя бабушка курит трубку,
Трубку курит бабушка моя.» — Гарик Сукачев

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

🖥️ Тема: IDE — главный инструмент DB-разработчика

IDE для разработчика — как любимая машина 🚗.
Поменять или обменять сложно: привыкаешь к дизайну, к деталям, к особенностям. Да, у других «машин» тоже есть крутые примочки, но своя всё равно ближе.

🔧 Лично я использую:

PL/SQL Developer — основное IDE.
Toad for Oracle («Жаба») — для анализа, форматирования и мониторинга.
Oracle SQL Developer — ради одной особенной фичи.

🔍 Реaltime SQL Monitor в Oracle SQL Developer

Это инструмент для мониторинга «тяжёлых» SQL-запросов и PL/SQL-блоков в реальном времени.
По сути, это удобная визуализация на базе DBMS_SQLTUNE.REPORT_SQL_MONITOR и данных V$SQL_MONITOR / V$SQL_PLAN_MONITOR.

💡 Чем полезен:

Показывает, на каком шаге плана и в каком объёме Oracle расходует ресурсы.
Позволяет увидеть узкие места в плане выполнения.
Для новичков — отличная визуализация, сразу становится понятней, как именно исполняется запрос.
Для профи — быстрый первый шаг в диагностике «тяжёлых» запросов.
В 90% случаев этого достаточно, чтобы понять, что именно не так с планом.

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

👨‍💻 А какими IDE пользуетесь Вы и за какие фичи их цените больше всего? Делитесь в комментах 👇
#️⃣ #SQLOptimization #Tools #Oracle
👍4👎1
🎵 «Ах! Крутится, вертится шар голубой,
Крутится, вертится над головой,
Крутится, вертится, хочет упасть.
Кавалер барышню хочет украсть.» — приписывается Ф. Садовскому

📌 Разбор задачи: Поворот матрицы 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%
Инвалиды!? А что это?