🔆«Тишина должна быть в библиотеке...» — Уральские пельмени
📌 Серия «Оптимизация SQL-запросов»
Этот пост — для тех, у кого возникают проблемы с пониманием плана запроса как такового.
Прежде чем разбирать конкретные планы запросов и их оптимизацию, давайте уясним, что такое план запроса.
Почему СУБД не всегда может гарантировать оптимальный план? И зачем нужны хинты (подсказки)?
📚 До изобретения баз данных человечество уже умело хранить и обрабатывать большие объёмы информации.
Где же? — спросите вы. Конечно же, в библиотеках!
Те, кто разрабатывал модели баз данных, имели опыт работы с библиотеками, и многие термины были заимствованы именно оттуда: индекс, ключевые слова и пр.
Аналогия с библиотекарем
Представим, что мы в самой большой библиотеке мира — Библиотеке Конгресса США в Вашингтоне.
Вы библиотекарь, и у вас нет никаких электронных устройств для помощи. Только:
📦 хранилище книг,
📑 индекс по названию,
✍️ индекс по автору,
🔑 индекс по ключевым словам.
Теперь рассмотрим примеры:
1️⃣ Самый простой запрос — «выдать все книги».
Подвозим вагоны и сгружаем в любом порядке.
Аналог: TABLE ACCESS FULL
2️⃣ Самый лёгкий запрос — найти книгу с известным местом хранения.
Просто идём и берём её, даже индекс не нужен.
Аналог: TABLE ACCESS BY ROWID
3️⃣ Запрос на книги автора — например, «Александр Сергеевич Пушкин».
Идём в индекс по авторам и получаем ссылки на его книги.
Аналог: INDEX RANGE SCAN (по диапазону).
Но что, если запрос будет таким:
автор пишет на индоевропейском языке,
фамилия начинается на «П»,
имя заканчивается на «р»,
жанр книги — сказка,
книга про рыбу.
Как быть?
Тут уже возникает несколько вариантов (планов) поиска:
искать авторов по первой букве,
искать книги по ключевым словам «сказка» и «рыба»,
пробовать разные комбинации,
или вовсе перебрать все книги.
👉 У вас уже 4 возможных плана запроса! И оптимизатору тоже приходится выбирать, какой путь будет наименее затратным.
⚙️ Оптимизатор:
каждому плану присваивает «стоимость»,
опирается на статистику (собранную заранее или из прошлых запросов),
иногда ошибается, если неправильно оценил путь поиска.
📊 Именно поэтому один и тот же запрос на разных базах может выполняться разными способами.
Если запрос сложный, нужно убедиться, что план не отличается от того, что был в тестовой среде.
Если отличается — применяем хинт или переписываем запрос, чтобы сделать его «понятнее» для оптимизатора.
💡 Главная мысль:
Чтобы понять, что такое план запроса, поставьте себя на место библиотекаря.
Продумайте, как бы вы искали данные вручную.
Именно так «думает» оптимизатор.
Дальше уже техника:
как подсказать оптимизатору, что он ошибается,
или как переписать запрос, чтобы сделать его проще для анализа.
✅ Итого:
План запроса — это последовательность шагов.
Набор приёмов ограничен и часто повторяется.
Главное — понимать логику поиска.
Надеюсь, с понятием «план запроса» мы разобрались.
Дальше будем разбирать конкретные шаги и реальные примеры.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — продолжаем!
👎 Палец вниз — скучно и неинтересно.
Если есть вопросы, задавайте.
Если Вы опытный и заметили ошибку и/или неточность пишите тоже.
Если же ни то, ни другое, то всё равно пишите. 👇
#️⃣ #SQLOptimization
📌 Серия «Оптимизация SQL-запросов»
Этот пост — для тех, у кого возникают проблемы с пониманием плана запроса как такового.
Прежде чем разбирать конкретные планы запросов и их оптимизацию, давайте уясним, что такое план запроса.
Почему СУБД не всегда может гарантировать оптимальный план? И зачем нужны хинты (подсказки)?
📚 До изобретения баз данных человечество уже умело хранить и обрабатывать большие объёмы информации.
Где же? — спросите вы. Конечно же, в библиотеках!
Те, кто разрабатывал модели баз данных, имели опыт работы с библиотеками, и многие термины были заимствованы именно оттуда: индекс, ключевые слова и пр.
Аналогия с библиотекарем
Представим, что мы в самой большой библиотеке мира — Библиотеке Конгресса США в Вашингтоне.
Вы библиотекарь, и у вас нет никаких электронных устройств для помощи. Только:
📦 хранилище книг,
📑 индекс по названию,
✍️ индекс по автору,
🔑 индекс по ключевым словам.
Теперь рассмотрим примеры:
1️⃣ Самый простой запрос — «выдать все книги».
Подвозим вагоны и сгружаем в любом порядке.
Аналог: TABLE ACCESS FULL
2️⃣ Самый лёгкий запрос — найти книгу с известным местом хранения.
Просто идём и берём её, даже индекс не нужен.
Аналог: TABLE ACCESS BY ROWID
3️⃣ Запрос на книги автора — например, «Александр Сергеевич Пушкин».
Идём в индекс по авторам и получаем ссылки на его книги.
Аналог: INDEX RANGE SCAN (по диапазону).
Но что, если запрос будет таким:
автор пишет на индоевропейском языке,
фамилия начинается на «П»,
имя заканчивается на «р»,
жанр книги — сказка,
книга про рыбу.
Как быть?
Тут уже возникает несколько вариантов (планов) поиска:
искать авторов по первой букве,
искать книги по ключевым словам «сказка» и «рыба»,
пробовать разные комбинации,
или вовсе перебрать все книги.
👉 У вас уже 4 возможных плана запроса! И оптимизатору тоже приходится выбирать, какой путь будет наименее затратным.
⚙️ Оптимизатор:
каждому плану присваивает «стоимость»,
опирается на статистику (собранную заранее или из прошлых запросов),
иногда ошибается, если неправильно оценил путь поиска.
📊 Именно поэтому один и тот же запрос на разных базах может выполняться разными способами.
Если запрос сложный, нужно убедиться, что план не отличается от того, что был в тестовой среде.
Если отличается — применяем хинт или переписываем запрос, чтобы сделать его «понятнее» для оптимизатора.
💡 Главная мысль:
Чтобы понять, что такое план запроса, поставьте себя на место библиотекаря.
Продумайте, как бы вы искали данные вручную.
Именно так «думает» оптимизатор.
Дальше уже техника:
как подсказать оптимизатору, что он ошибается,
или как переписать запрос, чтобы сделать его проще для анализа.
✅ Итого:
План запроса — это последовательность шагов.
Набор приёмов ограничен и часто повторяется.
Главное — понимать логику поиска.
Надеюсь, с понятием «план запроса» мы разобрались.
Дальше будем разбирать конкретные шаги и реальные примеры.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — продолжаем!
👎 Палец вниз — скучно и неинтересно.
Если есть вопросы, задавайте.
Если Вы опытный и заметили ошибку и/или неточность пишите тоже.
Если же ни то, ни другое, то всё равно пишите. 👇
#️⃣ #SQLOptimization
👍27
🎵 «Иду с дружком, гляжу — стоят
Они стояли молча в ряд
Они стояли молча в ряд
Их было восемь.
Со мною — нож, решил я: что ж
Меня так просто не возьмёшь.
Держитесь, гады! Держитесь, гады!» — Владимир Высоцкий
✨ Сегодня суббота — задачи разбирать будем позже.
А пока продолжаем рубрику «Полезные ресурсы».
🖥️ Тема выпуска: Телеграм-каналы и чаты
Поскольку я блогер, то волей-неволей приходится изучать «конкурирующие фирмы».
Каналы, как и люди, — все разные:
кто-то с отличными постами, но навязчивой рекламой;
кто-то — с классным оформлением, но скучным контентом.
📢 Уважаемые читатели!
Делюсь подборкой профильных каналов на русском языке,
а также списком чатов, которые я лично не читаю,
поэтому оценить их качество не берусь — чаты просто не моё 🙂
🔧 Каналы
https://t.me/dbbooks (16.3k 👨💻) — крутой ресурс с множеством профильных книг
https://t.me/seniorsql (15.8k) — образовательный канал с ненавязчивой рекламой
https://t.me/sql_ready (12.4k) — профильный канал с интересным оформлением
https://t.me/sqlquestions (10.2k) — канал с интересной подачей и умеренной рекламой
https://t.me/sqlacademyofficial (8.9k) — образовательный канал с приятной подачей
https://t.me/db_in_it (8.2k) — инфо-канал с хорошим дизайном и лёгкой рекламой
https://t.me/database_info (8.1k) — образовательный канал с аккуратным оформлением
https://t.me/sqlprofi (5.2k) — интересный канал с оригинальной подачей
https://t.me/oracle_dbd (3.2k) — полезный контент, но реклама назойлива
https://t.me/pg_guru (2.8k) — профильный канал по PostgreSQL
https://t.me/sql_oracle_databases (1.8k) — образовательный ресурс по SQL и Oracle
💬 Чаты
https://t.me/sql_beginner (4.8k)
https://t.me/PostgreSQL_1C_Linux (4.1k)
https://t.me/sql_ninja (4.1k)
https://t.me/oracle_ru (1.8k)
https://t.me/oracle_dba_ru (1.5k)
https://t.me/pg_probackup (564)
https://t.me/databaselabru (273)
https://t.me/postgresprochat (79)
📚 Предлагаю всем желающим ознакомиться и найти себе контент по душе.
💎 Поддержка канала⁉️
👨💻 Скорее всего, я что-то пропустил — какие интересные профильные ресурсы на русском знаете Вы? А может, есть классные источники на других языках? Делитесь в комментариях. 👇
#️⃣ #Tools
Они стояли молча в ряд
Они стояли молча в ряд
Их было восемь.
Со мною — нож, решил я: что ж
Меня так просто не возьмёшь.
Держитесь, гады! Держитесь, гады!» — Владимир Высоцкий
✨ Сегодня суббота — задачи разбирать будем позже.
А пока продолжаем рубрику «Полезные ресурсы».
🖥️ Тема выпуска: Телеграм-каналы и чаты
Поскольку я блогер, то волей-неволей приходится изучать «конкурирующие фирмы».
Каналы, как и люди, — все разные:
кто-то с отличными постами, но навязчивой рекламой;
кто-то — с классным оформлением, но скучным контентом.
📢 Уважаемые читатели!
Делюсь подборкой профильных каналов на русском языке,
а также списком чатов, которые я лично не читаю,
поэтому оценить их качество не берусь — чаты просто не моё 🙂
🔧 Каналы
https://t.me/dbbooks (16.3k 👨💻) — крутой ресурс с множеством профильных книг
https://t.me/seniorsql (15.8k) — образовательный канал с ненавязчивой рекламой
https://t.me/sql_ready (12.4k) — профильный канал с интересным оформлением
https://t.me/sqlquestions (10.2k) — канал с интересной подачей и умеренной рекламой
https://t.me/sqlacademyofficial (8.9k) — образовательный канал с приятной подачей
https://t.me/db_in_it (8.2k) — инфо-канал с хорошим дизайном и лёгкой рекламой
https://t.me/database_info (8.1k) — образовательный канал с аккуратным оформлением
https://t.me/sqlprofi (5.2k) — интересный канал с оригинальной подачей
https://t.me/oracle_dbd (3.2k) — полезный контент, но реклама назойлива
https://t.me/pg_guru (2.8k) — профильный канал по PostgreSQL
https://t.me/sql_oracle_databases (1.8k) — образовательный ресурс по SQL и Oracle
💬 Чаты
https://t.me/sql_beginner (4.8k)
https://t.me/PostgreSQL_1C_Linux (4.1k)
https://t.me/sql_ninja (4.1k)
https://t.me/oracle_ru (1.8k)
https://t.me/oracle_dba_ru (1.5k)
https://t.me/pg_probackup (564)
https://t.me/databaselabru (273)
https://t.me/postgresprochat (79)
📚 Предлагаю всем желающим ознакомиться и найти себе контент по душе.
💎 Поддержка канала⁉️
👨💻 Скорее всего, я что-то пропустил — какие интересные профильные ресурсы на русском знаете Вы? А может, есть классные источники на других языках? Делитесь в комментариях. 👇
#️⃣ #Tools
👍8🔥1
Затравка для следующего поста.😊 Знаете ли функцию, которая безопасно перекомпилирует инвалидные объекты🩼 всей базы Oracle в параллели🚀?
Anonymous Poll
26%
Да, знаю.
49%
Нет, не знаю.
26%
Инвалиды!? А что это?
🎵 «И посредине этого разгула
Я прошептал на ухо жениху -
И жениха, как будто ветром сдуло,-
Невеста, вон, рыдает наверху.» — Владимир Высоцкий
⚡ Накат и откат изменений в Oracle
Для управления изменениями в базе данных Oracle важно иметь чёткую стратегию наката и отката.
Ниже — модель, как это можно организовать на любой базе: тестовой или боевой.
🔹 Модель действий
1. Считаем количество инвалидных объектов до наката
2. Запускаем скрипт наката/отката изменений
3. Перекомпилируем всех "инвалидов"
4. Считаем количество инвалидов после наката и сравниваем
5. Если есть ошибки — протоколируем их в отдельный журнал, например: INVALID_ERRORS$TASK_NUMBER
📌 Эта модель подходит как для наката изменений, так и для отката.
Таблица фиксирует все текущие ошибки и позволяет легко отслеживать изменения.
🔹 Создание таблицы ошибок
🔹 Параллельная перекомпиляция объектов
После наката/отката рекомендуется использовать UTL_RECOMP для перекомпиляции всех объектов в параллели, чтобы база была в рабочем состоянии:
Вот ссылка на официальную документацию UTL_RECOMP.
🔹 Итог
Всегда фиксируйте текущее состояние базы перед накатом.
Используйте таблицу ошибок для логирования ошибок.
Имейте скрипт отката на случай непредвиденных проблем.
После наката/отката запускайте параллельную перекомпиляцию объектов, чтобы минимизировать INVALID.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — интересно!
👎 Палец вниз — мне это не нужно, и потому скучно!
Интересна Ваша обратная связь - пишите тут 👇.
#️⃣ #Cases #SQL #Oracle #PLSQL
Я прошептал на ухо жениху -
И жениха, как будто ветром сдуло,-
Невеста, вон, рыдает наверху.» — Владимир Высоцкий
⚡ Накат и откат изменений в Oracle
Для управления изменениями в базе данных Oracle важно иметь чёткую стратегию наката и отката.
Ниже — модель, как это можно организовать на любой базе: тестовой или боевой.
🔹 Модель действий
1. Считаем количество инвалидных объектов до наката
2. Запускаем скрипт наката/отката изменений
3. Перекомпилируем всех "инвалидов"
4. Считаем количество инвалидов после наката и сравниваем
5. Если есть ошибки — протоколируем их в отдельный журнал, например: INVALID_ERRORS$TASK_NUMBER
📌 Эта модель подходит как для наката изменений, так и для отката.
Таблица фиксирует все текущие ошибки и позволяет легко отслеживать изменения.
🔹 Создание таблицы ошибок
CREATE TABLE INVALID_ERRORS$TASK_NUMBER AS
SELECT
o.owner,
o.object_type,
o.object_name,
e.sequence,
e.line,
e.position,
e.text
FROM
dba_objects o
INNER JOIN
dba_errors e
ON o.object_name = e.name AND o.owner = e.owner
WHERE
o.status = 'INVALID'
AND o.object_type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'VIEW', 'TRIGGER')
ORDER BY
o.owner,
o.object_type,
o.object_name,
e.sequence;
🔹 Параллельная перекомпиляция объектов
После наката/отката рекомендуется использовать UTL_RECOMP для перекомпиляции всех объектов в параллели, чтобы база была в рабочем состоянии:
BEGIN
SYS.UTL_RECOMP.recomp_parallel(
threads => 4
);
END;
/
Вот ссылка на официальную документацию UTL_RECOMP.
🔹 Итог
Всегда фиксируйте текущее состояние базы перед накатом.
Используйте таблицу ошибок для логирования ошибок.
Имейте скрипт отката на случай непредвиденных проблем.
После наката/отката запускайте параллельную перекомпиляцию объектов, чтобы минимизировать INVALID.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — интересно!
👎 Палец вниз — мне это не нужно, и потому скучно!
Интересна Ваша обратная связь - пишите тут 👇.
#️⃣ #Cases #SQL #Oracle #PLSQL
👍9❤1👎1
🚀 Хотите прокачать SQL, но базы под рукой нет?
У моего коллеги Славы Рожнева — автора курса SQL в Яндексе — есть два ресурса:
🔹 sqltest.online — готовые задачи и тесты для практики.
🔹 sqlize.online — онлайн-песочница для запросов прямо в браузере.
📊 Отличный вариант, чтобы тренироваться в любом месте и в любое время!
У моего коллеги Славы Рожнева — автора курса SQL в Яндексе — есть два ресурса:
🔹 sqltest.online — готовые задачи и тесты для практики.
🔹 sqlize.online — онлайн-песочница для запросов прямо в браузере.
📊 Отличный вариант, чтобы тренироваться в любом месте и в любое время!
👍5🔥1
🎵 «Один говорил: "Нам свобода - награда:
Мы поезд, куда надо ведем".
Другой говорил: "Задаваться не надо.
Как сядем в него, так и сойдем".» — Андрей Макаревич
📌 Разбор задачи: Вычисление n-го числа Фибоначчи через динамическое программирование
Представьте себе, что вы строите лестницу чисел.
Каждый новый шаг — это сумма двух предыдущих.
И вот приходит администратор и говорит: «Посчитайте десятый Фибоначчи!»
Что делает скрипт?
1. Создаёт «лестницу» чисел (массив).
2. Заполняет первые два шага единицами.
3. Каждый следующий шаг — это сумма двух предыдущих.
4. В конце вы получаете нужное число и всю последовательность целиком.
💡 Как работает:
Первые два числа всегда 1.
Каждый новый элемент — это сумма двух предыдущих.
В итоге получаем как конкретное число, так и всю последовательность.
👍 Палец вверх — есть желание изучать алгоритмы.
👎 Палец вниз — хватит.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Что скажет "купечество"?
#️⃣ #SQL #Oracle #PLSQL
Мы поезд, куда надо ведем".
Другой говорил: "Задаваться не надо.
Как сядем в него, так и сойдем".» — Андрей Макаревич
📌 Разбор задачи: Вычисление n-го числа Фибоначчи через динамическое программирование
Представьте себе, что вы строите лестницу чисел.
Каждый новый шаг — это сумма двух предыдущих.
И вот приходит администратор и говорит: «Посчитайте десятый Фибоначчи!»
Что делает скрипт?
1. Создаёт «лестницу» чисел (массив).
2. Заполняет первые два шага единицами.
3. Каждый следующий шаг — это сумма двух предыдущих.
4. В конце вы получаете нужное число и всю последовательность целиком.
DECLARE
TYPE matrix_tab IS TABLE OF INTEGER;
fibMatrix matrix_tab := matrix_tab ();
fibonachiAmount INTEGER;
PROCEDURE get_fibonachi_matrix (index# IN INTEGER, matrix IN OUT NOCOPY matrix_tab, fibonachi_amount OUT INTEGER) IS
BEGIN
IF matrix.COUNT >= index# + 1 THEN
fibonachi_amount := matrix (index# + 1);
RETURN;
END IF;
IF index# >= 0 THEN
matrix.EXTEND;
matrix (matrix.COUNT) := 1;
IF index# = 0 THEN
fibonachi_amount := 1;
RETURN;
END IF;
END IF;
IF index# >= 1 THEN
matrix.EXTEND;
matrix (matrix.COUNT) := 1;
IF index# = 1 THEN
fibonachi_amount := 1;
RETURN;
END IF;
END IF;
FOR i IN 3 .. index# + 1 LOOP
matrix.EXTEND;
matrix (matrix.COUNT) := matrix (matrix.COUNT - 1) + matrix (matrix.COUNT - 2);
END LOOP;
fibonachi_amount := matrix (index# + 1);
END get_fibonachi_matrix;
PROCEDURE print_matrix (matrix IN OUT NOCOPY matrix_tab) IS
str VARCHAR2 (100);
BEGIN
FOR i IN 1 .. matrix.COUNT LOOP
str := str || ' ' || TO_CHAR (matrix (i));
END LOOP;
DBMS_OUTPUT.put_line (str);
END print_matrix;
BEGIN
get_fibonachi_matrix (index# => 10, matrix => fibMatrix, fibonachi_amount => fibonachiAmount);
DBMS_OUTPUT.put_line ('fibonachiAmount = ' || TO_CHAR (fibonachiAmount));
print_matrix (matrix => fibMatrix);
END;
💡 Как работает:
Первые два числа всегда 1.
Каждый новый элемент — это сумма двух предыдущих.
В итоге получаем как конкретное число, так и всю последовательность.
👍 Палец вверх — есть желание изучать алгоритмы.
👎 Палец вниз — хватит.
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
💬 Что скажет "купечество"?
#️⃣ #SQL #Oracle #PLSQL
👍7👎3
Продолжим или закончим 5 часть "Мерлезонского балета" - решение задач методами процедурного языка. Я выбрал очередные 5 задач из 5 тем: работа с массивами, работа со строками, работа с матрицами, структуры данных, динамическое программирование.
Anonymous Poll
11%
Сжатие строки: Выполните базовое сжатие строки, используя счётчик повторяющихся символов.
4%
Максимальная разница: Найдите максимальный абсолютный модуль разности между 2 соседними элементами в
7%
Сапёр: В матрице сапёра, где -1 - бомба, а 0 — пустая, вычислите кол-во бомб, смежных с каждой.
7%
Проверка на BST: Проверьте, является ли бинарное дерево деревом поиска (Binary Search Tree).
4%
Разбиение на слова: Дана строка и словарь слов. Разбейте строку на слова из словаря.
14%
Я разработчик DB. Всё остальное лишнее.
54%
Мне интересно всё.
👍3
Опрос — где вы определяете константы в PL/SQL-проектах?
Anonymous Poll
61%
Определяю константы в тех же спецификациях пакетов, что и методы.
18%
Нет. Держу константы в отдельном пакете.
21%
Что это — «константы в пакете»? Хочу пояснения.
🎵 «А я иду, шагаю по Москве,
И я ещё пройти смогу —
Солёный Тихий океан
И тундру, и тайгу…» — Сергей Никитин
📌 Разбор задачи: Разделение строки на слова
Иногда жизнь — как длинная дорога,
и каждое слово в строке — это шаг.
Одни шаги короткие, другие — длинные,
а между ними бывают остановки — пробелы.
🧠 Что делает скрипт?
1. Берёт исходную строку с кучей пробелов: ' 1 123 333 4567 '.
2. Проходит по ней символ за символом, как по дороге.
3. Когда встречает пробел — понимает: «О! шаг закончился».
4. Запоминает всё, что было между пробелами, — это и есть слово.
5. В конце выводит, сколько «шагов» пройдено и какие именно.
💡 Как работает:
Код идёт по строке, как путешественник по маршруту.
Каждый пробел — это остановка,
а всё между остановками — очередное слово.
Из ' 1 123 333 4567 ' получается маршрут:
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — если есть контакт
👎 Палец вниз — если это "сало" достало.
💬 Пишите, и Ваше слово будет прочитано! 👇.
#️⃣ #SQL #Oracle #PLSQL
И я ещё пройти смогу —
Солёный Тихий океан
И тундру, и тайгу…» — Сергей Никитин
📌 Разбор задачи: Разделение строки на слова
Иногда жизнь — как длинная дорога,
и каждое слово в строке — это шаг.
Одни шаги короткие, другие — длинные,
а между ними бывают остановки — пробелы.
🧠 Что делает скрипт?
1. Берёт исходную строку с кучей пробелов: ' 1 123 333 4567 '.
2. Проходит по ней символ за символом, как по дороге.
3. Когда встречает пробел — понимает: «О! шаг закончился».
4. Запоминает всё, что было между пробелами, — это и есть слово.
5. В конце выводит, сколько «шагов» пройдено и какие именно.
DECLARE
str VARCHAR2 (4000) := ' 1 123 333 4567 ';
TYPE str_tab IS TABLE OF VARCHAR2 (4000);
strs str_tab := str_tab ();
indexStartWord INTEGER := 0;
BEGIN
FOR index# IN 1 .. LENGTH (str)
LOOP
IF SUBSTR (str, index#, 1) = ' ' AND indexStartWord > 0
THEN
--DBMS_OUTPUT.put_line ('HERE');
strs.EXTEND;
strs (strs.COUNT) :=
SUBSTR (str, indexStartWord, index# - indexStartWord);
indexStartWord := 0;
END IF;
IF SUBSTR (str, index#, 1) != ' '
AND SUBSTR (str, index# - 1, 1) = ' '
THEN
indexStartWord := index#;
END IF;
--DBMS_OUTPUT.put_line (TO_CHAR (indexStartWord));
END LOOP;
-- print result
DBMS_OUTPUT.put_line (' ');
DBMS_OUTPUT.put_line ('word amount = ' || TO_CHAR (strs.COUNT));
FOR i IN 1 .. strs.COUNT
LOOP
DBMS_OUTPUT.put_line (strs (i));
END LOOP;
strs.delete;
END;
💡 Как работает:
Код идёт по строке, как путешественник по маршруту.
Каждый пробел — это остановка,
а всё между остановками — очередное слово.
Из ' 1 123 333 4567 ' получается маршрут:
1
123
333
4567
⚠️ Хотите проверить скрипты, но нет базы под рукой - онлайн-песочница
💎 Поддержка канала⁉️
👍 Палец вверх — если есть контакт
👎 Палец вниз — если это "сало" достало.
💬 Пишите, и Ваше слово будет прочитано! 👇.
#️⃣ #SQL #Oracle #PLSQL
👍9❤1👎1
🎵 «А не спеть ли мне песню о любви
А не выдумать ли новый жанр
Попопсовей мотив и стихи
И всю жизнь получать гонорар» — Чиж
📚 Серия «Оптимизация SQL-запросов». Планы запросов (одна таблица): часть 1
Продолжим аналогию с библиотекой — ведь она интуитивно понятна.
Я создал таблицу BOOKS, спроектированную весьма плохо — и это намеренно.
Зачем? Чтобы показать, как работает оптимизатор и какие бывают планы выполнения запросов при обращении к одной таблице.
📖 Таблица денормализована, индексов слишком много, констрейнов нет — всё специально.
Цель — рассмотреть, как Oracle строит планы на одной таблице.
🔍 Что у нас есть
Мы — библиотекарь (оптимизатор).
Перед нами хранилище из миллиарда книг 📦
Созданы индексы:
Смотрите, в ORACLE много различных индексов и все они так или иначе применяются, но база это B-tree индексы (B - это не значит бинарный, а значит сбалансированный).
Абсолютное большинство индексов, которые Вы будете встречать, это нормальные B-tree индексы.
Экзотика полезна, но её надо изучать в контексте.
Заполним таблицу тестовыми данными:
#️⃣ #SQLOptimization #SQL #Oracle
А не выдумать ли новый жанр
Попопсовей мотив и стихи
И всю жизнь получать гонорар» — Чиж
📚 Серия «Оптимизация SQL-запросов». Планы запросов (одна таблица): часть 1
Продолжим аналогию с библиотекой — ведь она интуитивно понятна.
Я создал таблицу BOOKS, спроектированную весьма плохо — и это намеренно.
Зачем? Чтобы показать, как работает оптимизатор и какие бывают планы выполнения запросов при обращении к одной таблице.
CREATE TABLE books (
inventory_number NUMBER NOT NULL,
book_name VARCHAR2(500) NOT NULL,
language VARCHAR2(100) NOT NULL,
book_genre VARCHAR2(100) NOT NULL,
author_fullname VARCHAR2(100),
author_firstname VARCHAR2(100),
author_lastname VARCHAR2(100),
author_birthday DATE,
status_book VARCHAR2(50),
book_text CLOB NOT NULL
);
📖 Таблица денормализована, индексов слишком много, констрейнов нет — всё специально.
Цель — рассмотреть, как Oracle строит планы на одной таблице.
🔍 Что у нас есть
Мы — библиотекарь (оптимизатор).
Перед нами хранилище из миллиарда книг 📦
Созданы индексы:
CREATE UNIQUE INDEX books_u01 ON books (inventory_number); -- уникальный номер книги
CREATE INDEX books_i01 ON books (book_name); -- по названию
CREATE INDEX books_i02 ON books (language); -- по языку
CREATE INDEX books_i03 ON books (book_genre); -- по жанру
CREATE INDEX books_i04 ON books (book_name, book_genre, language); -- составной
CREATE INDEX books_i05 ON books (author_fullname); -- по ФИО автора
CREATE INDEX books_i06 ON books (author_lastname, author_firstname); -- по фамилии и имени
CREATE INDEX books_i07 ON books (author_birthday); -- по дате рождения
CREATE INDEX books_i08 ON books (status_book); -- по статусу (Available, Checked Out, Reserved, Repaired)
Смотрите, в ORACLE много различных индексов и все они так или иначе применяются, но база это B-tree индексы (B - это не значит бинарный, а значит сбалансированный).
Абсолютное большинство индексов, которые Вы будете встречать, это нормальные B-tree индексы.
Экзотика полезна, но её надо изучать в контексте.
Заполним таблицу тестовыми данными:
INSERT ALL
INTO books (inventory_number, book_name, language, book_genre, author_fullname, author_firstname, author_lastname, author_birthday, status_book, book_text)
VALUES (1, 'War and Peace', 'English', 'Historical Novel', 'Leo Tolstoy', 'Leo', 'Tolstoy',
TO_DATE('09-09-1828', 'DD-MM-YYYY'), 'Available',
'Long philosophical novel about war and human destiny.')
INTO books VALUES (2, 'Crime and Punishment', 'English', 'Psychological Fiction',
'Fyodor Dostoevsky', 'Fyodor', 'Dostoevsky',
TO_DATE('11-11-1821', 'DD-MM-YYYY'), 'Checked Out',
'Story of guilt, morality, and redemption.')
INTO books VALUES (3, 'Pride and Prejudice', 'English', 'Romance',
'Jane Austen', 'Jane', 'Austen',
TO_DATE('16-12-1775', 'DD-MM-YYYY'), 'Available',
'A classic story about manners, marriage, and social class.')
INTO books VALUES (4, 'Les Misérables', 'French', 'Historical Novel',
'Victor Hugo', 'Victor', 'Hugo',
TO_DATE('26-02-1802', 'DD-MM-YYYY'), 'Reserved',
'Epic tale of injustice, revolution, and redemption.')
INTO books VALUES (5, 'Faust', 'German', 'Tragedy',
'Johann Wolfgang von Goethe', 'Johann', 'Goethe',
TO_DATE('28-08-1749', 'DD-MM-YYYY'), 'Available',
'A scholar makes a pact with the devil in search of knowledge.')
INTO books VALUES (6, 'Don Quixote', 'Spanish', 'Adventure',
'Miguel de Cervantes', 'Miguel', 'Cervantes',
TO_DATE('29-09-1547', 'DD-MM-YYYY'), 'Checked Out',
'A nobleman loses his sanity and becomes a wandering knight.')
SELECT * FROM dual;
#️⃣ #SQLOptimization #SQL #Oracle
📚 Планы запросов (одна таблица): часть 2
1️⃣ Полное чтение таблицы
📄 План: TABLE ACCESS FULL — читаем всю таблицу от начала до конца.
Oracle оценивает объём, строки и стоимость выполнения (Cost = 3).
💡 Если нет статистики — оптимизатор делает dynamic sampling.
Чтобы помочь ему, собираем статистику:
После этого оценки становятся точнее - 6 записей против 1.
2️⃣ Покрытые запросы (INDEX FULL SCAN)
Нужно получить все инвентарные номера:
SELECT inventory_number FROM books;
📈 Oracle использует индекс BOOKS_U01, не трогая таблицу.
Такой запрос называют покрытым — все нужные данные уже есть в индексе.
⚠️ Поэтому избегайте SELECT * — всегда указывайте только нужные колонки!
3️⃣ Самый быстрый запрос 🚀
🪶 ROWID хранит физическое местоположение строки.
Oracle идёт прямо к нужному блоку, минуя индексы.
#️⃣ #SQLOptimization #SQL #Oracle
1️⃣ Полное чтение таблицы
SELECT * FROM books;
Plan Hash Value : 2688610195
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2563 | 3 | 00:00:01 |
| 1 | TABLE ACCESS FULL | BOOKS | 1 | 2563 | 3 | 00:00:01 |
---------------------------------------------------------------------
Notes
-----
- Dynamic sampling used for this statement ( level = 2 )
📄 План: TABLE ACCESS FULL — читаем всю таблицу от начала до конца.
Oracle оценивает объём, строки и стоимость выполнения (Cost = 3).
💡 Если нет статистики — оптимизатор делает dynamic sampling.
Чтобы помочь ему, собираем статистику:
BEGIN
DBMS_STATS.GATHER_TABLE_STATS (USER, 'BOOKS');
END;
После этого оценки становятся точнее - 6 записей против 1.
Plan Hash Value : 2688610195
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 2928 | 3 | 00:00:01 |
| 1 | TABLE ACCESS FULL | BOOKS | 6 | 2928 | 3 | 00:00:01 |
---------------------------------------------------------------------
2️⃣ Покрытые запросы (INDEX FULL SCAN)
Нужно получить все инвентарные номера:
SELECT inventory_number FROM books;
📈 Oracle использует индекс BOOKS_U01, не трогая таблицу.
Такой запрос называют покрытым — все нужные данные уже есть в индексе.
⚠️ Поэтому избегайте SELECT * — всегда указывайте только нужные колонки!
3️⃣ Самый быстрый запрос 🚀
SELECT * FROM books WHERE rowid = ...;
Plan Hash Value : 2667597667
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 244 | 1 | 00:00:01 |
| 1 | TABLE ACCESS BY USER ROWID | BOOKS | 1 | 244 | 1 | 00:00:01 |
------------------------------------------------------------------------------
🪶 ROWID хранит физическое местоположение строки.
Oracle идёт прямо к нужному блоку, минуя индексы.
#️⃣ #SQLOptimization #SQL #Oracle
❤2
📚 Планы запросов (одна таблица): часть 3
4️⃣ Доступ по уникальному индексу
📘 План показывает:
INDEX UNIQUE SCAN по BOOKS_U01
затем TABLE ACCESS BY INDEX ROWID
То есть сначала Oracle ищет ROWID в индексе, потом берёт саму книгу.
5️⃣ Доступ по неуникальному индексу
🧭 План: INDEX RANGE SCAN
Даже если значение одно, Oracle предполагает, что книг с таким названием может быть несколько.
💬 Разница между INDEX UNIQUE SCAN и INDEX RANGE SCAN колоссальная по скорости.
Если можете писать точные фильтры по уникальному индексу — делайте это!
Пример:
Результат этих запросов идентичен, но план запросов по скорости отличается "как небо и земля".
В одном случае будет INDEX UNIQUE SCAN, в другом INDEX RANGE SCAN.
Если используется INDEX UNIQUE SCAN — это значительно быстрее, чем диапазонное условие.
6️⃣ Доступ по неполному индексу
SELECT * FROM books
WHERE language = 'Russian' AND book_genre = 'Novel';
📊 План: INDEX SKIP SCAN
Oracle использует составной индекс (book_name, book_genre, language),
пропуская первое поле (book_name).
⚠️ Если видите в плане INDEX SKIP SCAN, это сигнал проверить:
Правильные ли созданы индексы на таблице?
Не в том ли порядке созданы поля составного индекса?
7⃣ Напоследок: есть ещё INDEX FAST FULL SCAN,
но о нём как-нибудь в другой раз. Это уже разбор не для новичков.
#️⃣ #SQLOptimization #SQL #Oracle
4️⃣ Доступ по уникальному индексу
SELECT * FROM books WHERE inventory_number = 1;
Plan Hash Value : 6300684
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 244 | 1 | 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | BOOKS | 1 | 244 | 1 | 00:00:01 |
| * 2 | INDEX UNIQUE SCAN | BOOKS_U01 | 1 | | 0 | 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 2 - access("INVENTORY_NUMBER"=1)
📘 План показывает:
INDEX UNIQUE SCAN по BOOKS_U01
затем TABLE ACCESS BY INDEX ROWID
То есть сначала Oracle ищет ROWID в индексе, потом берёт саму книгу.
5️⃣ Доступ по неуникальному индексу
SELECT * FROM books WHERE book_name = 'War and Peace';
Plan Hash Value : 480843298
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 244 | 2 | 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED | BOOKS | 1 | 244 | 2 | 00:00:01 |
| * 2 | INDEX RANGE SCAN | BOOKS_I01 | 1 | | 1 | 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 2 - access("BOOK_NAME"='War and Peace')
🧭 План: INDEX RANGE SCAN
Даже если значение одно, Oracle предполагает, что книг с таким названием может быть несколько.
💬 Разница между INDEX UNIQUE SCAN и INDEX RANGE SCAN колоссальная по скорости.
Если можете писать точные фильтры по уникальному индексу — делайте это!
Пример:
SELECT * FROM books
WHERE inventory_number IN (1, 2, 3);
SELECT * FROM books
WHERE inventory_number >= 1 AND inventory_number <= 3;
Результат этих запросов идентичен, но план запросов по скорости отличается "как небо и земля".
В одном случае будет INDEX UNIQUE SCAN, в другом INDEX RANGE SCAN.
Если используется INDEX UNIQUE SCAN — это значительно быстрее, чем диапазонное условие.
6️⃣ Доступ по неполному индексу
SELECT * FROM books
WHERE language = 'Russian' AND book_genre = 'Novel';
Plan Hash Value : 1417276700
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 244 | 2 | 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED | BOOKS | 1 | 244 | 2 | 00:00:01 |
| * 2 | INDEX SKIP SCAN | BOOKS_I04 | 1 | | 1 | 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 2 - access("BOOK_GENRE"='Novel' AND "LANGUAGE"='Russian')
* 2 - filter("LANGUAGE"='Russian' AND "BOOK_GENRE"='Novel')
📊 План: INDEX SKIP SCAN
Oracle использует составной индекс (book_name, book_genre, language),
пропуская первое поле (book_name).
⚠️ Если видите в плане INDEX SKIP SCAN, это сигнал проверить:
Правильные ли созданы индексы на таблице?
Не в том ли порядке созданы поля составного индекса?
7⃣ Напоследок: есть ещё INDEX FAST FULL SCAN,
но о нём как-нибудь в другой раз. Это уже разбор не для новичков.
#️⃣ #SQLOptimization #SQL #Oracle
👍2
📚 Планы запросов (одна таблица): часть 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