Продолжим или закончим 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
📚 Планы запросов (одна таблица): часть 4.
📚 Сейчас же несколько советов и логику рассуждения первичной оптимизации.
🔍 Пример 1. Поиск по шаблону
Если у Вас нет нужного конкретного функционального индекса и Вам надо искать данные по шаблону в строке, используете LIKE.
🔧 Пример 2. Выборка по нескольким условиям
Часто ли книги отправляют на реставрацию? Ну, не часто! Из миллиарда книг, может быть 50 или 500, но не больше.
Если же Вам требуется найти все книги, которые на русском языке и отправлены на реставрацию.
Оптимизатор может использовать несколько планов.
1) Выбрать все книги на русском, а потом среди них искать, тех кто на реставрации.
2) Выбрать все книги на реставрации, а потом уже на русском.
Очевидно, что 2 вариант проще. Но бывают ситуации, что оптимизатор принимает неочевидное решение.
Смотрите, здесь оптимизатор ошибся! Он доступ по индексу использует язык access("B"."LANGUAGE"='Russian'), а потом уже фильтрует по статусу filter("B"."STATUS_BOOK"='Repaired')
Задача разработчика это прочитать и исправить. Например, явно указать какой индекс использовать для поиска нужных записей.
Для этого можно использовать хинт INDEX.
Теперь мы получим, то что и надо - индекс access("B"."STATUS_BOOK"='Repaired') и фильтр filter("B"."LANGUAGE"='Russian').
Оптимизатор сделал оценку плана и ошибся, в этом случае, оценка оптимизатора невалидна. Её можно игнорировать.
🎯 Вывод:
Проверяйте, как именно Oracle строит план.
Если оптимизатор ошибается — докажите это тестом, замером времени и статистикой.
Оптимизация — это не догадки, а проверка гипотез 💪.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — если мои посты Вам интересны
👎 Палец вниз — если вяло или пошло.
💬 Пишите - для меня весьма важна обратная связь! 👇.
#️⃣ #SQLOptimization #SQL #Oracle
📚 Сейчас же несколько советов и логику рассуждения первичной оптимизации.
🔍 Пример 1. Поиск по шаблону
Если у Вас нет нужного конкретного функционального индекса и Вам надо искать данные по шаблону в строке, используете LIKE.
SELECT * FROM books
WHERE author_lastname LIKE '%Tol';
🔧 Пример 2. Выборка по нескольким условиям
Часто ли книги отправляют на реставрацию? Ну, не часто! Из миллиарда книг, может быть 50 или 500, но не больше.
Если же Вам требуется найти все книги, которые на русском языке и отправлены на реставрацию.
Оптимизатор может использовать несколько планов.
1) Выбрать все книги на русском, а потом среди них искать, тех кто на реставрации.
2) Выбрать все книги на реставрации, а потом уже на русском.
Очевидно, что 2 вариант проще. Но бывают ситуации, что оптимизатор принимает неочевидное решение.
SELECT * FROM books b WHERE b.language = 'Russian' AND b.status_book = 'Repaired'
Plan Hash Value : 4070381338
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2563 | 1 | 00:00:01 |
| * 1 | TABLE ACCESS BY INDEX ROWID BATCHED | BOOKS | 1 | 2563 | 1 | 00:00:01 |
| * 2 | INDEX RANGE SCAN | BOOKS_I02 | 2 | | 1 | 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter("B"."STATUS_BOOK"='Repaired')
* 2 - access("B"."LANGUAGE"='Russian')
Смотрите, здесь оптимизатор ошибся! Он доступ по индексу использует язык access("B"."LANGUAGE"='Russian'), а потом уже фильтрует по статусу filter("B"."STATUS_BOOK"='Repaired')
Задача разработчика это прочитать и исправить. Например, явно указать какой индекс использовать для поиска нужных записей.
Для этого можно использовать хинт INDEX.
SELECT /*+ INDEX (b BOOKS_I08)*/ * FROM books b WHERE b.language = 'Russian' AND b.status_book = 'Repaired'
Теперь мы получим, то что и надо - индекс access("B"."STATUS_BOOK"='Repaired') и фильтр filter("B"."LANGUAGE"='Russian').
Оптимизатор сделал оценку плана и ошибся, в этом случае, оценка оптимизатора невалидна. Её можно игнорировать.
Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter("B"."LANGUAGE"='Russian')
* 2 - access("B"."STATUS_BOOK"='Repaired')
🎯 Вывод:
Проверяйте, как именно Oracle строит план.
Если оптимизатор ошибается — докажите это тестом, замером времени и статистикой.
Оптимизация — это не догадки, а проверка гипотез 💪.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — если мои посты Вам интересны
👎 Палец вниз — если вяло или пошло.
💬 Пишите - для меня весьма важна обратная связь! 👇.
#️⃣ #SQLOptimization #SQL #Oracle
👍8🔥2👎1
DB developers channel
Опрос — где вы определяете константы в PL/SQL-проектах?
🎵 «"Правда всегда одна"
Это сказал фараон
Он был очень умен
И за это его называли
Тутанхамон» — Наутилус Помпилиус
🚀 Друзья! Опрос «Где вы храните константы?» показал: 66% определяют их прямо в пакетах с методами.
⚠️ Это неточность!
Проблема в ошибке:
ORA-04068: existing state of packages has been discarded
Она появляется, когда пакет с состоянием (stateful package) был пересоздан, а сессия держала старую версию.
🔹 Пример “неправильного” пакета
💥 Если изменить константы в другой сессии — следующая попытка использовать пакет вызовет ORA-04068.
✅ Точный подход
Выносите константы в отдельный пакет:
Теперь пакет можно компилировать сколько угодно — ошибок ORA-04068 больше нет, скрипт работает стабильно! ✅
💡 Вывод: Храните константы отдельно, и ваши stateful packages будут всегда безопасны и стабильны.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — если информация полезна.
👎 Палец вниз — если неинтересно.
💬 Пишите Ваши слова туда! 👇.
#️⃣ #CodeArchitecture #SQL #Oracle #PLSQL
Это сказал фараон
Он был очень умен
И за это его называли
Тутанхамон» — Наутилус Помпилиус
🚀 Друзья! Опрос «Где вы храните константы?» показал: 66% определяют их прямо в пакетах с методами.
⚠️ Это неточность!
Проблема в ошибке:
ORA-04068: existing state of packages has been discarded
Она появляется, когда пакет с состоянием (stateful package) был пересоздан, а сессия держала старую версию.
🔹 Пример “неправильного” пакета
CREATE OR REPLACE PACKAGE PKG_TEST IS
external_constant CONSTANT NUMBER := 0;
PROCEDURE show_info;
END PKG_TEST;
/
CREATE OR REPLACE PACKAGE BODY PKG_TEST IS
internal_constant CONSTANT NUMBER := 0;
PROCEDURE show_info IS
BEGIN
DBMS_OUTPUT.put_line('internal_constant = ' || internal_constant);
DBMS_OUTPUT.put_line('external_constant = ' || external_constant);
END;
END PKG_TEST;
/
💥 Если изменить константы в другой сессии — следующая попытка использовать пакет вызовет ORA-04068.
✅ Точный подход
Выносите константы в отдельный пакет:
CREATE OR REPLACE PACKAGE PKG_CONSTANTS IS
external_constant CONSTANT NUMBER := 0;
internal_constant CONSTANT NUMBER := 0;
END PKG_CONSTANTS;
/
CREATE OR REPLACE PACKAGE PKG_TEST_NEW IS
PROCEDURE show_info;
END PKG_TEST_NEW;
/
CREATE OR REPLACE PACKAGE BODY PKG_TEST_NEW IS
PROCEDURE show_info IS
BEGIN
DBMS_OUTPUT.put_line('internal = ' || PKG_CONSTANTS.internal_constant);
DBMS_OUTPUT.put_line('external = ' || PKG_CONSTANTS.external_constant);
END;
END PKG_TEST_NEW;
/
Теперь пакет можно компилировать сколько угодно — ошибок ORA-04068 больше нет, скрипт работает стабильно! ✅
BEGIN
PKG_TEST_NEW.show_info();
END;
💡 Вывод: Храните константы отдельно, и ваши stateful packages будут всегда безопасны и стабильны.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — если информация полезна.
👎 Палец вниз — если неинтересно.
💬 Пишите Ваши слова туда! 👇.
#️⃣ #CodeArchitecture #SQL #Oracle #PLSQL
👍3👎1
DB developers channel
Продолжим или закончим 5 часть "Мерлезонского балета" - решение задач методами процедурного языка. Я выбрал очередные 5 задач из 5 тем: работа с массивами, работа со строками, работа с матрицами, структуры данных, динамическое программирование.
🎵 «Ах, эти черные глаза
Меня любили.
Куда же скрылись вы теперь,
Кто близок вам другой?» — Петр Лещенко
📌 Разбор задачи: Сжатие строки
Цель выполнить задачу прибегая исключительно процедурными методами.
Возможно, используя регулярки или встроенные средства, можно получить алгоритм короче и проще,
но, по сути, они будут делать тоже самое.
💡 Идея проста:
Мы проходим по строке и считаем, сколько раз подряд встречается один и тот же символ.
Если символ один — просто записываем его.
Если повторяется — добавляем цифру с количеством.
📦 Исходная строка:
aaabbccccdaa
🔧 Сжатая версия:
a3b2c4da2
🪄 Что делает код:
Берёт строку и идёт по ней символ за символом.
Считает, сколько раз подряд встречается текущий символ.
Если повторов нет — просто добавляет символ.
Если есть — добавляет символ и число повторов.
На выходе получаем сжатую строку без потери информации.
🔎 Смысл задачи:
Это классический пример Run-Length Encoding (RLE) —
одного из самых простых и эффективных алгоритмов сжатия данных.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — алгоритмы наше всё.
👎 Палец вниз — это лишнее.
💬 Хотите прокомментировать - милости просим! 👇.
#️⃣ #Oracle #PLSQL
Меня любили.
Куда же скрылись вы теперь,
Кто близок вам другой?» — Петр Лещенко
📌 Разбор задачи: Сжатие строки
Цель выполнить задачу прибегая исключительно процедурными методами.
Возможно, используя регулярки или встроенные средства, можно получить алгоритм короче и проще,
но, по сути, они будут делать тоже самое.
💡 Идея проста:
Мы проходим по строке и считаем, сколько раз подряд встречается один и тот же символ.
Если символ один — просто записываем его.
Если повторяется — добавляем цифру с количеством.
📦 Исходная строка:
aaabbccccdaa
🔧 Сжатая версия:
a3b2c4da2
DECLARE
str VARCHAR2 (4000) := 'aaabbccccdaa';
newStr VARCHAR2 (4000);
symbolCount INTEGER;
index# INTEGER := 1;
BEGIN
WHILE index# <= LENGTH (str)
LOOP
symbolCount := 1;
WHILE index# + symbolCount <= LENGTH (str)
LOOP
IF SUBSTR (str, index#, 1) =
SUBSTR (str, index# + symbolCount, 1)
THEN
symbolCount := symbolCount + 1;
ELSE
EXIT;
END IF;
END LOOP;
IF symbolCount = 1
THEN
newStr := newStr || SUBSTR (str, index#, 1);
ELSE
newStr :=
newStr || SUBSTR (str, index#, 1) || TO_CHAR (symbolCount);
END IF;
index# := index# + symbolCount;
END LOOP;
DBMS_OUTPUT.put_line (newStr);
END;
🪄 Что делает код:
Берёт строку и идёт по ней символ за символом.
Считает, сколько раз подряд встречается текущий символ.
Если повторов нет — просто добавляет символ.
Если есть — добавляет символ и число повторов.
На выходе получаем сжатую строку без потери информации.
🔎 Смысл задачи:
Это классический пример Run-Length Encoding (RLE) —
одного из самых простых и эффективных алгоритмов сжатия данных.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — алгоритмы наше всё.
👎 Палец вниз — это лишнее.
💬 Хотите прокомментировать - милости просим! 👇.
#️⃣ #Oracle #PLSQL
👍5❤1👎1👀1
Задание Oracle (1).docx
25.2 KB
🎵 «Я люблю тебя до слёз
Каждый вздох, как в первый раз
Вместо лжи красивых фраз
Это облако из роз» — Александр Серов
🎯 Задача из собеседовании в ЦБ РФ в 2020г.
Это забытое время без ИИ, тогда задачи могли высылать по почте с ограниченным временем на решение.
В самых первых сообщениях на канале я добавил именно самые интересные из них.
Поскольку я еще не знал/не умел оформлять посты, то эти задачи были недостаточно раскрыты.
Считаю, что это крайне несправедливо к задачам.
Хочу их раскрыть подробнее.
Дело в том, что для их решения "стандартного" обучения недостаточно (слишком они специфичны).
Технологию их решения требуется знать - выдумать на ходу не получится.
И так, задача 1:
Есть таблицы по реквизитам.
История в таблицах T1 и T2 может начинаться с разных дат и является непрерывной
(без пропусков,
дата окончания записи = дата начала следующей записи минус один день или 31.12.9999 у последней записи в истории) .
📄 Пример данных (ID = 125)
🧠 Задача:
Написать запрос, который формирует сводную историю всех реквизитов из обоих таблиц для организации с id=125.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Есть что писать - пишите! 👇.
#️⃣ #RealInterviewTasks #SQL #Oracle #PLSQL #PostgreSQL
Каждый вздох, как в первый раз
Вместо лжи красивых фраз
Это облако из роз» — Александр Серов
🎯 Задача из собеседовании в ЦБ РФ в 2020г.
Это забытое время без ИИ, тогда задачи могли высылать по почте с ограниченным временем на решение.
В самых первых сообщениях на канале я добавил именно самые интересные из них.
Поскольку я еще не знал/не умел оформлять посты, то эти задачи были недостаточно раскрыты.
Считаю, что это крайне несправедливо к задачам.
Хочу их раскрыть подробнее.
Дело в том, что для их решения "стандартного" обучения недостаточно (слишком они специфичны).
Технологию их решения требуется знать - выдумать на ходу не получится.
И так, задача 1:
Есть таблицы по реквизитам.
-- Основные реквизиты организации
CREATE TABLE T1 (
ID NUMBER, -- Уникальный идентификатор
OGRN VARCHAR2(20), -- ОГРН
INN VARCHAR2(20), -- ИНН
NAME VARCHAR2(200), -- Наименование организации
DS DATE, -- Дата начала действия записи
DE DATE -- Дата окончания действия записи
);
-- Расширенные реквизиты организации
CREATE TABLE T2 (
ID NUMBER, -- Уникальный идентификатор
STATUS VARCHAR2(10), -- Статус организации
ADDRESS VARCHAR2(200), -- Адрес
EQ NUMBER, -- Уставной капитал
DS DATE, -- Дата начала действия записи
DE DATE -- Дата окончания действия записи
);
История в таблицах T1 и T2 может начинаться с разных дат и является непрерывной
(без пропусков,
дата окончания записи = дата начала следующей записи минус один день или 31.12.9999 у последней записи в истории) .
📄 Пример данных (ID = 125)
T1 — основные реквизиты
ID OGRN INN NAME DS DE
125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 01.01.17 30.06.18
125 1127847448520 7810880684 ООО "ЛЕНТЕХ-РЕЗЕРВ" 01.07.18 31.12.19
125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 01.01.20 31.12.9999
T2 — расширенные реквизиты
ID STATUS ADDRESS EQ DS DE
125 001 СПб, Шуваловский пр. 22 10000 05.01.17 10.09.18
125 101 СПб, пр. Просвещения 130 10000 11.09.18 20.04.19
125 001 СПб, Московский пр. 222 10000 21.04.19 31.12.9999
🧠 Задача:
Написать запрос, который формирует сводную историю всех реквизитов из обоих таблиц для организации с id=125.
ID OGRN INN NAME STATUS ADDRESS EQ DS DE
125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" - - 01.01.17 04.01.17
125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 001 СПб, Шуваловский пр. 22 10000 05.01.17 30.06.18
125 1127847448520 7810880684 ООО "ЛЕНТЕХ-РЕЗЕРВ" 001 СПб, Шуваловский пр. 22 10000 01.07.18 10.09.18
125 1127847448520 7810880684 ООО "ЛЕНТЕХ-РЕЗЕРВ" 101 СПб, пр. Просвещения 130 10000 11.09.18 20.04.19
125 1127847448520 7810880684 ООО "ЛЕНТЕХ-РЕЗЕРВ" 001 СПб, Московский пр. 222 10000 21.04.19 31.12.19
125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 001 СПб, Московский пр. 222 10000 01.01.20 31.12.9999
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Есть что писать - пишите! 👇.
#️⃣ #RealInterviewTasks #SQL #Oracle #PLSQL #PostgreSQL
👍3
Друзья! Так вышло, что моя личная база знания исчерпывается. У меня много идей по развитию канала, но я стою перед диллемой. Есть много объемных интересных тем, но их расскрытие занимает много времени, и тогда количество публикаций уменьшится. Читай комме
Anonymous Poll
15%
Понемногу, но каждый день.
74%
Лучше меньше постов, но лучше.
11%
Мне всё равно.
🎵 «Кто весел, тот смеётся,
Кто хочет, тот добьётся,
Кто ищет, тот всегда найдёт!» — из к/ф "Дети капитана Гранта"
Сегодня день опросов и принятия "решений".
Так само по себе сложилось, что образовались рубрики:
1️⃣ SQL Оптимизация
2️⃣ Решение задач процедурными методами
3️⃣ Полезные ресурсы
4️⃣ Задачи на собесах
5️⃣ Случаи из практики
6️⃣ Архитектура кода
Я ими дорожу. 💛
По-моему, весьма солидный набор.
У меня есть мысли создать рубрики:
"Критический разбор чужих авторов"
и/или, например, "Читаем книгу вместе" 📚,
легкие задачи на 5 минут.
Но, возможно, то, что интересно мне, не интересно Вам. 🤔
Подскажите, что Вам нравится/не нравится больше всего.
Какие темы/рубрики Вы бы хотели видеть на канале? 💬
Кто хочет, тот добьётся,
Кто ищет, тот всегда найдёт!» — из к/ф "Дети капитана Гранта"
Сегодня день опросов и принятия "решений".
Так само по себе сложилось, что образовались рубрики:
1️⃣ SQL Оптимизация
2️⃣ Решение задач процедурными методами
3️⃣ Полезные ресурсы
4️⃣ Задачи на собесах
5️⃣ Случаи из практики
6️⃣ Архитектура кода
Я ими дорожу. 💛
По-моему, весьма солидный набор.
У меня есть мысли создать рубрики:
"Критический разбор чужих авторов"
и/или, например, "Читаем книгу вместе" 📚,
легкие задачи на 5 минут.
Но, возможно, то, что интересно мне, не интересно Вам. 🤔
Подскажите, что Вам нравится/не нравится больше всего.
Какие темы/рубрики Вы бы хотели видеть на канале? 💬
DB developers channel
Задание Oracle (1).docx
script_create_insert.sql
3 KB
🎵 «Мы бандито, знаменито, мы стрелято пистолето, о йес,
Мы фиато разъезжанто целый день в кабриолето, о йес,
Постоянно пьем чинзано, постоянно сыто-пьяно, о йес,
Держим банко миллионо и плеванто на законо, о йес.» - м/ф «Приключения капитана Врунгеля»
📊 Разбор задачи с собеседования ЦБ РФ
🧩 Идея задачи
В таблицах T1 и T2 хранится история изменений по компаниям — периоды действия реквизитов и атрибутов.
Проблема в том, что эти временные промежутки не совпадают, и чтобы свести данные по каждой организации, нужно сформировать единую временную сетку — матрицу всех возможных периодов.
📌 Главная мысль:
Мы создаём «нитку» времени, на которую потом будем нанизывать «бусинки» реквизитов из обеих таблиц.
🧠 Шаг 1. Формируем временные периоды
🪄 Здесь мы:
Объединили даты начала (ds) из обеих таблиц;
Для каждой строки нашли следующую дату (через LEAD) и вычли 1 день;
Нам вторая дата de, по сути, и не нужна, мы знаем, что end_period это следующая - 1 день, поэтому мы её и вычисляем.
Таким образом, получили интервалы [start_period, end_period].
🧩 Шаг 2. Совмещаем данные
Теперь, когда у нас есть нитка периодов, можно аккуратно «нанизать» реквизиты из обеих таблиц по левой связи:
⚙️ Результат:
Получаем непрерывную временную линию для каждой организации, где в каждом периоде указаны действующие:
реквизиты (из T1),
атрибуты (из T2).
💡 Вывод:
Такой приём — построение предварительной матрицы — часто встречается в задачах.
Это может быть и временные периоды, и матрицей всех возможных значений, матрица может выглядеть как набор множества полей.
Смысл один нужна "нитка", на которую нанизывают "бусинки".
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — хорошая задача.
👎 Палец вниз — трата времени в пустую.
💬 Хотите сказать - скажите! 👇.
#️⃣ #RealInterviewTasks #sql #Oracle #PostgreSQL #PL/SQL #PL/pgSQL
Мы фиато разъезжанто целый день в кабриолето, о йес,
Постоянно пьем чинзано, постоянно сыто-пьяно, о йес,
Держим банко миллионо и плеванто на законо, о йес.» - м/ф «Приключения капитана Врунгеля»
📊 Разбор задачи с собеседования ЦБ РФ
🧩 Идея задачи
В таблицах T1 и T2 хранится история изменений по компаниям — периоды действия реквизитов и атрибутов.
Проблема в том, что эти временные промежутки не совпадают, и чтобы свести данные по каждой организации, нужно сформировать единую временную сетку — матрицу всех возможных периодов.
📌 Главная мысль:
Мы создаём «нитку» времени, на которую потом будем нанизывать «бусинки» реквизитов из обеих таблиц.
🧠 Шаг 1. Формируем временные периоды
SELECT t.id,
t.reg_date AS start_period,
LEAD(t.reg_date, 1, TO_DATE('31.12.9999', 'DD.MM.YYYY'))
OVER (PARTITION BY t.id ORDER BY t.reg_date) - INTERVAL '1' DAY AS end_period
FROM (SELECT t1.id, t1.ds AS reg_date FROM t1
UNION
SELECT t2.id, t2.ds AS reg_date FROM t2) t
ORDER BY t.id, t.reg_date;
🪄 Здесь мы:
Объединили даты начала (ds) из обеих таблиц;
Для каждой строки нашли следующую дату (через LEAD) и вычли 1 день;
Нам вторая дата de, по сути, и не нужна, мы знаем, что end_period это следующая - 1 день, поэтому мы её и вычисляем.
Таким образом, получили интервалы [start_period, end_period].
🧩 Шаг 2. Совмещаем данные
Теперь, когда у нас есть нитка периодов, можно аккуратно «нанизать» реквизиты из обеих таблиц по левой связи:
WITH periods AS (
SELECT t.id,
t.reg_date AS start_period,
LEAD(t.reg_date, 1, TO_DATE('31.12.9999','DD.MM.YYYY'))
OVER (PARTITION BY t.id ORDER BY t.reg_date) - INTERVAL '1' DAY AS end_period
FROM (SELECT t1.id, t1.ds AS reg_date FROM t1
UNION
SELECT t2.id, t2.ds AS reg_date FROM t2) t
)
SELECT p.id,
t1.ogrn, t1.inn, t1.name,
t2.status, t2.address, t2.eq,
p.start_period AS ds,
p.end_period AS de
FROM periods p
LEFT JOIN t1 ON (p.id = t1.id AND p.start_period BETWEEN t1.ds AND t1.de AND p.end_period BETWEEN t1.ds AND t1.de)
LEFT JOIN t2 ON (p.id = t2.id AND p.start_period BETWEEN t2.ds AND t2.de AND p.end_period BETWEEN t2.ds AND t2.de)
ORDER BY p.id, p.start_period;
⚙️ Результат:
Получаем непрерывную временную линию для каждой организации, где в каждом периоде указаны действующие:
реквизиты (из T1),
атрибуты (из T2).
💡 Вывод:
Такой приём — построение предварительной матрицы — часто встречается в задачах.
Это может быть и временные периоды, и матрицей всех возможных значений, матрица может выглядеть как набор множества полей.
Смысл один нужна "нитка", на которую нанизывают "бусинки".
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — хорошая задача.
👎 Палец вниз — трата времени в пустую.
💬 Хотите сказать - скажите! 👇.
#️⃣ #RealInterviewTasks #sql #Oracle #PostgreSQL #PL/SQL #PL/pgSQL
👍4👎1
🎵 «Пускай капризен успех
Он выбирает из тех,
Кто может первым посмеяться над собой.
Пой засыпая, пой во сне, проснись и пой.» — Владимир Луговой
✨ Сегодня суббота — значит, время рубрики «Полезные ресурсы».
🐘 Если вы работаете со слоном PostgreSQL, то, возможно, уже знаете,
а если нет — срочно исправляйтесь:
есть мощный инструмент pgCodeKeeper для Eclipse.
Он умеет сравнивать схемы, генерировать миграции и даже дружит с Git —
словом, идеальный помощник, когда хочется, чтобы «всё было красиво,
а руками — поменьше».
🔗 pgcodekeeper.org
🔗 eclipse.org
🔗 Группа поддержки проекта
Его разработали проггеры из Такси Максим и это крутой инструмент.
Я сам его использовал и могу подтвердить, что такой инструмент повышает скорость разработки,
но и, вообще, меняет модель разработки - всё становится проще.
Идея в том, что плагин умеет сравнивать код базы и код из источника (GIT).
И все изменения из GIT может влить в выбранную базу, а все изменения из базе может записать в папку.
Останется только зафиксировать изменения в GIT.
А также может подтвердить, что источники эквивалентны.
Если Вы работаете с PostgreSQL, то использовать или нет это Ваше дело, но изучить этот инструмент Вы обязаны.
💎 Поддержка канала⁉️.
👍 Палец вверх — для Вас полезная информация
👎 Палец вниз — "ерунда на постном масле"
💬 Если не закончились клавиши на клавиатуре! 👇.
#️⃣ #Tools #PostgreSQL
Он выбирает из тех,
Кто может первым посмеяться над собой.
Пой засыпая, пой во сне, проснись и пой.» — Владимир Луговой
✨ Сегодня суббота — значит, время рубрики «Полезные ресурсы».
🐘 Если вы работаете со слоном PostgreSQL, то, возможно, уже знаете,
а если нет — срочно исправляйтесь:
есть мощный инструмент pgCodeKeeper для Eclipse.
Он умеет сравнивать схемы, генерировать миграции и даже дружит с Git —
словом, идеальный помощник, когда хочется, чтобы «всё было красиво,
а руками — поменьше».
🔗 pgcodekeeper.org
🔗 eclipse.org
🔗 Группа поддержки проекта
Его разработали проггеры из Такси Максим и это крутой инструмент.
Я сам его использовал и могу подтвердить, что такой инструмент повышает скорость разработки,
но и, вообще, меняет модель разработки - всё становится проще.
Идея в том, что плагин умеет сравнивать код базы и код из источника (GIT).
И все изменения из GIT может влить в выбранную базу, а все изменения из базе может записать в папку.
Останется только зафиксировать изменения в GIT.
А также может подтвердить, что источники эквивалентны.
Если Вы работаете с PostgreSQL, то использовать или нет это Ваше дело, но изучить этот инструмент Вы обязаны.
💎 Поддержка канала⁉️.
👍 Палец вверх — для Вас полезная информация
👎 Палец вниз — "ерунда на постном масле"
💬 Если не закончились клавиши на клавиатуре! 👇.
#️⃣ #Tools #PostgreSQL
pgCodeKeeper
A tool for PL/pgSQL code maintenance and migration scripts generation considering the dependent PostgreSQL objects graph.
👍8👎1
DB developers channel
Продолжим или закончим 5 часть "Мерлезонского балета" - решение задач методами процедурного языка. Я выбрал очередные 5 задач из 5 тем: работа с массивами, работа со строками, работа с матрицами, структуры данных, динамическое программирование.
🎵 «Отвесные стены — а ну, не зевай!
Ты здесь на везение не уповай
В горах ненадёжны ни камень, ни лёд, ни скала
Надеемся только на крепость рук
На руки друга и вбитый крюк
И молимся, чтобы страховка не подвела» — Владимир Высоцкий
📌 Разбор задачи: Максимальная разница между соседями
Я продолжаю тему решение задач процедурными методами.
Иногда жизнь — как дорога среди равнин и гор.
Мы шагаем вперёд, иногда ровно, а иногда — то вверх, то вниз.
Вот и в массиве бывают такие «перепады»: соседние значения могут отличаться сильно или почти не заметно.
Наша цель — найти самый большой скачок между элементами.
🧠 Что делает скрипт?
Берёт набор чисел:
0, 1, 2, 3, 7, 5, 6
Проходит по массиву, сравнивая каждую пару соседей.
Считает, где разница (по модулю) самая большая.
Выводит результат — максимальный «перепад высоты» между соседними элементами.
💡 Как работает:
Код движется по массиву,
замеряя, где был самый крутой подъём или спад.
Для массива (0,1,2,3,7,5,6)
максимальный скачок — 4 (между 3 и 7).
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — интересно
👎 Палец вниз — так себе
💬 Если лимит на буквы не закончился! 👇.
#️⃣ #sql #Oracle #PostgreSQL #PL/SQL #PL/pgSQL
Ты здесь на везение не уповай
В горах ненадёжны ни камень, ни лёд, ни скала
Надеемся только на крепость рук
На руки друга и вбитый крюк
И молимся, чтобы страховка не подвела» — Владимир Высоцкий
📌 Разбор задачи: Максимальная разница между соседями
Я продолжаю тему решение задач процедурными методами.
Иногда жизнь — как дорога среди равнин и гор.
Мы шагаем вперёд, иногда ровно, а иногда — то вверх, то вниз.
Вот и в массиве бывают такие «перепады»: соседние значения могут отличаться сильно или почти не заметно.
Наша цель — найти самый большой скачок между элементами.
🧠 Что делает скрипт?
Берёт набор чисел:
0, 1, 2, 3, 7, 5, 6
Проходит по массиву, сравнивая каждую пару соседей.
Считает, где разница (по модулю) самая большая.
Выводит результат — максимальный «перепад высоты» между соседними элементами.
DECLARE
TYPE set_tab IS TABLE OF NUMBER;
"set" set_tab := set_tab (0,1,2,3,7,5,6);
residualMaxAbs NUMBER := 0;
BEGIN
FOR i IN 2.."set".LAST LOOP
IF ABS("set"(i) - "set"(i-1)) > residualMaxAbs THEN
residualMaxAbs := ABS("set"(i) - "set"(i-1));
END IF;
END LOOP;
DBMS_OUTPUT.put_line('residualMaxAbs = ' || TO_CHAR(residualMaxAbs));
END;
💡 Как работает:
Код движется по массиву,
замеряя, где был самый крутой подъём или спад.
Для массива (0,1,2,3,7,5,6)
максимальный скачок — 4 (между 3 и 7).
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — интересно
👎 Палец вниз — так себе
💬 Если лимит на буквы не закончился! 👇.
#️⃣ #sql #Oracle #PostgreSQL #PL/SQL #PL/pgSQL
👍3👎1
DB developers channel
Задание Oracle (1).docx
Задание Oracle (1).docx
25.2 KB
🎵 «И если мне неймётся и не спится
Или с похмелья нет на мне лица
Открою Кодекс на любой странице
И не могу — читаю до конца» — Владимир Высоцкий
🎯 Вторая задача с собеса ЦБ РФ (2020 год).
Это было время, когда SQL-интервью ещё высылали по почте.
На тот момент эта задача мне показалась трудной, но очень интересной.
Потратил на неё много времени - 3-4 часа!
Я опубликовал задачи от ЦБ самыми первыми на канале, но хочу их переопубликовать,
чтобы уделить задачам больше внимание. Эти задачи этого заслуживают.
📂 В прошлой задаче мы формировали полную сводную историю реквизитов организации.
А теперь — вторая часть: нужно объединить бизнес-реквизиты и сократить историю.
Используем те же таблицы T1 и T2:
T1 — основные реквизиты (OGRN, INN, NAME)
T2 — расширенные реквизиты (EQ и даты действия)
История по каждой таблице непрерывная и может начинаться с разных дат.
📄 Написать запрос, который формирует сводную историю бизнес реквизитов OGRN, INN, NAME и EQ
При этом две и более, рядом стоящие в истории строки, с одинаковыми бизнес реквизитами
должны быть заменены одной строкой с объеденным интервалом действия этих записей
(строки выделены красным цветом, строки с номерам 3-5).
Строки с номерами 2 и 6 имеют одинаковые бизнес реквизиты, но они не являются рядом стоящие,
так как между этими записями во временном интервале присутствует другой набор бизнес реквизитов (строки 3-5).
Итоговая таблица должна быть следующего вида:
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Как бы вы подошли к решению? Пишите в комментариях свои идеи! 👇
#️⃣ #RealInterviewTasks #SQL #Oracle #PLSQL #PostgreSQL
Или с похмелья нет на мне лица
Открою Кодекс на любой странице
И не могу — читаю до конца» — Владимир Высоцкий
🎯 Вторая задача с собеса ЦБ РФ (2020 год).
Это было время, когда SQL-интервью ещё высылали по почте.
На тот момент эта задача мне показалась трудной, но очень интересной.
Потратил на неё много времени - 3-4 часа!
Я опубликовал задачи от ЦБ самыми первыми на канале, но хочу их переопубликовать,
чтобы уделить задачам больше внимание. Эти задачи этого заслуживают.
📂 В прошлой задаче мы формировали полную сводную историю реквизитов организации.
А теперь — вторая часть: нужно объединить бизнес-реквизиты и сократить историю.
Используем те же таблицы T1 и T2:
T1 — основные реквизиты (OGRN, INN, NAME)
T2 — расширенные реквизиты (EQ и даты действия)
История по каждой таблице непрерывная и может начинаться с разных дат.
📄 Написать запрос, который формирует сводную историю бизнес реквизитов OGRN, INN, NAME и EQ
Номер ID OGRN INN NAME EQ DS DE
1 125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 01.01.2017 04.01.2017
2 125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 10 000 05.01.2017 30.06.2018
🔴 3 125 1127847448520 7810880684 ООО "ЛЕНТЕХ-РЕЗЕРВ" 10 000 01.07.2018 10.09.2018
🔴 4 125 1127847448520 7810880684 ООО "ЛЕНТЕХ-РЕЗЕРВ" 10 000 11.09.2018 20.04.2019
🔴 5 125 1127847448520 7810880684 ООО "ЛЕНТЕХ-РЕЗЕРВ" 10 000 21.04.2019 31.12.2019
6 125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 10 000 01.01.2020 31.12.9999
При этом две и более, рядом стоящие в истории строки, с одинаковыми бизнес реквизитами
должны быть заменены одной строкой с объеденным интервалом действия этих записей
(строки выделены красным цветом, строки с номерам 3-5).
Строки с номерами 2 и 6 имеют одинаковые бизнес реквизиты, но они не являются рядом стоящие,
так как между этими записями во временном интервале присутствует другой набор бизнес реквизитов (строки 3-5).
Итоговая таблица должна быть следующего вида:
ID OGRN INN NAME EQ DS DE
125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 01.01.2017 04.01.2017
125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 10 000 05.01.2017 30.06.2018
125 1127847448520 7810880684 ООО "ЛЕНТЕХ-РЕЗЕРВ" 10 000 01.07.2018 31.12.2019
125 1127847448520 7810880684 ООО "ЛЕН-РЕЗЕРВ" 10 000 01.01.2020 31.12.9999
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Как бы вы подошли к решению? Пишите в комментариях свои идеи! 👇
#️⃣ #RealInterviewTasks #SQL #Oracle #PLSQL #PostgreSQL
👍2👎1
DB developers channel
script_create_insert.sql
🎵 «Возьму с собою я в прогулку кавалера
Он песняки мои все знает наизусть
Не иностранец и не сын миллионера
Бухгалтер он простой, да ну и пусть» — Алена Апина
📊 Разбор второй задачи с собеса ЦБ РФ (2020 г.)
🧩 Идея задачи
В таблицах T1 и T2 хранится история по компаниям — периоды действия основных и дополнительных реквизитов.
После объединения этих таблиц мы получаем длинную «историю жизни» компании, но с дублированием соседних строк, где бизнес-реквизиты не менялись.
Шаг 1. Формируем непрерывную временную матрицу. (periods )
Шаг 2. Нанизываем реквизиты на временную «нить» (summary_data )
Теперь к каждому периоду подцепляем актуальные значения из T1 и T2.
Первые два шага уже разбирали - повторяться не буду.
Есть интересный момент с хаш функцией: вместо, чтобы "тащить" поля t1.ogrn, t1.inn, t1.name, t2.eq, мы вычисляем ХАШ значения hash_group.
Дело в том, что от количество полей, которые используется в GROUP BY, напрямую зависит объем использования временного табличного пространства TEMP и зависимость драматическая. Необходимо использовать, либо хаш функции, либо указываете только те поля, которые определяют группировку и не более.
Остальные же поля можно вывести в виде результата агрегатных функций. Например как MAX(sd2.id) AS id.
Шаг 3. Сжимаем соседние периоды с одинаковыми данными
Чтобы объединить подряд идущие периоды, используем оконные функции.
Тут есть интересный SQL трюк, который выдумать на ходу крайне сложно.
Для нахождения нужной нам группы мы используем ROW_NUMBER() и ставим 1, если начинается новая группа и 0, если мы находимся в старой группе.
И так, мы определим поле hash_group_sign.
Далее, мы используем оконную функцию, которая проходит по полю hash_group_sign и вычисляет накопительную сумму
и таким образом все записи одной группы будут иметь свой вычисляемый синтетический идентификатор group_id.
Шаг 4. Агрегируем по группе и берём минимальный ds и максимальный de.
Поле hash_group задает уникальные реквизиты, поле group_id задает уникальность внутри соседних групп.
💡 Вывод:
Такой приём — «временная компрессия» — часто встречается в аналитических задачах.
Сначала строим «нить времени», потом «нанизываем» реквизиты, и наконец — сжимаем лишние интервалы, где данные не менялись.
⚠️ Хотите проверить скрипты, но нет базы под рукой — используйте онлайн-песочницу.
💎 Поддержка канала⁉️
👍 Палец вверх — задача стоящая.
👎 Палец вниз — слишком много аналитики.
💬 Как бы вы подошли к этой задаче? Пишите в комментариях 👇
#️⃣ #RealInterviewTasks #SQL #Oracle #PostgreSQL
Он песняки мои все знает наизусть
Не иностранец и не сын миллионера
Бухгалтер он простой, да ну и пусть» — Алена Апина
📊 Разбор второй задачи с собеса ЦБ РФ (2020 г.)
🧩 Идея задачи
В таблицах T1 и T2 хранится история по компаниям — периоды действия основных и дополнительных реквизитов.
После объединения этих таблиц мы получаем длинную «историю жизни» компании, но с дублированием соседних строк, где бизнес-реквизиты не менялись.
WITH
periods AS
(SELECT t.id,
t.reg_date AS start_period,
COALESCE (LEAD (t.reg_date) OVER (PARTITION BY t.id ORDER BY t.reg_date) - INTERVAL '1' DAY,
TO_DATE ('31.12.9999', 'DD.MM.YYYY')) AS end_period
FROM (SELECT t1.id, t1.ds AS reg_date FROM t1
UNION
SELECT t2.id, t2.ds AS reg_date FROM t2) t
ORDER BY t.id, t.reg_date),
summary_data AS
(SELECT p.id,
t1.ogrn,
t1.inn,
t1.name,
t2.eq,
p.start_period AS ds,
p.end_period AS de,
STANDARD_HASH (t1.ogrn || t1.inn || t1.name || '/' || t2.eq) AS hash_group
FROM periods p
LEFT JOIN t1 ON (p.id = t1.id AND p.start_period BETWEEN t1.ds AND t1.de AND p.end_period BETWEEN t1.ds AND t1.de)
LEFT JOIN t2 ON (p.id = t2.id AND p.start_period BETWEEN t2.ds AND t2.de AND p.end_period BETWEEN t2.ds AND t2.de)
)
SELECT
MAX(sd2.id) AS id,
MAX(sd2.ogrn) AS ogrn,
MAX(sd2.inn) AS inn,
MAX(sd2.name) AS name,
MAX(sd2.eq) AS eq,
MIN (sd2.ds) AS ds,
MAX (sd2.de) AS de
FROM
(SELECT
sd1.*,
SUM(hash_group_sign) OVER (ORDER BY sd1.ds) AS group_id
FROM
(SELECT
sd.*,
CASE
WHEN ROW_NUMBER () OVER (PARTITION BY sd.hash_group ORDER BY sd.ds) = 1 THEN 1
ELSE 0
END AS hash_group_sign
FROM summary_data sd) sd1) sd2
GROUP BY sd2.hash_group, sd2.group_id
ORDER BY id, ds
Шаг 1. Формируем непрерывную временную матрицу. (periods )
Шаг 2. Нанизываем реквизиты на временную «нить» (summary_data )
Теперь к каждому периоду подцепляем актуальные значения из T1 и T2.
Первые два шага уже разбирали - повторяться не буду.
Есть интересный момент с хаш функцией: вместо, чтобы "тащить" поля t1.ogrn, t1.inn, t1.name, t2.eq, мы вычисляем ХАШ значения hash_group.
Дело в том, что от количество полей, которые используется в GROUP BY, напрямую зависит объем использования временного табличного пространства TEMP и зависимость драматическая. Необходимо использовать, либо хаш функции, либо указываете только те поля, которые определяют группировку и не более.
Остальные же поля можно вывести в виде результата агрегатных функций. Например как MAX(sd2.id) AS id.
Шаг 3. Сжимаем соседние периоды с одинаковыми данными
Чтобы объединить подряд идущие периоды, используем оконные функции.
Тут есть интересный SQL трюк, который выдумать на ходу крайне сложно.
Для нахождения нужной нам группы мы используем ROW_NUMBER() и ставим 1, если начинается новая группа и 0, если мы находимся в старой группе.
И так, мы определим поле hash_group_sign.
Далее, мы используем оконную функцию, которая проходит по полю hash_group_sign и вычисляет накопительную сумму
и таким образом все записи одной группы будут иметь свой вычисляемый синтетический идентификатор group_id.
Шаг 4. Агрегируем по группе и берём минимальный ds и максимальный de.
Поле hash_group задает уникальные реквизиты, поле group_id задает уникальность внутри соседних групп.
💡 Вывод:
Такой приём — «временная компрессия» — часто встречается в аналитических задачах.
Сначала строим «нить времени», потом «нанизываем» реквизиты, и наконец — сжимаем лишние интервалы, где данные не менялись.
⚠️ Хотите проверить скрипты, но нет базы под рукой — используйте онлайн-песочницу.
💎 Поддержка канала⁉️
👍 Палец вверх — задача стоящая.
👎 Палец вниз — слишком много аналитики.
💬 Как бы вы подошли к этой задаче? Пишите в комментариях 👇
#️⃣ #RealInterviewTasks #SQL #Oracle #PostgreSQL
👍6👎1
🎵 «Я гляжу ей вслед -
Ничего в ней нет,
А я все гляжу -
Глаз не отвожу.» — Лев Ошанин
⚙️ Рубрика — «Случай из практики»: антиспам и агрегация сообщений
Есть таблица сообщений, из которой внешний сервис читает новые записи и:
пересылает события подписанным системам,
а также дублирует информацию во внутренний чат мессенджера для менеджеров.
Если что-то пошло не так, сообщение попадает в чат.
И вот тут начинается самое интересное…
Допустим, один из видов сообщений —
«Шеф! Всё пропало! Клиент уезжает — гипс снимают!»
Если такое сообщение появляется раз в час, это можно пережить.
Но если оно сыпется каждые несколько секунд, менеджеры начинают паниковать
🎯 Задача:
На стороне базы данных реализовать систему антиспам / агрегации сообщений
с возможностью настройки частоты публикации для каждого типа события.
Смысл — группировать похожие сообщения и публиковать их реже,
если поток становится слишком интенсивным.
📊 Пример логики:
Первое сообщение отправляем сразу.
Если за период пришло 2–10 сообщений → отправляем одно сгруппированное сообщение с задержкой 10 минут.
Если за период пришло 11–50 сообщений → одно сообщение с задержкой 30 минут.
Если за период пришло более 50 сообщений → одно сообщение с задержкой 1 час.
Формат сгруппированного сообщения, например:
«За последние 10 минут поступило 27 сообщений вида
“Шеф! Всё пропало! Клиент уезжает — гипс снимают!”»
⚠️ Требования к решению:
1️⃣ Исключить конкуренцию между сессиями — без блокировок и гонок данных.
2️⃣ Поддерживать разные настройки частоты для разных типов сообщений.
⚠️ Хотите проверить скрипты, но нет базы под рукой — используйте онлайн-песочницу.
💎 Поддержка канала⁉️
👍 Палец вверх — интересна реализация.
👎 Палец вниз — не интересно.
💬 Есть мысли как это сделать - пишите 👇
#️⃣ #Cases #SQL #Oracle #PostgreSQL #PLSQL
Ничего в ней нет,
А я все гляжу -
Глаз не отвожу.» — Лев Ошанин
⚙️ Рубрика — «Случай из практики»: антиспам и агрегация сообщений
Есть таблица сообщений, из которой внешний сервис читает новые записи и:
пересылает события подписанным системам,
а также дублирует информацию во внутренний чат мессенджера для менеджеров.
Если что-то пошло не так, сообщение попадает в чат.
И вот тут начинается самое интересное…
Допустим, один из видов сообщений —
«Шеф! Всё пропало! Клиент уезжает — гипс снимают!»
Если такое сообщение появляется раз в час, это можно пережить.
Но если оно сыпется каждые несколько секунд, менеджеры начинают паниковать
🎯 Задача:
На стороне базы данных реализовать систему антиспам / агрегации сообщений
с возможностью настройки частоты публикации для каждого типа события.
Смысл — группировать похожие сообщения и публиковать их реже,
если поток становится слишком интенсивным.
📊 Пример логики:
Первое сообщение отправляем сразу.
Если за период пришло 2–10 сообщений → отправляем одно сгруппированное сообщение с задержкой 10 минут.
Если за период пришло 11–50 сообщений → одно сообщение с задержкой 30 минут.
Если за период пришло более 50 сообщений → одно сообщение с задержкой 1 час.
Формат сгруппированного сообщения, например:
«За последние 10 минут поступило 27 сообщений вида
“Шеф! Всё пропало! Клиент уезжает — гипс снимают!”»
⚠️ Требования к решению:
1️⃣ Исключить конкуренцию между сессиями — без блокировок и гонок данных.
2️⃣ Поддерживать разные настройки частоты для разных типов сообщений.
⚠️ Хотите проверить скрипты, но нет базы под рукой — используйте онлайн-песочницу.
💎 Поддержка канала⁉️
👍 Палец вверх — интересна реализация.
👎 Палец вниз — не интересно.
💬 Есть мысли как это сделать - пишите 👇
#️⃣ #Cases #SQL #Oracle #PostgreSQL #PLSQL
👍8👎1
DB developers channel
Продолжим или закончим 5 часть "Мерлезонского балета" - решение задач методами процедурного языка. Я выбрал очередные 5 задач из 5 тем: работа с массивами, работа со строками, работа с матрицами, структуры данных, динамическое программирование.
🎵 «Сапер ошибается два раза в жизни: первый раз при выборе профессии,
второй раз - когда женится» — несмешной анекдот
📌 Рубрика решение задач процедурными методами
Сапёр: Для данной матрицы сапёра, где -1 обозначает бомбу, а 0 — пустую клетку, вычислите количество бомб, смежных с каждой пустой клеткой.
Что делает скрипт?
Берёт матрицу поля, где
-1 — это бомба,
0 — пустая клетка.
Проходит по всем клеткам.
Для каждой бомбы прибавляет +1
во все соседние ячейки — вверх, вниз, влево, вправо и по диагонали.
В итоге каждая пустая клетка «узнаёт», сколько мин рядом.
Получается классическая карта сапёра.
💡 Как работает:
Каждая мина «освещает» вокруг себя все восемь направлений.
Код считает количество соседних мин, где каждая клетка знает, насколько опасно рядом.
🧩 Из входных данных:
получаем:
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — алгоритмы трогают Ваше сердце
👎 Палец вниз — это всё не для DB
💬 Пишите, и Ваше слово будет прочитано! 👇.
#️⃣ #SQL #Oracle #PLSQL
второй раз - когда женится» — несмешной анекдот
📌 Рубрика решение задач процедурными методами
Сапёр: Для данной матрицы сапёра, где -1 обозначает бомбу, а 0 — пустую клетку, вычислите количество бомб, смежных с каждой пустой клеткой.
Что делает скрипт?
Берёт матрицу поля, где
-1 — это бомба,
0 — пустая клетка.
Проходит по всем клеткам.
Для каждой бомбы прибавляет +1
во все соседние ячейки — вверх, вниз, влево, вправо и по диагонали.
В итоге каждая пустая клетка «узнаёт», сколько мин рядом.
Получается классическая карта сапёра.
DECLARE
TYPE row_tab IS TABLE OF NUMBER;
TYPE matrix_tab IS TABLE OF row_tab;
matrix matrix_tab := matrix_tab (row_tab (-1, 0, -1, 0), row_tab (0, -1, 0, -1), row_tab (0, 0, 0, 0), row_tab (-1, -1, 0, 0));
BEGIN
FOR i IN 1..matrix.COUNT
LOOP
FOR j IN 1..matrix(1).COUNT
LOOP
IF matrix(i)(j) = -1 THEN
IF i+1 <= matrix.COUNT AND j+1 <= matrix(1).COUNT THEN
IF matrix(i + 1)(j+1) != -1 THEN
matrix(i + 1)(j+1) := matrix(i + 1)(j+1) + 1;
END IF;
END IF;
IF i+1 <= matrix.COUNT THEN
IF matrix(i+1)(j) != -1 THEN
matrix(i+1)(j) := matrix(i+1)(j) + 1;
END IF;
END IF;
IF j+1 <= matrix(1).COUNT THEN
IF matrix (i)(j+1) != -1 THEN
matrix(i)(j+1) := matrix(i)(j+1) + 1;
END IF;
END IF;
IF i - 1 > 0 AND j - 1 > 0 THEN
IF matrix (i-1)(j-1) != -1 THEN
matrix(i-1)(j-1) := matrix(i-1)(j-1) + 1;
END IF;
END IF;
IF i - 1 > 0 THEN
IF matrix (i-1)(j) != -1 THEN
matrix(i-1)(j) := matrix(i-1)(j) + 1;
END IF;
END IF;
IF j - 1 > 0 THEN
IF matrix (i)(j-1) != -1 THEN
matrix(i)(j-1) := matrix(i)(j-1) + 1;
END IF;
END IF;
END IF;
END LOOP;
END LOOP;
-- print matrix
FOR i IN 1..matrix.COUNT LOOP
DECLARE s VARCHAR2(100);
BEGIN
FOR j IN 1..matrix(1).COUNT LOOP
s := s || LPAD(matrix(i)(j), 5);
END LOOP;
DBMS_OUTPUT.put_line(s);
END;
END LOOP;
END;
💡 Как работает:
Каждая мина «освещает» вокруг себя все восемь направлений.
Код считает количество соседних мин, где каждая клетка знает, насколько опасно рядом.
🧩 Из входных данных:
-1 0 -1 0
0 -1 0 -1
0 0 0 0
-1 -1 0 0
получаем:
-1 3 -1 2
2 -1 3 -1
2 2 1 1
-1 -1 1 0
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — алгоритмы трогают Ваше сердце
👎 Палец вниз — это всё не для DB
💬 Пишите, и Ваше слово будет прочитано! 👇.
#️⃣ #SQL #Oracle #PLSQL
🕊3
🎵 «Разлук так много на земле и разных судеб,
Надежду дарит на заре паромщик людям.
То берег левый нужен им, то берег правый…
Влюблённых много — он один у переправы.»
— Михаил Танич
🚀 Серия: Оптимизация SQL-запросов
📘 Тема: Чтение плана запроса при соединении двух и более таблиц.
Для примера возьмём две таблицы — A и B.
И самый простой запрос:
Поставьте себя на место оптимизатора.
Как бы вы соединили таблицы?
Наверное, взяли бы первую строку из таблицы A, посмотрели её id,
а потом по этому id нашли бы соответствующие строки в B.
То есть прошли бы циклом по A, а для каждой строки делали бы поиск в B.
Верно? 💡
Отлично! Именно этот способ называется Nested Loop Join.
📖 Подробности можно прочитать в официальной документации Oracle: docs.oracle.com — Joins
Но подождите…
А если в A — миллиард записей, а в B — всего две?
Проходить миллиард раз и искать в B — нерационально.
Очевидное решение — просто поменять местами таблицы.
И вот тут начинается самое интересное —
🧩 Оптимизатор должен выбрать порядок соединения таблиц.
Если таблиц много (A, B, C, D, …, Z),
то количество вариантов соединения растёт факториально:
10! = 3 628 800 вариантов при соединении 10 таблиц
Даже Терминатор T-800 устанет столько считать!
Oracle действительно оценивает разные варианты,
но чтобы не сойти с ума, ставит внутренние ограничения
(точную границу в документации не раскрывают) на разбор всех вариантов. Тут хинты требуются точно.
💡 Как подсказать оптимизатору, в каком порядке соединять таблицы?
Хинт LEADING — явно указываем порядок:
Хинт ORDERED — порядок берётся прямо из текста запроса:
📚 Сегодня мы обсудили:
1️⃣ Тип соединения таблиц — Nested Loop
2️⃣ Порядок соединения и способы его изменить (LEADING, ORDERED)
А что делать, если в A — 1 млрд строк, а в B — 2 млрд?
Даже уникальный индекс не спасёт — цикл будет слишком долгим.
В следующем посте разберём другие типы соединений:
HASH JOIN и MERGE JOIN.
💎 Поддержка канала
👍 Палец вверх — продолжаем разбор SQL-планов
👎 Палец вниз — зачем писать очевидные вещи?
💬 Если заметили неточность — напишите в комментариях 👇
#️⃣ #SQLOptimization #SQL #Oracle
Надежду дарит на заре паромщик людям.
То берег левый нужен им, то берег правый…
Влюблённых много — он один у переправы.»
— Михаил Танич
🚀 Серия: Оптимизация SQL-запросов
📘 Тема: Чтение плана запроса при соединении двух и более таблиц.
Для примера возьмём две таблицы — A и B.
И самый простой запрос:
SELECT a.*, b.*
FROM a
INNER JOIN b ON (a.id = b.id);
Поставьте себя на место оптимизатора.
Как бы вы соединили таблицы?
Наверное, взяли бы первую строку из таблицы A, посмотрели её id,
а потом по этому id нашли бы соответствующие строки в B.
То есть прошли бы циклом по A, а для каждой строки делали бы поиск в B.
Верно? 💡
Отлично! Именно этот способ называется Nested Loop Join.
📖 Подробности можно прочитать в официальной документации Oracle: docs.oracle.com — Joins
Но подождите…
А если в A — миллиард записей, а в B — всего две?
Проходить миллиард раз и искать в B — нерационально.
Очевидное решение — просто поменять местами таблицы.
И вот тут начинается самое интересное —
🧩 Оптимизатор должен выбрать порядок соединения таблиц.
Если таблиц много (A, B, C, D, …, Z),
то количество вариантов соединения растёт факториально:
10! = 3 628 800 вариантов при соединении 10 таблиц
Даже Терминатор T-800 устанет столько считать!
Oracle действительно оценивает разные варианты,
но чтобы не сойти с ума, ставит внутренние ограничения
(точную границу в документации не раскрывают) на разбор всех вариантов. Тут хинты требуются точно.
💡 Как подсказать оптимизатору, в каком порядке соединять таблицы?
Хинт LEADING — явно указываем порядок:
SELECT /*+ LEADING(z y x ... a) */
a.*, b.*
FROM a
JOIN b ON (a.id = b.id)
JOIN c ON (a.id = c.id)
...
JOIN z ON (y.id = z.id);
Хинт ORDERED — порядок берётся прямо из текста запроса:
SELECT /*+ ORDERED */
...
FROM a
JOIN b
JOIN c ...
📚 Сегодня мы обсудили:
1️⃣ Тип соединения таблиц — Nested Loop
2️⃣ Порядок соединения и способы его изменить (LEADING, ORDERED)
А что делать, если в A — 1 млрд строк, а в B — 2 млрд?
Даже уникальный индекс не спасёт — цикл будет слишком долгим.
В следующем посте разберём другие типы соединений:
HASH JOIN и MERGE JOIN.
💎 Поддержка канала
👍 Палец вверх — продолжаем разбор SQL-планов
👎 Палец вниз — зачем писать очевидные вещи?
💬 Если заметили неточность — напишите в комментариях 👇
#️⃣ #SQLOptimization #SQL #Oracle
👍16👎1
🎵 «А нам всё равно, пусть боимся мы волка и сову…» — Леонид Дербенёв.
📊 Простая, но полезная задача из практики
Часто приходится проверять, что возвращает метод или процедура, особенно если она выдаёт результат в виде SYS_REFCURSOR.
Но когда полей в выборке десятки (а то и сотни) — перечислять их явно в FETCH совсем не хочется 😅
Например, такой метод
🧩 Вопрос подписчикам:
Как вывести содержимое SYS_REFCURSOR — включая NULL-поля — не указывая имена колонок явно?
Есть способ сделать это лаконично и даже красиво 😎
Как бы Вы поступили в такой ситуации?
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — задача полезная
👎 Палец вниз — бессмысленная трата времени
👇 Делитесь своими решениями в комментариях!
#️⃣ #Cases #SQL #Oracle #PLSQL
📊 Простая, но полезная задача из практики
Часто приходится проверять, что возвращает метод или процедура, особенно если она выдаёт результат в виде SYS_REFCURSOR.
Но когда полей в выборке десятки (а то и сотни) — перечислять их явно в FETCH совсем не хочется 😅
Например, такой метод
CREATE OR REPLACE PACKAGE test_refcur AS
PROCEDURE get_refcur(refcur OUT SYS_REFCURSOR);
END test_refcur;
/
CREATE OR REPLACE PACKAGE BODY test_refcur AS
PROCEDURE get_refcur (refcur OUT SYS_REFCURSOR) IS
BEGIN
OPEN refcur FOR
SELECT
1 AS col1,
2.3 AS col2,
'4' AS col3,
CAST (NULL AS NUMBER) AS col4,
CAST (NULL AS VARCHAR2 (10)) AS col5
FROM DUAL;
END get_refcur;
END test_refcur;
/
🧩 Вопрос подписчикам:
Как вывести содержимое SYS_REFCURSOR — включая NULL-поля — не указывая имена колонок явно?
Есть способ сделать это лаконично и даже красиво 😎
Как бы Вы поступили в такой ситуации?
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — задача полезная
👎 Палец вниз — бессмысленная трата времени
👇 Делитесь своими решениями в комментариях!
#️⃣ #Cases #SQL #Oracle #PLSQL
👍8👎1