🎵 «Как следует смажь оба "кольта",
"Винчестер" как следует смажь
И трогай в дорогу, поскольку
Взбрела тебе в голову блажь.» — Юлий Ким
📌 Разбор задачи: Разбиение строки на слова
Цель — реализовать задачу разделения строки на последовательность слов из словаря,
используя только процедурные методы PL/SQL, без регулярных выражений и внешних функций.
💡 Идея алгоритма:
Это приём динамического программирования (DP):
мы проверяем, можно ли разбить строку так, чтобы каждая часть входила в словарь.
Алгоритм проходит по строке, проверяет все возможные подстроки и отмечает,
где удаётся найти известное слово.
Исходные данные:
Строка: catsandddog
Словарь: cat, cats, and, sand, dog
Задача — понять, можно ли эту строку собрать из слов словаря.
🔧 Ключевые шаги:
1️⃣ Инициализируем массив matrix, где matrix(i) = 1,
если подстрока до позиции i может быть составлена из слов словаря.
2️⃣ Проходим по всем позициям строки,
для каждой проверяем возможные окончания слов —
если найдено совпадение с элементом из словаря, помечаем позицию как достижимую.
3️⃣ В массив prevs записываем позиции,
с которых начинается каждое найденное слово —
это даёт возможность восстановить путь разбиения.
🪄 Что делает код:
Проверяет, можно ли из заданных слов собрать исходную строку.
Заполняет таблицу достижимых позиций.
Выводит результат и промежуточные таблицы (matrix, prevs) для визуального анализа.
Пример работы:
Исходная строка: catsandddog
Результат: NO
Потому что в словаре нет слова “ddog” — значит строка неразбиваемая. Если добавить в словарь "ddog", алгоритм сразу найдёт решение и выведет YES.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — Ни дня без алгоритмов.
👎 Палец вниз — Хорошо же сидели и вот опять!
💬 Хороший коммент - половина дела 👇.
#️⃣ #Oracle #PLSQL
"Винчестер" как следует смажь
И трогай в дорогу, поскольку
Взбрела тебе в голову блажь.» — Юлий Ким
📌 Разбор задачи: Разбиение строки на слова
Цель — реализовать задачу разделения строки на последовательность слов из словаря,
используя только процедурные методы PL/SQL, без регулярных выражений и внешних функций.
💡 Идея алгоритма:
Это приём динамического программирования (DP):
мы проверяем, можно ли разбить строку так, чтобы каждая часть входила в словарь.
Алгоритм проходит по строке, проверяет все возможные подстроки и отмечает,
где удаётся найти известное слово.
Исходные данные:
Строка: catsandddog
Словарь: cat, cats, and, sand, dog
Задача — понять, можно ли эту строку собрать из слов словаря.
🔧 Ключевые шаги:
1️⃣ Инициализируем массив matrix, где matrix(i) = 1,
если подстрока до позиции i может быть составлена из слов словаря.
2️⃣ Проходим по всем позициям строки,
для каждой проверяем возможные окончания слов —
если найдено совпадение с элементом из словаря, помечаем позицию как достижимую.
3️⃣ В массив prevs записываем позиции,
с которых начинается каждое найденное слово —
это даёт возможность восстановить путь разбиения.
DECLARE
str VARCHAR2 (100) := 'catsandddog';
TYPE word_tab IS TABLE OF VARCHAR2 (100);
words word_tab
:= word_tab ('cat',
'cats',
'and',
'sand',
'dog');
TYPE matrix_tab IS TABLE OF INTEGER;
matrix matrix_tab := matrix_tab ();
prevs matrix_tab := matrix_tab ();
PROCEDURE get_matrix (words IN OUT NOCOPY word_tab, matrix IN OUT NOCOPY matrix_tab, prevs OUT matrix_tab) IS
resultPrevs matrix_tab := matrix_tab ();
BEGIN
matrix.EXTEND (LENGTH (str) + 1);
matrix (1) := 1;
FOR i IN 2 .. matrix.COUNT LOOP
matrix (i) := 0;
FOR j IN 2 .. i LOOP
DECLARE
isWordFound VARCHAR2 (1) := 'N';
BEGIN
FOR q IN 1 .. words.COUNT LOOP
IF matrix (j - 1) = 1 AND SUBSTR (str, j - 1, i - j + 1) = words (q) THEN
isWordFound := 'Y';
EXIT;
END IF;
END LOOP;
IF isWordFound = 'Y' THEN
matrix (i) := 1;
resultPrevs.EXTEND;
resultPrevs (resultPrevs.COUNT) := j;
END IF;
END;
END LOOP;
END LOOP;
prevs := resultPrevs;
END get_matrix;
PROCEDURE print_matrix (matrix IN OUT NOCOPY matrix_tab) IS
strTemp VARCHAR2 (100);
BEGIN
FOR i IN 1 .. matrix.COUNT LOOP
strTemp :=
CASE
WHEN strTemp IS NULL THEN '"' || COALESCE (TO_CHAR (matrix (i)), ' ')
ELSE strTemp || ' "' || COALESCE (TO_CHAR (matrix (i)), ' ') || '"'
END;
END LOOP;
DBMS_OUTPUT.put_line ('matrix');
DBMS_OUTPUT.put_line (strTemp);
END print_matrix;
BEGIN
get_matrix (words => words, matrix => matrix, prevs => prevs);
print_matrix (matrix => matrix);
print_matrix (matrix => prevs);
IF matrix (matrix.COUNT) = 1 THEN
DBMS_OUTPUT.PUT_LINE('YES');
ELSE
DBMS_OUTPUT.PUT_LINE('NO');
END IF;
END;
🪄 Что делает код:
Проверяет, можно ли из заданных слов собрать исходную строку.
Заполняет таблицу достижимых позиций.
Выводит результат и промежуточные таблицы (matrix, prevs) для визуального анализа.
Пример работы:
Исходная строка: catsandddog
Результат: NO
Потому что в словаре нет слова “ddog” — значит строка неразбиваемая. Если добавить в словарь "ddog", алгоритм сразу найдёт решение и выведет YES.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Палец вверх — Ни дня без алгоритмов.
👎 Палец вниз — Хорошо же сидели и вот опять!
💬 Хороший коммент - половина дела 👇.
#️⃣ #Oracle #PLSQL
👍9👎2🔥1
Затравка для следующего поста. Как Вы считаете, если в плане запроса есть запись MERGE JOIN CARTESIAN, насколько это плохо? и, если Вы отвечаете неплохо, то в каких именно случаях декартовых произведения множеств полезны?
Anonymous Poll
17%
В плане запроса есть MERGE JOIN CARTESIAN - жди беды!
74%
Во общем случае, повод насторожиться, но есть редкие моменты, когда декартовое произведение полезно!
9%
Очень часто использую декартовое произведение множеств!
DB developers channel
Затравка для следующего поста. Как Вы считаете, если в плане запроса есть запись MERGE JOIN CARTESIAN, насколько это плохо? и, если Вы отвечаете неплохо, то в каких именно случаях декартовых произведения множеств полезны?
🔆 Cogito, ergo sum
или по-русски «Мыслю, следовательно, существую» - Рене Декарт
🚀 Серия: Оптимизация SQL-запросов
Тема: MERGE CARTESIAN — соединения таблиц
💡 Во всех источниках по оптимизации SQL можно встретить предупреждение:
если видите в плане строку MERGE JOIN CARTESIAN, — стоит насторожиться.
Почему?
Потому что это значит, что оптимизатор решил перемножить два набора данных,
то есть выполнить декартово произведение множеств.
Такой вид соединения вы гарантированно получите, если напишете, например:
📘 Результат этих запросов — это декартово произведение множеств A и B.
Если вдруг забыли, как это выглядит — вот пример:
То есть количество строк = 3 * 3 = 9.
🧠 А теперь представьте, что таблица A содержит 1 млрд строк,
а таблица B — 100 млн строк.
И вот уже результат — 10^17 строк.
Не уверен, что во Вселенной столько атомов,
но пространства TEMP точно не хватит нигде.
Да и выполняться это будет сказочно долго.
📛 Поэтому в книгах по оптимизации справедливо пишут:
если в плане появился MERGE JOIN CARTESIAN,
скорее всего, вы забыли указать условие соединения.
У новичков такое бывает — пока рефлекс не выработался.
Но!
Я хочу поделиться своей идеей, которую придумал лет 5–6 назад.
В ней я осознанно и эффективно использую картезианское соединение.
Скорее всего, не я один, но приятно, что дошёл до этого сам.
Работа разработчика ведь часто состоит в том,
чтобы переосмысливать то, что увиденно, услышано, прочитано.
📊 Представьте, что вы читаете данные из таблицы A дважды или трижды,
но с разными условиями clause1, clause2,
а затем объединяете результаты.
Причём вам нужно рассчитать разные вычисляемые поля field1, field2
в зависимости от условий.
В выражения могут использоваться, и аналитические, и групповые функции.
Обычно это делают через UNION ALL:
🎯 Проблема в том, что таблица A должна считаться дважды (FULL ACCESS TABLE).
И по-другому вроде бы нельзя, ведь строки должны повторяться.
Попытка сделать всё в одном запросе через CASE не работает:
⚠️ Такой подход ошибочен,
потому что строки, подходящие под оба условия, обязаны дублироваться.
🧩 Чтобы не читать таблицу дважды, я придумал такой вариант:
В чём выигрыш?
Мы читаем таблицу всего один раз,
а затем просто умножаем её на 2 с помощью CROSS JOIN.
Для многих отчётных запросов это даёт отличную оптимизацию.
💬 Кто не знал про такой подход — пользуйтесь!
Пусть MERGE CARTESIAN будет вам не врагом, а инструментом ⚙️
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Хороший прием буду знать!
👎 Какая ерунда, честно слово!
💬 Как приятно читать Ваши комменты! Вы бы знали! 👇
#️⃣ #SQLOptimization #Cases #SQL #Oracle #PostgreSQL #PLSQL
или по-русски «Мыслю, следовательно, существую» - Рене Декарт
🚀 Серия: Оптимизация SQL-запросов
Тема: MERGE CARTESIAN — соединения таблиц
💡 Во всех источниках по оптимизации SQL можно встретить предупреждение:
если видите в плане строку MERGE JOIN CARTESIAN, — стоит насторожиться.
Почему?
Потому что это значит, что оптимизатор решил перемножить два набора данных,
то есть выполнить декартово произведение множеств.
Такой вид соединения вы гарантированно получите, если напишете, например:
SELECT * FROM a, b;
-- или
SELECT * FROM a CROSS JOIN b;
-- или
SELECT * FROM a INNER JOIN b ON (1 = 1);
📘 Результат этих запросов — это декартово произведение множеств A и B.
Если вдруг забыли, как это выглядит — вот пример:
A = {1, 2, 3}
B = {'A', 'B', 'C'}
A × B = {
{1,'A'},{1,'B'},{1,'C'},
{2,'A'},{2,'B'},{2,'C'},
{3,'A'},{3,'B'},{3,'C'}
}
То есть количество строк = 3 * 3 = 9.
🧠 А теперь представьте, что таблица A содержит 1 млрд строк,
а таблица B — 100 млн строк.
И вот уже результат — 10^17 строк.
Не уверен, что во Вселенной столько атомов,
но пространства TEMP точно не хватит нигде.
Да и выполняться это будет сказочно долго.
📛 Поэтому в книгах по оптимизации справедливо пишут:
если в плане появился MERGE JOIN CARTESIAN,
скорее всего, вы забыли указать условие соединения.
У новичков такое бывает — пока рефлекс не выработался.
Но!
Я хочу поделиться своей идеей, которую придумал лет 5–6 назад.
В ней я осознанно и эффективно использую картезианское соединение.
Скорее всего, не я один, но приятно, что дошёл до этого сам.
Работа разработчика ведь часто состоит в том,
чтобы переосмысливать то, что увиденно, услышано, прочитано.
📊 Представьте, что вы читаете данные из таблицы A дважды или трижды,
но с разными условиями clause1, clause2,
а затем объединяете результаты.
Причём вам нужно рассчитать разные вычисляемые поля field1, field2
в зависимости от условий.
В выражения могут использоваться, и аналитические, и групповые функции.
Обычно это делают через UNION ALL:
SELECT expression1 AS field1, expression2 AS field2
FROM a
WHERE clause1 = TRUE
UNION ALL
SELECT expression3 AS field1, expression4 AS field2
FROM a
WHERE clause2 = TRUE;
🎯 Проблема в том, что таблица A должна считаться дважды (FULL ACCESS TABLE).
И по-другому вроде бы нельзя, ведь строки должны повторяться.
Попытка сделать всё в одном запросе через CASE не работает:
SELECT
CASE WHEN clause1 THEN expression1 ELSE expression3 END AS field1,
CASE WHEN clause2 THEN expression2 ELSE expression4 END AS field2
FROM a
WHERE clause1 OR clause2;
⚠️ Такой подход ошибочен,
потому что строки, подходящие под оба условия, обязаны дублироваться.
🧩 Чтобы не читать таблицу дважды, я придумал такой вариант:
SELECT
CASE
WHEN c."case" = 1 AND clause1 THEN expression1
WHEN c."case" = 2 AND clause2 THEN expression2
END AS field1,
CASE
WHEN c."case" = 1 AND clause1 THEN expression3
WHEN c."case" = 2 AND clause2 THEN expression4
END AS field2
FROM a
CROSS JOIN (
SELECT 1 AS "case" FROM dual
UNION ALL
SELECT 2 AS "case" FROM dual
) c
WHERE clause1 OR clause2;
В чём выигрыш?
Мы читаем таблицу всего один раз,
а затем просто умножаем её на 2 с помощью CROSS JOIN.
Для многих отчётных запросов это даёт отличную оптимизацию.
💬 Кто не знал про такой подход — пользуйтесь!
Пусть MERGE CARTESIAN будет вам не врагом, а инструментом ⚙️
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
👍 Хороший прием буду знать!
👎 Какая ерунда, честно слово!
💬 Как приятно читать Ваши комменты! Вы бы знали! 👇
#️⃣ #SQLOptimization #Cases #SQL #Oracle #PostgreSQL #PLSQL
👍10❤3👎1
🎵 «Пусть нету ни кола и не двора,
Зато не платят королю налоги
Работники ножа и топора -
Романтики с большой дороги.» — Юрий Энтин
📌 Затравка для следующего поста про коллекции:
Анализируя "чужой" код, вижу неоднократно повторяющийся скрипт примерно с таким содержание.
Скрипт рабочий, но Вас ничего не смущает?
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Лучшая награда посту - это комменты! 👇
#Cases #SQL #Oracle #PLSQL
Зато не платят королю налоги
Работники ножа и топора -
Романтики с большой дороги.» — Юрий Энтин
📌 Затравка для следующего поста про коллекции:
Анализируя "чужой" код, вижу неоднократно повторяющийся скрипт примерно с таким содержание.
DECLARE
TYPE words_tab IS TABLE OF VARCHAR2 (100)
INDEX BY PLS_INTEGER;
words words_tab;
BEGIN
words (1) := 'Первый';
words (2) := 'Второй';
words (3) := 'Третий';
FOR index# IN 1 .. words.COUNT
LOOP
DBMS_OUTPUT.put_line ('index#=' || TO_CHAR (index#) || ' -> ' || words (index#));
END LOOP;
END;
/
Скрипт рабочий, но Вас ничего не смущает?
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Лучшая награда посту - это комменты! 👇
#Cases #SQL #Oracle #PLSQL
👍8🤔2❤1
Смотрите, предыдущий пост и голосуем...
Anonymous Poll
30%
Смущает расскажу в комментах.
17%
Всё норм - скрипт же рабочий.
30%
С коллекциями не работаю и потому не знаю.
22%
А что такое коллекция?
🚀 Хотите прокачать SQL, но базы под рукой нет?
У Славы Рожнева — автора курса SQL в Яндексе — есть два классных ресурса:
🔹 sqltest.online — задачи и тесты для практики
📢 Канал: t.me/sqltestonline
🔹 sqlize.online — онлайн-песочница для SQL-запросов прямо в браузере
📢 Канал: t.me/sqlize
✨ Отличный вариант, чтобы учиться и держать форму в любом месте и в любое время.
#FriendlyResources
У Славы Рожнева — автора курса SQL в Яндексе — есть два классных ресурса:
🔹 sqltest.online — задачи и тесты для практики
📢 Канал: t.me/sqltestonline
🔹 sqlize.online — онлайн-песочница для SQL-запросов прямо в браузере
📢 Канал: t.me/sqlize
✨ Отличный вариант, чтобы учиться и держать форму в любом месте и в любое время.
#FriendlyResources
🔥5❤1
🎵 «Нам лижут пятки языки костра.
За что же так не любят недотроги
Работников ножа и топора —
Романтиков с большой дороги?» — Юрий Энтин
📌 Разбор ситуации с ассоциативным массивом
Многие читатели обратили внимание, что демо код — не самый безопасный:
Дело в том, что ассоциативные массивы в Oracle не гарантируют плотность индексов.
Они могут иметь пропуски, ключи вовсе не обязаны начинаться с единицы, и элементы могут добавляться в любом порядке.
👉 COUNT — это количество элементов,
а не максимальный индекс.
💥 Поэтому диапазон 1 .. words.COUNT корректен только если индексы плотные (1,2,3…) и начинаются с 1.
Но в реальном проекте никто не мешает изменить коллекцию, и тогда неизбежно вылетает ошибка:
ORA-01403: no data found
Да, можно сказать: “А я слежу за тем, чтобы коллекция всегда начиналась с 1 и была плотной.”
Но при совместной разработке невозможно гарантировать, что все коллеги будут поддерживать вот это всё.
Именно поэтому код становится небезопасным.
👍 Вариант 1: использовать обычную TABLE
Nested table плотная по определению, и здесь цикл безопасен.
👍 Вариант 2: безопасная навигация по индексам FIRST/NEXT
Это гарантированный способ обхода любой неплотной коллекции.
🤔 Так в чём же прикол использовать TABLE OF вместо TABLE в таких плотных коллекциях?
Если коротко — ни в чём, если вам нужна плотная, предсказуемая коллекция.
INDEX BY — это гибкость, работа с “дырками” и ручное управление ключами.
TABLE — это надёжный массив с естественным, непрерывным диапазоном индексов.
Для большинства рабочих задач, где коллекция просто хранит список значений, удобнее и безопаснее использовать именно TABLE.
В качестве дополнения рекомендую статью на Habr: 📚 «Всё о коллекциях в Oracle».
Когда опыта было недостаточно, всегда использовал её как справочник.
👍 Хорошая тема для поста!
👎 Не годится никак!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Комменты иногда читать интереснее чем пост! 👇
#Cases #SQL #Oracle #PLSQL
За что же так не любят недотроги
Работников ножа и топора —
Романтиков с большой дороги?» — Юрий Энтин
📌 Разбор ситуации с ассоциативным массивом
Многие читатели обратили внимание, что демо код — не самый безопасный:
DECLARE
TYPE words_tab IS TABLE OF VARCHAR2(100)
INDEX BY PLS_INTEGER;
words words_tab;
BEGIN
words(1) := 'Первый';
words(2) := 'Второй';
words(3) := 'Третий';
FOR index# IN 1 .. words.COUNT LOOP
DBMS_OUTPUT.put_line('index#=' || index# || ' -> ' || words(index#));
END LOOP;
END;
/
Дело в том, что ассоциативные массивы в Oracle не гарантируют плотность индексов.
Они могут иметь пропуски, ключи вовсе не обязаны начинаться с единицы, и элементы могут добавляться в любом порядке.
👉 COUNT — это количество элементов,
а не максимальный индекс.
💥 Поэтому диапазон 1 .. words.COUNT корректен только если индексы плотные (1,2,3…) и начинаются с 1.
Но в реальном проекте никто не мешает изменить коллекцию, и тогда неизбежно вылетает ошибка:
ORA-01403: no data found
Да, можно сказать: “А я слежу за тем, чтобы коллекция всегда начиналась с 1 и была плотной.”
Но при совместной разработке невозможно гарантировать, что все коллеги будут поддерживать вот это всё.
Именно поэтому код становится небезопасным.
👍 Вариант 1: использовать обычную TABLE
DECLARE
TYPE words_tab IS TABLE OF VARCHAR2(100);
words words_tab := words_tab();
BEGIN
words.EXTEND; words(words.COUNT) := 'Первый';
words.EXTEND; words(words.COUNT) := 'Второй';
words.EXTEND; words(words.COUNT) := 'Третий';
FOR index# IN 1 .. words.COUNT LOOP
DBMS_OUTPUT.put_line('index#=' || index# || ' -> ' || words(index#));
END LOOP;
END;
/
Nested table плотная по определению, и здесь цикл безопасен.
👍 Вариант 2: безопасная навигация по индексам FIRST/NEXT
DECLARE
TYPE words_tab IS TABLE OF VARCHAR2(100)
INDEX BY PLS_INTEGER;
words words_tab;
index# PLS_INTEGER;
BEGIN
words(1) := 'Первый';
words(3) := 'Третий';
index# := words.FIRST;
WHILE index# IS NOT NULL LOOP
DBMS_OUTPUT.put_line(words(index#));
index# := words.NEXT(index#);
END LOOP;
END;
/
Это гарантированный способ обхода любой неплотной коллекции.
🤔 Так в чём же прикол использовать TABLE OF вместо TABLE в таких плотных коллекциях?
Если коротко — ни в чём, если вам нужна плотная, предсказуемая коллекция.
INDEX BY — это гибкость, работа с “дырками” и ручное управление ключами.
TABLE — это надёжный массив с естественным, непрерывным диапазоном индексов.
Для большинства рабочих задач, где коллекция просто хранит список значений, удобнее и безопаснее использовать именно TABLE.
В качестве дополнения рекомендую статью на Habr: 📚 «Всё о коллекциях в Oracle».
Когда опыта было недостаточно, всегда использовал её как справочник.
👍 Хорошая тема для поста!
👎 Не годится никак!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Комменты иногда читать интереснее чем пост! 👇
#Cases #SQL #Oracle #PLSQL
👍10👎1
🎵 «Закройте вашу книжку,
Допейте вашу чашку,
Дожуйте ваш дежурный бутерброд…
Снимите и продайте последнюю рубашку
И купите билет на пароход.
Ну что там ваши сводки, анкеты и доклады,
Когда повсюду рядом — там и тут —
Счастливые находки, таинственные клады…
Неужели так и пропадут?!»
— Юлий Ким
📌 Практическая "банковская" задача
Интересную задачу прислал мой друг (Спасибо тебе, Ирек! 🙌).
Решить её можно по-разному, и наши решения с ним заметно расходятся.
Любопытно было бы узнать ваш подход — такие задачки отлично тренируют мышление.
Допустим, есть таблица:
В таблице огромный объём данных — от 1 млн до 1 млрд строк.
А также есть входящая таблица:
Здесь данных может быть сколько угодно — от пусто до миллионов.
🎯 Требуется:
Для каждой записи tmp_orders найти thing при условиях:
1️⃣ things.id = tmp_orders.id
2️⃣ Если по tmp_orders.id данных нет — вернуть NULL
3️⃣ Для каждой tmp_orders.processed_date:
• выбрать первую запись справа, то есть processed_date <= saved_date
• если записей справа нет — взять первую запись слева, то есть saved_date < processed_date
💡 Решать можно любым способом, но результат должен быть именно в виде SQL-запроса:
(id, processed_date, thing)
👍 Хорошая задача!
👎 Так себе задача!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Как бы эту задачу решали Вы? 👇
#RealInterviewTasks #Cases #SQL #Oracle #PostgreSQL #PLSQL #PLpgSQL
Допейте вашу чашку,
Дожуйте ваш дежурный бутерброд…
Снимите и продайте последнюю рубашку
И купите билет на пароход.
Ну что там ваши сводки, анкеты и доклады,
Когда повсюду рядом — там и тут —
Счастливые находки, таинственные клады…
Неужели так и пропадут?!»
— Юлий Ким
📌 Практическая "банковская" задача
Интересную задачу прислал мой друг (Спасибо тебе, Ирек! 🙌).
Решить её можно по-разному, и наши решения с ним заметно расходятся.
Любопытно было бы узнать ваш подход — такие задачки отлично тренируют мышление.
Допустим, есть таблица:
CREATE TABLE things
(
id NUMBER,
saved_date DATE,
thing NUMBER
);
CREATE UNIQUE INDEX THINGS_U01
ON things (id, saved_date);
В таблице огромный объём данных — от 1 млн до 1 млрд строк.
А также есть входящая таблица:
CREATE TABLE tmp_orders (
id NUMBER,
processed_date DATE
);
Здесь данных может быть сколько угодно — от пусто до миллионов.
🎯 Требуется:
Для каждой записи tmp_orders найти thing при условиях:
1️⃣ things.id = tmp_orders.id
2️⃣ Если по tmp_orders.id данных нет — вернуть NULL
3️⃣ Для каждой tmp_orders.processed_date:
• выбрать первую запись справа, то есть processed_date <= saved_date
• если записей справа нет — взять первую запись слева, то есть saved_date < processed_date
💡 Решать можно любым способом, но результат должен быть именно в виде SQL-запроса:
(id, processed_date, thing)
👍 Хорошая задача!
👎 Так себе задача!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Как бы эту задачу решали Вы? 👇
#RealInterviewTasks #Cases #SQL #Oracle #PostgreSQL #PLSQL #PLpgSQL
👍10👎1
select_test.sql
21.7 KB
🎵 «Ах, знаю-знаю-знаю, порядок есть порядок
Все клады и находки учтены
Записаны в анкетах, отмечены в докладах
И законами все защищены
Они помогут делу, они послужат людям
Я знаю это, знаю наперёд
И значит мы не станем, и значит мы не будем
Покупать билет на пароход» — Юлий Ким
🔍 Доступ к временным данным или большая победа канала
Друзья, прежде всего хочу выразить благодарность Иреку Вафину за задачу и свой вариант решения,
а также читателю Алексею Онину за активное участие — и всем, кто заинтересовался задачей 🙌
Что сказать? Это тот редкий и любимый мной момент, когда комменты интереснее поста 😄
⭐ Были представлены несколько интересных вариантов решения
🟦 Вариант 1 — от Алексея Онина
Всё логично 👍 И coalesce здесь не случайно:
в отличие от NVL, он не трогает следующие значения, если текущее IS NOT NULL.
🟦 Вариант 2 — также от Алексея Онина
Тоже логично, но логика уже сложнее:
строим периоды [start_saved_date, end_saved_date] и ищем пересечение.
Проблема: нужно полностью прочитать things, что плохо для больших таблиц.
🟦 Вариант 3 — от Ирека Вафина
Очень интересный вариант через аналитику и сортировку.
Но проблема всё та же — полное чтение things + сортировка.
🟦 Вариант 4 — мой
Идея: использовать FFS или листовой доступ (FAST FULL INDEX SCAN).
Для этого создаём два метода:
🔹 get_previous_thing
🔹 get_next_thing
И затем:
Суть метода:
идём по листам индекса, достаём первый попавшийся элемент слева или справа.
🧪 Эксперимент
Заполняем:
things — 10 млн строк
tmp_orders — 10 тыс. строк
200k уникальных id × 50 записей в things
Все скрипты приложены к посту.
UPDATE 2025.11.23
В скрипте приложенном к посту есть ошибка (Спасибо Алексею Онину - заметил).
Исправленный скрипт в комментах.
Продолжение 👇
#RealInterviewTasks #Cases #SQL #Oracle #PLSQL
Все клады и находки учтены
Записаны в анкетах, отмечены в докладах
И законами все защищены
Они помогут делу, они послужат людям
Я знаю это, знаю наперёд
И значит мы не станем, и значит мы не будем
Покупать билет на пароход» — Юлий Ким
🔍 Доступ к временным данным или большая победа канала
Друзья, прежде всего хочу выразить благодарность Иреку Вафину за задачу и свой вариант решения,
а также читателю Алексею Онину за активное участие — и всем, кто заинтересовался задачей 🙌
Что сказать? Это тот редкий и любимый мной момент, когда комменты интереснее поста 😄
⭐ Были представлены несколько интересных вариантов решения
🟦 Вариант 1 — от Алексея Онина
select
o.id,
o.processed_date,
COALESCE(
(select
tr.thing
from things tr
where tr.id = o.id and o.processed_date <= tr.saved_date
order by tr.saved_date asc
fetch first 1 rows only
), (select
tl.thing
from things tl
where tl.id = o.id and tl.saved_date < o.processed_date
order by tl.saved_date desc
fetch first 1 rows only
)
) AS thing
from tmp_orders o;
Всё логично 👍 И coalesce здесь не случайно:
в отличие от NVL, он не трогает следующие значения, если текущее IS NOT NULL.
🟦 Вариант 2 — также от Алексея Онина
with tr as (
select
t.id,
coalesce(
lag(t.saved_date) over(partition by t.id order by t.saved_date ASC) + interval '1' second,
date '1900-01-01'
) as start_saved_date,
nvl2(
lead(t.saved_date) over(partition by t.id order by t.saved_date ASC),
t.saved_date,
DATE '9999-12-31'
) as end_saved_date,
t.thing
from things t
where t.id in (select o0.id from tmp_orders o0)
and t.saved_date is not null
)
select
o.id,
o.processed_date,
tr.thing
from tmp_orders o
left join tr
on tr.id = o.id
and o.processed_date between tr.start_saved_date and tr.end_saved_date;
Тоже логично, но логика уже сложнее:
строим периоды [start_saved_date, end_saved_date] и ищем пересечение.
Проблема: нужно полностью прочитать things, что плохо для больших таблиц.
🟦 Вариант 3 — от Ирека Вафина
SELECT --+ use_hash(o t)
o.id,
o.processed_date,
MAX(t.thing)
KEEP (DENSE_RANK FIRST
ORDER BY
CASE
WHEN o.processed_date <= t.saved_date THEN t.saved_date
ELSE NULL
END NULLS LAST,
t.saved_date DESC) AS thing
FROM tmp_orders o
LEFT OUTER JOIN things t ON t.id = o.id
GROUP BY o.Id, o.processed_date;
Очень интересный вариант через аналитику и сортировку.
Но проблема всё та же — полное чтение things + сортировка.
🟦 Вариант 4 — мой
Идея: использовать FFS или листовой доступ (FAST FULL INDEX SCAN).
Для этого создаём два метода:
🔹 get_previous_thing
CREATE OR REPLACE FUNCTION get_previous_thing (...)
...
🔹 get_next_thing
CREATE OR REPLACE FUNCTION get_next_thing (...)
...
И затем:
SELECT
tmp_o.id,
tmp_o.processed_date,
COALESCE(
get_next_thing (...),
get_previous_thing (...)
) AS thing
FROM tmp_orders tmp_o;
Суть метода:
идём по листам индекса, достаём первый попавшийся элемент слева или справа.
🧪 Эксперимент
Заполняем:
things — 10 млн строк
tmp_orders — 10 тыс. строк
200k уникальных id × 50 записей в things
Все скрипты приложены к посту.
UPDATE 2025.11.23
В скрипте приложенном к посту есть ошибка (Спасибо Алексею Онину - заметил).
Исправленный скрипт в комментах.
Продолжение 👇
#RealInterviewTasks #Cases #SQL #Oracle #PLSQL
👍1
Продолжение👆
🔍 Доступ к временным данным или большая победа канала
📊 Результаты
вариант 1
time: 0.002201 s, consistent gets: 250556, physical reads: 2, CPU: 149
вариант 2
time: 0.001823 s, consistent gets: 30506, physical reads: 30224, CPU: 171
вариант 3
time: 0.000877 s, consistent gets: 30491, physical reads: 30224, CPU: 82
вариант 4
time: 0.000311 s, consistent gets: 40247, physical reads: 2, CPU: 29
Все скрипты выполнялись на «разогретой» базе — блоки уже были в кэше.
Из-за этого результаты противоречивы:
Вариант 4 — самый быстрый,
но делает больше логических чтений (consistent gets).
Вариант 3 — читает меньше блоков,
но читает с диска, а не из кэша.
Выводы делайте сами!
👍 Отличный пост!
👎 Могло быть и лучше!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Как бы эту задачу решали Вы? 👇
#RealInterviewTasks #Cases #SQL #Oracle #PLSQL
🔍 Доступ к временным данным или большая победа канала
📊 Результаты
вариант 1
time: 0.002201 s, consistent gets: 250556, physical reads: 2, CPU: 149
вариант 2
time: 0.001823 s, consistent gets: 30506, physical reads: 30224, CPU: 171
вариант 3
time: 0.000877 s, consistent gets: 30491, physical reads: 30224, CPU: 82
вариант 4
time: 0.000311 s, consistent gets: 40247, physical reads: 2, CPU: 29
Все скрипты выполнялись на «разогретой» базе — блоки уже были в кэше.
Из-за этого результаты противоречивы:
Вариант 4 — самый быстрый,
но делает больше логических чтений (consistent gets).
Вариант 3 — читает меньше блоков,
но читает с диска, а не из кэша.
Выводы делайте сами!
👍 Отличный пост!
👎 Могло быть и лучше!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Как бы эту задачу решали Вы? 👇
#RealInterviewTasks #Cases #SQL #Oracle #PLSQL
👍8👎1
🔥 Затравка к следующему посту. В высоконагруженных системах нередко нужно быстро проверить, существует ли уникальная запись в таблице. Все варианты рабочие — но какой самый оптимальный вы используете?
Anonymous Poll
27%
1️⃣ SELECT ... INTO v_id; → EXCEPTION WHEN NO_DATA_FOUND
44%
2️⃣ SELECT CASE WHEN EXISTS(SELECT NULL FROM t WHERE ...) THEN 1 ELSE 0 END FROM DUAL
15%
3️⃣ SELECT MAX(id) INTO v_id FROM t WHERE ... → IF v_id > 0 THEN ...
9%
По сути без разницы!
5%
Мне всё равно!
select_test3.sql
15.6 KB
🎵 «Тот мужик поклоны бьет, отвечает вежливо:
"Я не вор, я не шпион, я вообще-то - дух,-
За свободу за мою - захотите ежели вы -
Изобью за вас любого, можно даже двух!"
Тут я понял: это - джинн,- он ведь может многое -
Он же может мне сказать: "Враз озолочу!"...
"Ваше предложение,- говорю,- убогое.
Морды будем после бить - я вина хочу!» — Владимир Высоцкий
🔍 Проверка существования уникальной записи, или «Развенчание мифов»
В предыдущем опросе приняло участие 47 подписчиков — всем большое спасибо за неравнодушие 🙌
Честно сказать, я никогда глубоко не исследовал эту тему.
По наивности думал, что разницы нет, ведь самое тяжёлое в БД — это чтение с диска.
Всё остальное так или иначе оптимизировано и работает приемлемо.
Но коллеги настаивали на своём подходе — и я решил проверить и убедиться сам.
🧪 Начинаем эксперимент
Создадим тестовую таблицу и заполним её данными.
id заполнил значениями от 1 до 1 млн, но только нечётными.
Итого получилось 500 тыс. записей.
Далее исследуем «мини»-трассировкой три варианта проверки существования строки по уникальному индексу.
В цикле — 10 млн итераций, id вычисляется случайно от 1 до 1 млн.
⚙️ Вариант 1 — через EXCEPTION NO_DATA_FOUND
⚙️ Вариант 2 — через EXISTS
От себя могу добавить: этот вариант я всегда считал самым логичным и читаемым.
Вообще люблю, когда код отражает логику:
проверяем существование → используем EXISTS. Логично! ✔️
⚙️ Вариант 3 — через MAX
📊 Результаты
Эксперимент проводился на «разогретой» базе — все блоки таблицы были в кэше
variant1_with_exception_no_data_found
ids_count = 4998770, non_ids_count = 5001230
time: 00:02:23.434
consistent gets: 30000000
CPU: 14262
variant1_with_exists
ids_count = 4997912, non_ids_count = 5002088
time: 00:02:00.054
consistent gets: 30000000
CPU: 11942
variant1_with_max
ids_count = 4999842, non_ids_count = 5000158
time: 00:02:03.955
consistent gets: 30000000
CPU: 12377
🏁 Итог
Самый оптимальный вариант — через EXISTS.
Он быстрее и по времени, и по использованному CPU.
Исходники теста в приложении!
А выводы… делайте сами!
👍 Интересно или неожиданно!
👎 Скучно или ожидаемо!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Хотите сказать - скажите!👇
#Cases #SQL #Oracle #PLSQL
"Я не вор, я не шпион, я вообще-то - дух,-
За свободу за мою - захотите ежели вы -
Изобью за вас любого, можно даже двух!"
Тут я понял: это - джинн,- он ведь может многое -
Он же может мне сказать: "Враз озолочу!"...
"Ваше предложение,- говорю,- убогое.
Морды будем после бить - я вина хочу!» — Владимир Высоцкий
🔍 Проверка существования уникальной записи, или «Развенчание мифов»
В предыдущем опросе приняло участие 47 подписчиков — всем большое спасибо за неравнодушие 🙌
Честно сказать, я никогда глубоко не исследовал эту тему.
По наивности думал, что разницы нет, ведь самое тяжёлое в БД — это чтение с диска.
Всё остальное так или иначе оптимизировано и работает приемлемо.
Но коллеги настаивали на своём подходе — и я решил проверить и убедиться сам.
🧪 Начинаем эксперимент
Создадим тестовую таблицу и заполним её данными.
id заполнил значениями от 1 до 1 млн, но только нечётными.
Итого получилось 500 тыс. записей.
CREATE TABLE test_2025_11_23
(
id NUMBER PRIMARY KEY,
email VARCHAR2(200)
);
INSERT /*+ APPEND */
INTO test_2025_11_23 (id, email)
SELECT a.id, a.email
FROM (
SELECT LEVEL AS id,
'user_' || TRUNC(DBMS_RANDOM.VALUE(1, POWER(10, 9))) || '@example.com' AS email
FROM DUAL
CONNECT BY LEVEL <= POWER(10, 6)
) a
WHERE MOD(a.id, 2) = 1;
COMMIT;
Далее исследуем «мини»-трассировкой три варианта проверки существования строки по уникальному индексу.
В цикле — 10 млн итераций, id вычисляется случайно от 1 до 1 млн.
⚙️ Вариант 1 — через EXCEPTION NO_DATA_FOUND
DECLARE
test_id test_2025_11_23.id%TYPE;
random_id test_2025_11_23.id%TYPE := TRUNC(DBMS_RANDOM.VALUE(1, POWER(10, 6)));
BEGIN
SELECT t.id
INTO test_id
FROM test_2025_11_23 t
WHERE t.id = get_count_ids.random_id;
ids_count := ids_count + 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
non_ids_count := non_ids_count + 1;
END;
⚙️ Вариант 2 — через EXISTS
От себя могу добавить: этот вариант я всегда считал самым логичным и читаемым.
Вообще люблю, когда код отражает логику:
проверяем существование → используем EXISTS. Логично! ✔️
DECLARE
is_exists_id VARCHAR2(1);
random_id test_2025_11_23.id%TYPE := TRUNC(DBMS_RANDOM.VALUE(1, POWER(10, 6)));
BEGIN
SELECT CASE
WHEN EXISTS (SELECT 1
FROM test_2025_11_23 t
WHERE t.id = get_count_ids.random_id)
THEN 'Y'
ELSE 'N'
END
INTO is_exists_id
FROM DUAL;
IF is_exists_id = 'Y' THEN
ids_count := ids_count + 1;
ELSE
non_ids_count := non_ids_count + 1;
END IF;
END;
⚙️ Вариант 3 — через MAX
DECLARE
id_count NUMBER;
random_id test_2025_11_23.id%TYPE := TRUNC(DBMS_RANDOM.VALUE(1, POWER(10, 6)));
BEGIN
SELECT MAX(t.id)
INTO id_count
FROM test_2025_11_23 t
WHERE t.id = get_count_ids.random_id;
IF id_count IS NOT NULL THEN
ids_count := ids_count + 1;
ELSE
non_ids_count := non_ids_count + 1;
END IF;
END;
📊 Результаты
Эксперимент проводился на «разогретой» базе — все блоки таблицы были в кэше
variant1_with_exception_no_data_found
ids_count = 4998770, non_ids_count = 5001230
time: 00:02:23.434
consistent gets: 30000000
CPU: 14262
variant1_with_exists
ids_count = 4997912, non_ids_count = 5002088
time: 00:02:00.054
consistent gets: 30000000
CPU: 11942
variant1_with_max
ids_count = 4999842, non_ids_count = 5000158
time: 00:02:03.955
consistent gets: 30000000
CPU: 12377
🏁 Итог
Самый оптимальный вариант — через EXISTS.
Он быстрее и по времени, и по использованному CPU.
Исходники теста в приложении!
А выводы… делайте сами!
👍 Интересно или неожиданно!
👎 Скучно или ожидаемо!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Хотите сказать - скажите!👇
#Cases #SQL #Oracle #PLSQL
👍7❤3👎1
🎵 «Глеб Жеглов и Володя Шарапов за столом засиделись не зря
Глеб Жеглов и Володя Шарапов ловят банду и главаря!
Расцвела буйным цветом малина, разухабилась разная тварь
Хлеба нет, а полно гуталина, да глумится горбатый главарь»
— Александр Шаганов
🕵️♂️ Детектив или просто интересная ошибка?
На днях получил задачу найти баг в коде. Задача оказалась нетривиальной: код незнакомый, пакет — больше 10 тысяч строк, ошибка — где-то внутри метода примерно на 1500 строк.
В общем, «развлечение» на четыре часа.
Но ошибка — интересная!
📌 Суть
Допустим, есть строка, в которой лежит форматированный SQL-запрос.
Запрос парсится на наличие подзапросов, которые сохраняются в коллекцию, а в исходной строке — временно заменяются.
Потом должны вернуться обратно.
Но что-то пошло не так.
Задача: выяснить, где ошибка, и предложить простой вариант исправления.
🧩 Код (публикую «как есть»)
📤 Результат
❓ Интересен ли вам такой формат или эта ошибка понравилась только мне?
👍 Формат интересен!
👎 Своих проблем хватает!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Есть что написать — пишите! 👇
#Cases #SQL #Oracle #PLSQL
Глеб Жеглов и Володя Шарапов ловят банду и главаря!
Расцвела буйным цветом малина, разухабилась разная тварь
Хлеба нет, а полно гуталина, да глумится горбатый главарь»
— Александр Шаганов
🕵️♂️ Детектив или просто интересная ошибка?
На днях получил задачу найти баг в коде. Задача оказалась нетривиальной: код незнакомый, пакет — больше 10 тысяч строк, ошибка — где-то внутри метода примерно на 1500 строк.
В общем, «развлечение» на четыре часа.
Но ошибка — интересная!
📌 Суть
Допустим, есть строка, в которой лежит форматированный SQL-запрос.
Запрос парсится на наличие подзапросов, которые сохраняются в коллекцию, а в исходной строке — временно заменяются.
Потом должны вернуться обратно.
Но что-то пошло не так.
Задача: выяснить, где ошибка, и предложить простой вариант исправления.
🧩 Код (публикую «как есть»)
DECLARE
str CLOB := q'{(/**/select1 (/**/select2 (/**/select4) ) (/**/select3) ) }';
i INTEGER := 0;
type lt_selects is table of varchar2(32000) index by pls_integer;
v_child_selects lt_selects;
BEGIN
loop
for rec in (
select
childs_select,
instr(lower(childs_select),'(/**/select') is_child
from (
select distinct substr(to_char(regexp_substr(str,'(+)\(([^()]*)\)',1,level)),1) childs_select
from dual
connect by level <= REGEXP_COUNT(str,'(+)\(([^()]*)\)')
)
)loop
i := i + 1;
str := replace(str,rec.childs_select,
'/*' || TO_CHAR(i) || '_child_start*/' ||
'@'|| TO_CHAR (i) ||'@' ||
'/*' || TO_CHAR (i) || '_child_end*/');
v_child_selects(v_child_selects.count+1) := rec.childs_select;
end loop;
if nvl(REGEXP_COUNT(str,'(+)\(([^()]*)\)'),0) = 0
then
exit;
end if;
end loop;
-- return subselects
if v_child_selects.count > 0
then
for i in 1..v_child_selects.count
loop
if instr(str,'@' || i || '@') > 0
then
str := replace(str,
'@'||i||'@',
v_child_selects(i)
);
end if;
end loop;
end if;
dbms_output.put_line ('str=' || str);
END;
📤 Результат
str=/*4_child_start*/(/**/select1 /*3_child_start*/@3@/*3_child_end*/ /*2_child_start*/@2@/*2_child_end*/ )/*4_child_end*/
❓ Интересен ли вам такой формат или эта ошибка понравилась только мне?
👍 Формат интересен!
👎 Своих проблем хватает!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Есть что написать — пишите! 👇
#Cases #SQL #Oracle #PLSQL
👍12🤯2👎1
DB developers channel
🎵 «Глеб Жеглов и Володя Шарапов за столом засиделись не зря Глеб Жеглов и Володя Шарапов ловят банду и главаря! Расцвела буйным цветом малина, разухабилась разная тварь Хлеба нет, а полно гуталина, да глумится горбатый главарь» — Александр Шаганов 🕵️♂️ Детектив…
🎵 «До утра не погаснет окошко —
Глеб Жеглов и Володя не спят.
Пресловутая «Чёрная кошка» забоится наших ребят!
Глеб Жеглов и Володя Шарапов заслужили в боях ордена —
После мирного дня трудового будь спокойна, родная страна!»
— Александр Шаганов
🕵️♂️ Детектив или просто интересная ошибка. Разбор
Вот такой legacy-код необходимо поддерживать. Руки чешутся всё переделать, но нельзя: при любом рефакторинге велик риск изменить логику — и тогда все труды обесценятся, а то и вовсе навредят.
А главное правило при разработке, как у врача, — «Не навреди!» ⚕️
Такой код у новичков вызывает шок, но, на самом деле, это весьма лайтовый вариант - бывает куда хуже!
Читатель Алексей Онин обладает поразительной наблюдательностью — и уже не раз это доказал.
Он правильно отметил, что ошибка в том, что в цикле коллекция наполняется подзапросами в прямом порядке:
от самого верхнего уровня до самого глубокого.
Чтобы подмену провести корректно, замену нужно делать в обратном порядке 🔄.
🎯 Задача: исправленный вариант.
🧩 Код (публикую «как есть»)
📤 Результат — ровно то, что и ожидалось:
Дополнительно я изменил проход по ассоциативному массиву. Мы уже подробно разбирали эту тему.
Объяснять, почему так, — не буду 😉.
👍 Хочу ещё разбор интересных ошибок!
👎 Давайте что-то другое!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Пишите комменты: не для меня или себя, а просто так! 👇
#Cases #SQL #Oracle #PLSQL
Глеб Жеглов и Володя не спят.
Пресловутая «Чёрная кошка» забоится наших ребят!
Глеб Жеглов и Володя Шарапов заслужили в боях ордена —
После мирного дня трудового будь спокойна, родная страна!»
— Александр Шаганов
🕵️♂️ Детектив или просто интересная ошибка. Разбор
Вот такой legacy-код необходимо поддерживать. Руки чешутся всё переделать, но нельзя: при любом рефакторинге велик риск изменить логику — и тогда все труды обесценятся, а то и вовсе навредят.
А главное правило при разработке, как у врача, — «Не навреди!» ⚕️
Такой код у новичков вызывает шок, но, на самом деле, это весьма лайтовый вариант - бывает куда хуже!
Читатель Алексей Онин обладает поразительной наблюдательностью — и уже не раз это доказал.
Он правильно отметил, что ошибка в том, что в цикле коллекция наполняется подзапросами в прямом порядке:
от самого верхнего уровня до самого глубокого.
Чтобы подмену провести корректно, замену нужно делать в обратном порядке 🔄.
🎯 Задача: исправленный вариант.
🧩 Код (публикую «как есть»)
DECLARE
str CLOB := q'{(/**/select1 (/**/select2 (/**/select4) ) (/**/select3) ) }';
i INTEGER := 0;
type lt_selects is table of varchar2(32000) index by pls_integer;
v_child_selects lt_selects;
BEGIN
loop
for rec in (
select
childs_select,
instr(lower(childs_select),'(/**/select') is_child
from (
select distinct substr(to_char(regexp_substr(str,'(+)\(([^()]*)\)',1,level)),1) childs_select
from dual
connect by level <= REGEXP_COUNT(str,'(+)\(([^()]*)\)')
)
)loop
i := i + 1;
dbms_output.put_line ('rec.childs_select=' || rec.childs_select || ',' || TO_CHAR (rec.is_child));
str := replace(str,rec.childs_select,
'/*' || TO_CHAR(i) || '_child_start*/' ||
'@'|| TO_CHAR (i) ||'@' ||
'/*' || TO_CHAR (i) || '_child_end*/');
v_child_selects(v_child_selects.count+1) := rec.childs_select;
end loop;
if nvl(REGEXP_COUNT(str,'(+)\(([^()]*)\)'),0) = 0
then
exit;
end if;
end loop;
IF v_child_selects.COUNT > 0
THEN
<<replace_subselects_back>>
DECLARE
index# PLS_INTEGER;
BEGIN
index# := v_child_selects.LAST; -- REVERSE is needed
WHILE index# IS NOT NULL
LOOP
IF INSTR (str, '@' || TO_CHAR (index#) || '@') > 0
THEN
str :=
REPLACE (str, '@' || TO_CHAR (index#) || '@', v_child_selects (index#));
END IF;
index# := v_child_selects.PRIOR (index#);
END LOOP;
END;
END IF;
DBMS_OUTPUT.put_line ('str=' || str);
END;
📤 Результат — ровно то, что и ожидалось:
str=/*4_child_start*/(/**/select1 /*3_child_start*/(/**/select2 /*1_child_start*/(/**/select4)/*1_child_end*/ )/*3_child_end*/ /*2_child_start*/(/**/select3)/*2_child_end*/ )/*4_child_end*/
Дополнительно я изменил проход по ассоциативному массиву. Мы уже подробно разбирали эту тему.
Объяснять, почему так, — не буду 😉.
👍 Хочу ещё разбор интересных ошибок!
👎 Давайте что-то другое!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Пишите комменты: не для меня или себя, а просто так! 👇
#Cases #SQL #Oracle #PLSQL
👍9👎1
Затравка для следующего поста. Знаете ли Вы как сделать дедлок внутри "одной" сессии?
Anonymous Poll
4%
Знаю и поделюсь в комментах.
25%
Знаю и не скажу.
62%
Не знаю.
9%
Мне плевать на дедлоки.
🤔3
DB developers channel
🎵 «До утра не погаснет окошко — Глеб Жеглов и Володя не спят. Пресловутая «Чёрная кошка» забоится наших ребят! Глеб Жеглов и Володя Шарапов заслужили в боях ордена — После мирного дня трудового будь спокойна, родная страна!» — Александр Шаганов 🕵️♂️ Детектив…
🎵 «Сколько лет прошло, всё о том же гудят провода
Всё того же ждут самолёты
Девочка с глазами из самого синего льда
Тает под огнём пулемёта
Должен же растаять хоть кто-то» — Александр Васильев
🔒 Deadlock в одной сессии? Да, это возможно!
Что «нефиг делать» для одних — то «не знаю» для других.
Такой вопрос мне частенько задавали устно на собеседованиях, и, наверное, поэтому я так хорошо его запомнил.
Обычно deadlock представляют как ситуацию, когда две разные сессии ждут друг друга.
Но Oracle умеет удивлять — словить deadlock можно даже внутри одной сессии.
Вот минимальный пример:
🧩 Что здесь происходит
Главный блок начинает транзакцию и ставит блокировку на строку t.id = 1.
Затем вызывается процедура update_t, помеченная как PRAGMA AUTONOMOUS_TRANSACTION — это новая независимая транзакция, работающая в рамках той же сессии.
Внутри автономной транзакции мы снова пытаемся обновить ту же строку t.id = 1.
Но строка уже заблокирована внешним UPDATE.
Oracle пытается получить блокировку… у самой себя.
Ждать сам себя он, конечно, не может — и возникает классический deadlock:
ORA-00060: deadlock detected while waiting for resource
🔍 Почему Oracle не ждёт сам себя?
Потому что автономная транзакция — это полноценная независимая транзакция со своим собственным контекстом блокировок (lock manager context).
Oracle видит две конкурирующие транзакции внутри одной сессии и прекращает ожидание, считая его заведомо бесперспективным.
🧠 Важный вывод
AUTONOMOUS_TRANSACTION — мощный инструмент,
но на практике оправдан, по моему опыту, только в двух случаях:
Логирование (особенно ошибок).
Выполнение DDL-операций как в «оболочке», чтобы скрыть неявный COMMIT для основной транзакции.
👍 Тема автономок интересна - хочу ещё!
👎 Тема автономок не интересна!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Если есть время - уделите 30 секунд комменту!👇
#Cases #SQL #Oracle #PLSQL
Всё того же ждут самолёты
Девочка с глазами из самого синего льда
Тает под огнём пулемёта
Должен же растаять хоть кто-то» — Александр Васильев
🔒 Deadlock в одной сессии? Да, это возможно!
Что «нефиг делать» для одних — то «не знаю» для других.
Такой вопрос мне частенько задавали устно на собеседованиях, и, наверное, поэтому я так хорошо его запомнил.
Обычно deadlock представляют как ситуацию, когда две разные сессии ждут друг друга.
Но Oracle умеет удивлять — словить deadlock можно даже внутри одной сессии.
Вот минимальный пример:
DECLARE
v_id t.id%TYPE := 1;
PROCEDURE update_t (id IN t.id%TYPE)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE t
SET t.something = 111
WHERE t.id = update_t.id;
COMMIT;
END update_t;
BEGIN
UPDATE t
SET t.something = 222
WHERE t.id = v_id;
update_t (id => v_id);
COMMIT;
END;
🧩 Что здесь происходит
Главный блок начинает транзакцию и ставит блокировку на строку t.id = 1.
Затем вызывается процедура update_t, помеченная как PRAGMA AUTONOMOUS_TRANSACTION — это новая независимая транзакция, работающая в рамках той же сессии.
Внутри автономной транзакции мы снова пытаемся обновить ту же строку t.id = 1.
Но строка уже заблокирована внешним UPDATE.
Oracle пытается получить блокировку… у самой себя.
Ждать сам себя он, конечно, не может — и возникает классический deadlock:
ORA-00060: deadlock detected while waiting for resource
🔍 Почему Oracle не ждёт сам себя?
Потому что автономная транзакция — это полноценная независимая транзакция со своим собственным контекстом блокировок (lock manager context).
Oracle видит две конкурирующие транзакции внутри одной сессии и прекращает ожидание, считая его заведомо бесперспективным.
🧠 Важный вывод
AUTONOMOUS_TRANSACTION — мощный инструмент,
но на практике оправдан, по моему опыту, только в двух случаях:
Логирование (особенно ошибок).
Выполнение DDL-операций как в «оболочке», чтобы скрыть неявный COMMIT для основной транзакции.
👍 Тема автономок интересна - хочу ещё!
👎 Тема автономок не интересна!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Если есть время - уделите 30 секунд комменту!👇
#Cases #SQL #Oracle #PLSQL
👍17❤2👎2
🎵 «У леса на опушке
Жила Зима в избушке.
Она снежки солила
В берёзовой кадушке.
Она сучила пряжу,
Она ткала холсты,
Ковала ледяные
Над реками мосты» — Сергей Островой
🧵 Автономка и DDL операции
По реакции видно, что тема автономных транзакций зашла 🔥
Не отпускаем её и посмотрим на другой важный аспект — неявный коммит в DDL.
Для этого возьмём такую задачу 👇
❓ Вопрос к вам
Как по-вашему, какое значение будет выведено в консоль?
И главное — почему именно такое?
👍 Славно, славно!
👎 Кошмар и ужас!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Каждый имеет право высказаться в рамках приличия!👇
#Cases #SQL #Oracle #PLSQL
Жила Зима в избушке.
Она снежки солила
В берёзовой кадушке.
Она сучила пряжу,
Она ткала холсты,
Ковала ледяные
Над реками мосты» — Сергей Островой
🧵 Автономка и DDL операции
По реакции видно, что тема автономных транзакций зашла 🔥
Не отпускаем её и посмотрим на другой важный аспект — неявный коммит в DDL.
Для этого возьмём такую задачу 👇
--DROP TABLE test_2025_12_01 PURGE;
CREATE TABLE test_2025_12_01
(
id NUMBER PRIMARY KEY,
something NUMBER
);
DECLARE
PROCEDURE update_execute_rollback (execute_ddl IN VARCHAR2)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE test_2025_12_01 t
SET t.something = NVL (t.something, 0) + 1
WHERE t.id = 1;
EXECUTE IMMEDIATE execute_ddl;
ROLLBACK;
END update_execute_rollback;
BEGIN
INSERT INTO test_2025_12_01 (id, something)
VALUES (1, 1);
COMMIT;
update_execute_rollback (
execute_ddl => q'{COMMENT ON TABLE test_2025_12_01 IS 'test_comment'}');
update_execute_rollback (
execute_ddl =>
q'{COMMENT ON COLUMN test_2025_12_01.id IS 'test_column_comment'}');
update_execute_rollback (
execute_ddl =>
q'{ALTER TABLE test_2025_12_01 ADD CONSTRAINT test_2025_12_01_chk01 CHECK (something > 0)}');
update_execute_rollback (
execute_ddl =>
q'{CREATE INDEX test_2025_12_01_i01 ON test_2025_12_01 (something)}');
<<print_something>>
DECLARE
something test_2025_12_01.something%TYPE;
BEGIN
SELECT t.something
INTO something
FROM test_2025_12_01 t
WHERE t.id = 1;
DBMS_OUTPUT.put_line ('something =' || TO_CHAR (something));
END print_something;
END;
❓ Вопрос к вам
Как по-вашему, какое значение будет выведено в консоль?
И главное — почему именно такое?
👍 Славно, славно!
👎 Кошмар и ужас!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Каждый имеет право высказаться в рамках приличия!👇
#Cases #SQL #Oracle #PLSQL
👍5👎3❤1👀1
🎵 «Вот и ко мне пришла беда —
стартер заел, —
Теперь уж это не езда,
а ерзанье.
И надо б выйти, подтолкнуть —
но прыти нет, —
Авось подъедет кто-нибудь
и вытянет». — Владимир Высоцкий
🎯 Практическая задача получения списка уникальных символов.
Видимо, под впечатлением от вариантов решения его прошлой задачи, читатель Ирек Вафин, по совместительству мой друг, прислал новую задачу.
Формулируется она очень просто: требуется из строкового поля, по всем строкам большой таблицы, проанализировать и получить список всех уникальных символов, которые могут встречаться в этом поле.
Умение решать такие задачи — ценный навык: детали могут быть разные, но по сути техник решения подобных задача несколько, и трудно придумать новый велосипед 🚲🙂.
Итак, формальная постановка:
есть большая таблица (от 1 млн до 1 млрд записей).
Необходимо получить список уникальных символов (порядок не важен):
Навскидку, возникает несколько вариантов решения, но таблица большая, и требуется найти способ оптимальный во всех смыслах.
Решать можно как процедурными методами, так и полностью внутри SQL.
👍 Задача моей мечты!
👎 Хорошо, что мне такая задача не попалась!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 С нетерпением жду ваши варианты решения!👇
#Cases #SQL #Oracle #PLSQL
стартер заел, —
Теперь уж это не езда,
а ерзанье.
И надо б выйти, подтолкнуть —
но прыти нет, —
Авось подъедет кто-нибудь
и вытянет». — Владимир Высоцкий
🎯 Практическая задача получения списка уникальных символов.
Видимо, под впечатлением от вариантов решения его прошлой задачи, читатель Ирек Вафин, по совместительству мой друг, прислал новую задачу.
Формулируется она очень просто: требуется из строкового поля, по всем строкам большой таблицы, проанализировать и получить список всех уникальных символов, которые могут встречаться в этом поле.
Умение решать такие задачи — ценный навык: детали могут быть разные, но по сути техник решения подобных задача несколько, и трудно придумать новый велосипед 🚲🙂.
Итак, формальная постановка:
есть большая таблица (от 1 млн до 1 млрд записей).
CREATE TABLE test_texts (
id NUMBER PRIMARY KEY,
text VARCHAR2(4000)
);
INSERT INTO test_texts (id, text)
VALUES (1, '11qqq%%11qq^^');
INSERT INTO test_texts (id, text)
VALUES (2, '7766&&^^qqEE11');
COMMIT;
Необходимо получить список уникальных символов (порядок не важен):
'1', 'q', '%', '^', '7', '6', '&', 'E'
Навскидку, возникает несколько вариантов решения, но таблица большая, и требуется найти способ оптимальный во всех смыслах.
Решать можно как процедурными методами, так и полностью внутри SQL.
👍 Задача моей мечты!
👎 Хорошо, что мне такая задача не попалась!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 С нетерпением жду ваши варианты решения!👇
#Cases #SQL #Oracle #PLSQL
👍12👎2❤1
2025_12_06_test.sql
28.9 KB
🎵 «Сам виноват — и слёзы лью, и охаю:
Попал в чужую колею глубокую.
Я цели намечал свои на выбор сам —
А вот теперь из колеи не выбраться.
Крутые скользкие края имеет эта колея.
Я кляну проложивших её — скоро лопнет терпенье моё —
И склоняю, как школьник плохой:
Колею, в колее, с колеей…» — Владимир Высоцкий
🔬 Эксперимент: как быстрее всего получить набор уникальных символов из больших текстов?
Как я измучился с этой задачей! Кто бы знал! 😅
— «Зачем тебе это надо?» — спрашивает меня моя любимая женушка.
— «Мне это надо! Мне это нравится!» — отвечаю я ей.
Спасибо всем, кто активно и пассивно участвовал 🙏
Всем, кто присылал свои варианты, — искреннее уважение и благодарность.
И тебе, Ирек, — за три дня беспокойной жизни — тоже скромное, но честное спасибо 😄
Итак… всем розовых слонов раздали 🐘 — начинаем!
🛠 Подготовим тестовую среду
Мы наполняем test_texts случайными данными:
1️⃣ Длина строки — от 1 до 1000.
(Почему не 4000? LISTAGG снова шалит. В каждом релизе Oracle её чинят, но баги — как тараканы — всегда возвращаются.)
2️⃣ Символы — случайные CHAR 1–60000 с пропуском суррогатов.
Получаются строки вроде такой:
賔ᩆऩ뷖⧕쟀ᕺྗ畄됖ֱ䌆䳻漽㕲萓麱䷃䅠쿬...
Надеюсь, мы случайно не призвали какого-нибудь демона из среднекитайского.
🧪 Тестируемые варианты
1️⃣ CONNECT BY LEVEL + SYS_GUID()
2️⃣ CONNECT BY LEVEL + DBMS_RANDOM.VALUE
3️⃣ CROSS APPLY + CONNECT BY
4️⃣ PL/SQL + ассоциативный массив
5️⃣ То же, но с оптимизацией через REPLACE
6️⃣ Рекурсивный WITH (от «великого и ужасного» Славы Рожнего —
как говорится, «старый богатырь ещё может завинтить свой измаильский штык!» ⚔️)
📦 Вариант 1 — SYS_GUID()
SYS_GUID() здесь выглядит тяжеловесно.
Честно говоря, напоминает «стрелять по воробьям из гаубицы».
И шанс переполнить TEMP очень велик.
📦 Вариант 2 — DBMS_RANDOM
То же, но чуть легче. TEMP всё равно в печали.
Продолжение 👇
#RealInterviewTasks #Cases #SQL #Oracle #PLSQL
Попал в чужую колею глубокую.
Я цели намечал свои на выбор сам —
А вот теперь из колеи не выбраться.
Крутые скользкие края имеет эта колея.
Я кляну проложивших её — скоро лопнет терпенье моё —
И склоняю, как школьник плохой:
Колею, в колее, с колеей…» — Владимир Высоцкий
🔬 Эксперимент: как быстрее всего получить набор уникальных символов из больших текстов?
Как я измучился с этой задачей! Кто бы знал! 😅
— «Зачем тебе это надо?» — спрашивает меня моя любимая женушка.
— «Мне это надо! Мне это нравится!» — отвечаю я ей.
Спасибо всем, кто активно и пассивно участвовал 🙏
Всем, кто присылал свои варианты, — искреннее уважение и благодарность.
И тебе, Ирек, — за три дня беспокойной жизни — тоже скромное, но честное спасибо 😄
Итак… всем розовых слонов раздали 🐘 — начинаем!
🛠 Подготовим тестовую среду
CREATE TABLE test_texts
(
id NUMBER PRIMARY KEY,
text VARCHAR2 (4000 CHAR)
);
CREATE TABLE test_chars
(
"CHAR" VARCHAR2 (1 CHAR)
);
DECLARE
current_max_id INTEGER;
BEGIN
FOR index# IN 1..100
LOOP
IF index# = 1 THEN
current_max_id := 0;
ELSE
current_max_id := current_max_id + POWER (10, 3);
END IF;
INSERT INTO test_texts (id, text)
WITH
generated_rows
AS
( SELECT current_max_id + LEVEL AS id, TRUNC (DBMS_RANDOM.VALUE (1, 1000)) AS text_length
FROM DUAL
CONNECT BY LEVEL <= POWER (10, 3)),
generated_random_text
AS
(
SELECT a.id, CHR(
CASE
WHEN a.random_char_number < 55296 THEN a.random_char_number
ELSE a.random_char_number + 2048 -- без суррогатов D800–DFFF
END
USING NCHAR_CS
) AS random_symbol
FROM(
SELECT gr.id, TRUNC(DBMS_RANDOM.VALUE(1, 60000)) AS random_char_number
FROM generated_rows gr
CONNECT BY PRIOR gr.id = gr.id
AND PRIOR DBMS_RANDOM.VALUE () IS NOT NULL
AND LEVEL <= gr.text_length
) a
)
SELECT grt.id,
LISTAGG (grt.random_symbol, '') WITHIN GROUP (ORDER BY NULL) AS text
FROM generated_random_text grt
GROUP BY grt.id;
END LOOP;
END;
Мы наполняем test_texts случайными данными:
1️⃣ Длина строки — от 1 до 1000.
(Почему не 4000? LISTAGG снова шалит. В каждом релизе Oracle её чинят, но баги — как тараканы — всегда возвращаются.)
2️⃣ Символы — случайные CHAR 1–60000 с пропуском суррогатов.
Получаются строки вроде такой:
賔ᩆऩ뷖⧕쟀ᕺྗ畄됖ֱ䌆䳻漽㕲萓麱䷃䅠쿬...
Надеюсь, мы случайно не призвали какого-нибудь демона из среднекитайского.
🧪 Тестируемые варианты
1️⃣ CONNECT BY LEVEL + SYS_GUID()
2️⃣ CONNECT BY LEVEL + DBMS_RANDOM.VALUE
3️⃣ CROSS APPLY + CONNECT BY
4️⃣ PL/SQL + ассоциативный массив
5️⃣ То же, но с оптимизацией через REPLACE
6️⃣ Рекурсивный WITH (от «великого и ужасного» Славы Рожнего —
как говорится, «старый богатырь ещё может завинтить свой измаильский штык!» ⚔️)
📦 Вариант 1 — SYS_GUID()
SELECT DISTINCT SUBSTR (tt.text, LEVEL, 1) AS "CHAR"
FROM test_texts tt
CONNECT BY LEVEL <= LENGTH (tt.text)
AND PRIOR tt.id = tt.id
AND PRIOR SYS_GUID () IS NOT NULL;
SYS_GUID() здесь выглядит тяжеловесно.
Честно говоря, напоминает «стрелять по воробьям из гаубицы».
И шанс переполнить TEMP очень велик.
📦 Вариант 2 — DBMS_RANDOM
SELECT DISTINCT SUBSTR (tt.text, LEVEL, 1) AS "CHAR"
FROM test_texts tt
CONNECT BY LEVEL <= LENGTH (tt.text)
AND PRIOR tt.id = tt.id
AND PRIOR DBMS_RANDOM.value () IS NOT NULL;
То же, но чуть легче. TEMP всё равно в печали.
Продолжение 👇
#RealInterviewTasks #Cases #SQL #Oracle #PLSQL
🔬 Эксперимент: как быстрее всего получить набор уникальных символов из больших текстов?. Продолжение
📦 Вариант 3 — CROSS APPLY
Красиво, логично, TEMP не бомбится.
Но не чемпион.
📦 Вариант 4 — Ассоциативный массив
Просто, надёжно, как автомат Калашникова.
📦 Вариант 5 — Ассоциативный массив + REPLACE
Хорош, когда набор символов ограничен.
В случае Unicode-зоопарка — разницы почти нет.
📦 Вариант 6 — Рекурсивный WITH
С самого начала подозрительный вариант.
И действительно — TEMP взрывается как дрожжевое тесто.
📊 Результаты
variant1 (SYS_GUID) 00:41:32 ❌
variant2 (RANDOM) 00:05:23 ❌
variant3 (CROSS APPLY) 00:04:22 ⚠️
variant4 (indexed table) 00:01:39 ⭐
variant5 (indexed + replace) 00:01:38 ⭐
variant6 (recursive with) ORA-01652 ❌ TEMP умер героем
🧠 Итог
❌ Варианты 1, 2, 6 — слишком прожорливы по TEMP.
⚠️ Вариант 3 — рабочий, но не лучший.
🏆 Варианты 4 и 5 — оптимальны.
Разницы почти нет, но если набор символов ограничен реальным алфавитом — REPLACE даёт небольшой бонус.
Все скрипты теста в приложенном файле.
🏁 Занавес
Я пошёл зализывать раны.
Надеюсь, «хитрый татарин» получил то, на что рассчитывал.
👍 Страшно интересно!
👎 Страшно скучно!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Вот хотя бы что-то напишите - я же старался!👇
#RealInterviewTasks #Cases #SQL #Oracle #PLSQL
📦 Вариант 3 — CROSS APPLY
SELECT DISTINCT c."CHAR"
FROM test_texts tt
CROSS APPLY (SELECT SUBSTR (tt.text, LEVEL, 1) AS "CHAR"
FROM DUAL
CONNECT BY LEVEL <= LENGTH (tt.text)) c;
Красиво, логично, TEMP не бомбится.
Но не чемпион.
📦 Вариант 4 — Ассоциативный массив
DECLARE
TYPE chars_tab IS TABLE OF BOOLEAN
INDEX BY VARCHAR2 (1 CHAR);
chars chars_tab;
char_index VARCHAR2 (1);
BEGIN
FOR texts IN (SELECT tt.text
FROM test_texts tt)
LOOP
FOR index# IN 1 .. LENGTH (texts.text)
LOOP
chars (SUBSTR (texts.text, index#, 1)) := TRUE;
END LOOP;
END LOOP;
chars.delete ();
EXCEPTION
WHEN OTHERS THEN
IF chars IS NOT NULL OR chars.COUNT > 0 THEN
chars.delete ();
END IF;
END;
Просто, надёжно, как автомат Калашникова.
📦 Вариант 5 — Ассоциативный массив + REPLACE
DECLARE
TYPE chars_tab IS TABLE OF BOOLEAN
INDEX BY VARCHAR2 (1 CHAR);
chars chars_tab;
replaced_text VARCHAR2 (4000 CHAR);
char_index VARCHAR2 (1 CHAR);
BEGIN
FOR texts IN (SELECT tt.text
FROM test_texts tt)
LOOP
char_index := chars.FIRST;
replaced_text := chars.NEXT (char_index);
WHILE char_index IS NOT NULL AND replaced_text IS NOT NULL
LOOP
replaced_text := REPLACE (replaced_text, char_index, NULL);
char_index := chars.NEXT (char_index);
END LOOP;
FOR index# IN 1 .. LENGTH (texts.text)
LOOP
chars (SUBSTR (texts.text, index#, 1)) := TRUE;
END LOOP;
END LOOP;
chars.delete ();
EXCEPTION
WHEN OTHERS THEN
IF chars IS NOT NULL OR chars.COUNT > 0 THEN
chars.delete ();
END IF;
END;
Хорош, когда набор символов ограничен.
В случае Unicode-зоопарка — разницы почти нет.
📦 Вариант 6 — Рекурсивный WITH
WITH
text_split ("CHAR", remaining_text)
AS
(SELECT SUBSTR (tt.text, 1, 1) AS "CHAR", SUBSTR (tt.text, 2) AS remaining_text
FROM test_texts tt
UNION ALL
SELECT SUBSTR (ts.remaining_text, 1, 1) AS "CHAR",
SUBSTR (ts.remaining_text, 2) AS remaining_text
FROM text_split ts
WHERE ts.remaining_text IS NOT NULL)
SELECT DISTINCT "CHAR"
FROM text_split;
С самого начала подозрительный вариант.
И действительно — TEMP взрывается как дрожжевое тесто.
📊 Результаты
variant1 (SYS_GUID) 00:41:32 ❌
variant2 (RANDOM) 00:05:23 ❌
variant3 (CROSS APPLY) 00:04:22 ⚠️
variant4 (indexed table) 00:01:39 ⭐
variant5 (indexed + replace) 00:01:38 ⭐
variant6 (recursive with) ORA-01652 ❌ TEMP умер героем
🧠 Итог
❌ Варианты 1, 2, 6 — слишком прожорливы по TEMP.
⚠️ Вариант 3 — рабочий, но не лучший.
🏆 Варианты 4 и 5 — оптимальны.
Разницы почти нет, но если набор символов ограничен реальным алфавитом — REPLACE даёт небольшой бонус.
Все скрипты теста в приложенном файле.
🏁 Занавес
Я пошёл зализывать раны.
Надеюсь, «хитрый татарин» получил то, на что рассчитывал.
👍 Страшно интересно!
👎 Страшно скучно!
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница.
💎 Поддержка канала⁉️.
💬 Вот хотя бы что-то напишите - я же старался!👇
#RealInterviewTasks #Cases #SQL #Oracle #PLSQL
👍11👎1