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

📢 Ну вот я и блогер.
А как любой порядочный блогер, я обязан написать правила и начать "клянить" деньги.

📜 Правила:

1️⃣ Все подписчики достойны уважения вне зависимости от пола, возраста, национальности, опыта и прочего.
Проявление неуважения к себе, к автору канала или другим подписчикам — недопустимо.

Всё! Правила закончились.
Поймите меня правильно, я люблю, когда люди шутят, но считаю не допустимым, когда смеются над кем-то. Любое токсичное поведение порицается.
Споры приведствуются в корректной форме.

📣 Реклама:

Реклама всего, что касается темы канала и тем более на прочие темы — запредельно дорогая 💸 (надо же соблюдать правила!).

Реклама или ссылки на другие TG-каналы и ресурсы — допустимы и даже желательны, но только на принципе взаимности.
👉 Пишите в личку — обсудим.

🤝 Поддержка канала

Самая главная поддержка — делиться своими знаниями (задачами, фичами, техниками).
У меня много идей и материала, но рано или поздно они закончатся.

Вторая просьба — делитесь ссылкой на канал.
Если он нравится Вам, то понравится и Вашим друзьям.

Донаты.
Я жадный и сам донатю редко, поэтому просить или клянчить не буду.
Донатить стоит тогда и только тогда, когда уже не можете не донатить, и если и только, если канал Вас реально обогатил (во всех смыслах).

💡 Я хорошо зарабатываю, но даже один рубль — огромная мотивация.
Для меня важна не сумма, а то, что вы потратили своё личное время и отметили мою работу.

💳 Из России:
СБП: +79022628036 — мой телефон (Сбер, Т-банк)
Карта Сбера моей жены «Екатерина П.» — 4817 7603 0901 1850

🌍 Из-за бугра: Boosty

Крипта USDT:
TLJNKq7iiAq4DXmNXNbb3cpL3CX2SPjURk

Поиск по каналу:
#CodeArchitecture — посты об архитектуре кода.
#SQLOptimization — посты про оптимизацию SQL.
#RealInterviewTasks — реальные задачи с собеседований в разное время.
#Cases — случаи из практики.
#Tools — полезные ссылки.
#FriendlyResources — дружественные ресурсы.
#Reclama — то, что вряд ли будет.
🤡10👍2👎1🔥1🕊1
DB developers channel pinned « «Через десять минут на советский берег вышел странный человек без шапки и в одном сапоге. Ни к кому не обращаясь, он громко сказал: — Не надо оваций! Графа Монте-Кристо из меня не вышло. Придётся переквалифицироваться в управдомы». — Илья Ильф и Евгений…»
🎵 «Я искала тебя годами долгими,
Искала тебя дворами тёмными,
В журналах, в кино, среди друзей…
И в день, когда нашла — с ума сошла.» — Земфира

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

📚 Отличный канал, где собрано много интересных книжек:
👉 https://t.me/books_itishki

⚠️ Часто спрашивают: «С какой книги начать?»
Рекомендую «Секреты ORACLE SQL»:
https://t.me/books_itishki/3139

Это экспресс-метод Илоны Давыдовой — написано понятно, живо и информативно.

👨‍💻 Когда я только начинал работать разработчиком БД (ещё в далёком 2017 году), коллеги подсунули мне именно эту книгу.
Я штудировал её по схеме «одна глава в день» — и за 2–3 недели уже уверенно понимал и писал SQL-запросы.
Дальше практика только закрепила навык: каждый день приходилось разбирать и переписывать трёх- и даже пятиэтажные монстры-запросы.
Тогда они казались мне монстрами. Сейчас нет такого запроса, который бы меня удивил. Это дело не таланта, а исключительно дело привычки.

💎 Помошь каналу⁉️
💡 Если у Вас есть свои полезные ссылки — не жадничайте, делитесь в комментариях!
💡 Какие книжки Вы бы порекомендовали начинающим?
#tools
👍11🤡10👎21
🎵 «Первая любовь, снег на проводах,
В небе промелькнувшая звезда.
Не повторяется, не повторяется,
Не повторяется такое никогда.» - Михаил Пляцковский

📌 Разбор задачи: Самая длинная подстрока без повторяющихся символов

🧩 Условие:
Дана строка. Нужно найти длину самой длинной подстроки без повторяющихся символов.
Решение процедурными методами. Задача скорее на скорость, чем на оптимальность.
Требуется за 10 минут придумать рабочий алгоритм и пусть он не будет оптимальным.

🔢 Пример:

Строка: 11222listen33455677
👉 Результат: 8 (2listen3)

⚙️ Алгоритм (кратко):
1️⃣ Идём по строке посимвольно.
2️⃣ Если текущий символ совпадает с предыдущими — фиксируем максимум, обнуляем счётчик.
3️⃣ Иначе — увеличиваем длину текущей подстроки.
4️⃣ В конце выводим максимальное значение.

DECLARE
str VARCHAR2 (4000) := '11222listen33455677';
maxSubStrLenght INTEGER := 1;
currentSubStrLength INTEGER := 1;

FUNCTION is_unique_symbol (str IN VARCHAR2,
last_index IN INTEGER,
str_length IN INTEGER,
symbol IN CHAR)
RETURN VARCHAR2 IS
result VARCHAR2 (1) := 'Y';
BEGIN
FOR index# IN 1 .. str_length LOOP
IF SUBSTR (str, last_index - index#, 1) = symbol THEN
result := 'N';
EXIT;
END IF;
END LOOP;

RETURN result;
END is_unique_symbol;
BEGIN
FOR index# IN 2 .. LENGTH (str) LOOP
IF is_unique_symbol (str => str,
last_index => index#,
str_length => currentSubStrLength,
symbol => SUBSTR (str, index#, 1)) = 'Y' THEN
currentSubStrLength := currentSubStrLength + 1;
ELSE
maxSubStrLenght := GREATEST (currentSubStrLength, maxSubStrLenght);
currentSubStrLength := 1;
END IF;
END LOOP;

maxSubStrLenght := GREATEST (currentSubStrLength, maxSubStrLenght);

DBMS_OUTPUT.PUT_LINE ('maxSubStrLenght = ' || TO_CHAR (maxSubStrLenght));
END;

👍 Палец вверх — продолжаем в том же духе.
👎 Палец вниз — значит, пора менять тему.

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

💬 Делитесь своими идеями в комментах 👇
#️⃣ #SQL #Oracle #PLSQL
👍11🤡9👎4
🎵 «И качнутся бессмысленной высью
Пара фраз, залетевших отсюда.
Я тебя никогда не увижу,
Я тебя никогда не забуду.» — Андрей Вознесенский

📢 Изучаем необычную оптимизацию SQL

Сколько строчек выведет этот скрипт в консоль и почему?
SQL
DECLARE
cursorText VARCHAR2 (4000)
:= 'WITH
FUNCTION get_amount ("CASE" IN INTEGER)
RETURN INTEGER
IS
result INTEGER;
BEGIN
DBMS_OUTPUT.put_line (''"CASE" = '' || TO_CHAR ("CASE"));

RETURN 1;
END get_amount;

SELECT SUM (aa.function_amount) AS function_amount_sum
FROM (SELECT (SELECT get_amount ("CASE" => a."CASE") FROM DUAL) AS function_amount
FROM ( SELECT MOD(level, 2) AS "CASE"
FROM DUAL
CONNECT BY LEVEL <= 10) a) aa ';

testCursor SYS_REFCURSOR;
functionAmountSum NUMBER;
BEGIN
OPEN testCursor FOR cursorText;

FETCH testCursor INTO functionAmountSum;

CLOSE testCursor;

DBMS_OUTPUT.put_line ('functionAmountSum = ' || functionAmountSum);
END;

🟢 Варианты ответа:
🍏 — 3 строки
🍌 — 11 строк
💥 — выдаст ошибку

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

✍️ Пишите свои варианты в комментариях! Завтра будет подробный разбор.
#️⃣ #Cases #SQL #Oracle #PLSQL
👎3💊21🤣1
DB developers channel
🎵 «И качнутся бессмысленной высью Пара фраз, залетевших отсюда. Я тебя никогда не увижу, Я тебя никогда не забуду.» — Андрей Вознесенский 📢 Изучаем необычную оптимизацию SQL Сколько строчек выведет этот скрипт в консоль и почему? SQL DECLARE cursorText…
📢 Разбор SQL-кейса: хитрый вызов функции через подзапрос

Иногда в коде встречается конструкция вроде:
SELECT 
(SELECT my_function(my_param => a.my_column)
FROM DUAL) AS function_result
FROM my_table;

В скрипте я специально усложнил пример: добавил динамику, иерархию, явный курсор и функцию прямо в WITH FUNCTION. Всё это — ради того, чтобы отвлечь внимание от ключевой части:
👉 SELECT (SELECT get_amount("CASE" => a."CASE") FROM DUAL) AS function_amount FROM ...

И вот тут кроется интересное поведение Oracle: при таком вызове результаты функции кэшируются.

🔎 Разберём пошагово:

1️⃣ Таблица a:
SELECT MOD(LEVEL, 2) AS "CASE"
FROM DUAL
CONNECT BY LEVEL <= 10

Вернёт 10 строк: 0,1,0,1,0,1,0,1,0,1.

2️⃣ Внутренний подзапрос:
(SELECT get_amount("CASE" => a."CASE") FROM DUAL)

На каждой строке из a Oracle вызывает функцию через DUAL. Но из-за конструкции с подзапросом он воспринимает вызов как условно детерминированный и кэширует результат.

3️⃣ Эффект вызова функции:
Хотя строк 10, сама функция реально вызовется только 2 раза — для CASE = 0 и CASE = 1.
А в DBMS_OUTPUT мы увидим всего две строки:
"CASE" = 0
"CASE" = 1

4️⃣ Финальный SUM:
Функция всегда возвращает 1, значит внешняя сумма даст 10.
И на экран добавится строка:
functionAmountSum = 10

Итоговый расклад:
2 строки от вызова функции
1 строка с финальным результатом
Всего: 3 строки в консоли

⚠️ Важно:
1️⃣ кеш ограничивается 255 уникальными значениями. Если входных параметров больше — эффекта кэширования не будет.
2️⃣ Если Вы предполагаете использовать кеширование в запросе, то лучше это делать явно.

📚 Эффект давно известен, но в официальной документации Oracle его нет.
Зато есть хорошая статья в https://oracle-base.com/articles/misc/efficient-function-calls-from-sql

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

✍️ Черканите в комментах, только если хотите👇
#️⃣ #SQL #Oracle #PLSQL #Cases
3👍3👎1💊1
🎵 «Мы себе давали слово - не сходить с пути прямого
Но так уж суждено, хмм
И уж если откровенно - всех пугают перемены
Но - тут уж всё равно, хмм» — Андрей Макаревич

📌 Разбор задачи: Транспонирование матрицы

🧩 Условие:
Дана матрица M x N. Нужно построить новую матрицу N x M, в которой строки становятся столбцами, а столбцы — строками.

🔢 Пример:
Исходная матрица:
1 2 3
4 5 6

👉 Результат:
1 4
2 5
3 6

⚙️ Алгоритм (кратко):
1️⃣ Создаём пустую матрицу размером, равным транспонированной.
2️⃣ Для каждой ячейки matrix(i, j) кладём её значение в transposedMatrix(j, i).
3️⃣ Выводим результат.

DECLARE
TYPE row_tab IS TABLE OF NUMBER;
TYPE matrix_tab IS TABLE OF row_tab;

matrix matrix_tab := matrix_tab (row_tab (1, 2, 3), row_tab (4, 5, 6));
transposedMatrix matrix_tab := matrix_tab ();
BEGIN
<<create_trasposed_matrix>>
DECLARE
row row_tab := row_tab();
BEGIN
row.EXTEND(matrix.COUNT);

FOR i IN 1 .. matrix(1).COUNT LOOP
transposedMatrix.EXTEND;
transposedMatrix(transposedMatrix.COUNT) := row;
END LOOP;

FOR i IN 1 .. matrix.COUNT LOOP
FOR j IN 1 .. matrix(i).COUNT LOOP
transposedMatrix(j)(i) := matrix(i)(j);
END LOOP;
END LOOP;
END;

<<print_matrix>>
DECLARE
str VARCHAR2(100);
BEGIN
FOR i IN 1 .. transposedMatrix.COUNT LOOP
str := '';
FOR j IN 1 .. transposedMatrix(i).COUNT LOOP
str := str || ' ' || TO_CHAR(transposedMatrix(i)(j));
END LOOP;
DBMS_OUTPUT.put_line(str);
END LOOP;
END;
END;

👍 Палец вверх — если тема заходит.
👎 Палец вниз — если пора что-то сменить.

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

💬 Как Вам такой поворот? Ответы и прочее там. 👇
#️⃣ #SQL #Oracle #PLSQL
👍9👎2💊2
script_copy_complex_structure_table.sql
28.5 KB
🎵 «Товаришч, товаришч, скыжи моей ты мамене
Что сын ее погибнул на войне
И с шашкою в рукою, с винтовкою в другою
И с песнею веселой на губе» — приписывается Борису Тимофееву

📢 Копирование сложной структуры таблицы, или как косячит ИИ 🤖

Итак, постановка реальной задачи.
Есть две таблицы TEST.DEMO_PRIMARY_TABLE, TEST.DEMO_SECONDARY_TABLE.
По структуре они должны быть идентичны — отличаться только по имени таблицы PRIMARY SECONDARY и по названиям объектов: _P_ _S_.
Требуется скрипт, который бы динамически: при наличии TEST.DEMO_PRIMARY_TABLE создавал TEST.DEMO_SECONDARY_TABLE и наоборот — без данных.
При этом — правильное соответствие в названиях объектов.
-- primary
CREATE TABLE TEST.demo_primary_table
(
id NUMBER NOT NULL,
col1 VARCHAR2 (50 CHAR) NOT NULL,
col2 TIMESTAMP (6) WITH TIME ZONE NOT NULL,
col3 DATE,
col4 NUMBER NOT NULL,
col5 RAW (16),
col6 INTEGER,
col7 TIMESTAMP (6),
col8_id NUMBER,
col8 VARCHAR2 (100),
col3_trunc AS (TRUNC ("COL3"))
)
TABLESPACE USERS;

COMMENT ON TABLE test.demo_primary_table IS 'PRIMARY';
COMMENT ON COLUMN TEST.demo_primary_table.col1 IS 'col1';
COMMENT ON COLUMN TEST.demo_primary_table.col3_trunc IS 'col3_trunc';

CREATE BITMAP INDEX TEST.DEMO_TABLE_P_BMI01
ON TEST.demo_primary_table (col1)
TABLESPACE USERS;

CREATE BITMAP INDEX TEST.DEMO_TABLE_P_BMI02
ON TEST.demo_primary_table (col3_trunc)
TABLESPACE USERS;

CREATE INDEX TEST.DEMO_TABLE_P_I01
ON TEST.demo_primary_table (id)
TABLESPACE USERS;

CREATE INDEX TEST.DEMO_TABLE_P_I02
ON TEST.demo_primary_table (col4, col1, col3)
TABLESPACE USERS;

ALTER TABLE TEST.demo_primary_table
ADD CONSTRAINT DEMO_TABLE_P_C01 CHECK
( (col8 IS NOT NULL AND col8_id IS NOT NULL)
OR (col8 IS NULL AND col8_id IS NULL));

🔎 Зачем это нужно
В DWH реализован механизм «двух ног» через синонимы: для каждого среза данных есть 2 таблицы — PRIMARY и SECONDARY.
Они абсолютно идентичны, отличаются только названием таблицы и названиями объектов.
Когда срез пересобирают (новые поля / логика / баги), один срез заполняется, второй остаётся доступным для потребителей.
После завершения — таблицы меняют местами через пересоздание синонима.

Со временем от этого механизма отказались, потому что данных стало слишком много — пересоздание срезов и открытие доступа создавали большую нагрузку.
Пересоздавать срезы начали на резервном сервере, и лишние таблицы стали не нужны.
Удалить их относительно легко — но для каждого наката нужен скрипт отката.
А вот его создать не так-то просто.
Тестовых сред много, и в каждой среде тестировщики могли пересоздавать срезы каждый день — заранее знать, какая таблица актуальна в какой среде, нельзя.
Никто не гарантирует, что везде имена и состав объектов одинаковы.

🤖 А как же косячит Терминатор?
Он не справился с задачей: даже после 4 часов подбора промптов получались разные ошибки и несоответствия. Доверие к ИИ немного подорвано — зато задача стала яснее. Пришлось взять за основу более или менее подходящий скрипт от ИИ и работать самому.
Джон Коннор жив!

🤓 - Ставим, если хотите подробный разбор скрипта.
Если соберем более 20 🤓, то сделаю подробный разбор.

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

✍️ Пишите свои мысли в комментариях!
#Cases #SQL #Oracle #PLSQL
🤓1311👎1💊1
🎵 «Мы колесили по дорогам,
Меняя струны и подруг,
О, нам не хватало рук,
И если все добро от Бога -
Нам не светит теплый рай,
Сколько ни играй,
Это просто замкнутый круг.» — Ария

📌 Разбор задачи: Определение цикла в связном списке

🧩 Условие:
Дан связный список. Необходимо определить, содержит ли он цикл (т.е. есть ли узел, в который можно попасть снова, двигаясь по ссылкам NEXT).

⚙️ Алгоритм (кратко):
1️⃣ Заводим два указателя — медленный (slow) и быстрый (fast).
2️⃣ slow двигается по одному шагу, а fast — по два.
3️⃣ Если в какой-то момент они указывают на один и тот же элемент — цикл существует.
4️⃣ Если fast дошёл до конца (NULL) — цикла нет.

🔢 Пример работы:
Список:  1 → 2 → ... → 9 → 10
↑ ↓
└─────────────┘
👉 Есть цикл (10 → 2)


Код на PL/SQL
CREATE OR REPLACE TYPE node AS OBJECT (
value NUMBER,
next REF node
);

CREATE TABLE nodes OF node;

DECLARE
TYPE list_tab IS TABLE OF REF node;

"list" list_tab := list_tab();

isExistCycle VARCHAR2 (1) := 'N';
listElementCount INTEGER := 10;
BEGIN
<<build_initial_list>>
DECLARE
newElement REF node;
BEGIN
FOR index# IN 1 .. listElementCount
LOOP
INSERT INTO nodes n
VALUES (node (VALUE => index#, NEXT => NULL))
RETURNING REF (n)
INTO newElement;

"list".extend;
"list" ("list".COUNT) := newElement;
IF index# > 1
THEN
UPDATE nodes n
SET n.NEXT = "list" (index#)
WHERE REF (n) = "list" (index# - 1);
END IF;
END LOOP;
END;

<<add_circle_to_list>>
DECLARE
BEGIN
UPDATE nodes n
SET n.NEXT = "list" (2)
WHERE REF (n) = "list" (listElementCount);
END;

<<is_exists_circle>>
DECLARE
slow REF node := "list" (1);
fast REF node := "list" (1);
BEGIN
WHILE fast IS NOT NULL
LOOP
SELECT DEREF (fast).NEXT INTO fast FROM DUAL;

EXIT WHEN fast IS NULL;

SELECT DEREF (fast).NEXT INTO fast FROM DUAL;

SELECT DEREF (slow).NEXT INTO slow FROM DUAL;

IF slow = fast
THEN
isExistCycle := 'Y';
EXIT;
END IF;
END LOOP;
END;

"list".delete;

IF isExistCycle = 'Y'
THEN
DBMS_OUTPUT.put_line ('The cycle is found!');
ELSE
DBMS_OUTPUT.put_line ('The cycle is not fount');
END IF;
EXCEPTION
WHEN OTHERS THEN
IF "list" IS NOT NULL AND "list" IS NOT EMPTY THEN
"list".delete;
END IF;

RAISE;
END;

👍 Палец вверх — если тема с алгоритмами продолжает заходить.
👎 Палец вниз — если пора сменить трек.

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

💬 А у вас в коде бывали «замкнутые круги»? 👇
#️⃣ #SQL #Oracle #PLSQL
👍101💊1
🎭 «Гость долго грустил и дергался, но наконец заговорил:
— Видите ли, какая странная история, я здесь сижу из-за того же, что и вы, именно из-за Понтия Пилата, — тут гость пугливо оглянулся и сказал: — Дело в том, что год тому назад я написал о Пилате роман.
— Вы — писатель? — с интересом спросил поэт.
Гость потемнел лицом и погрозил Ивану кулаком, потом сказал:
— Я — мастер»
— М.А. Булгаков, «Мастер и Маргарита»

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

Все, кто работает с Oracle, рано или поздно сталкиваются с оптимизацией запросов. И тут появляются хинты — инструмент тонкой настройки работы оптимизатора.

📌 Лучший ресурс по этой теме (и мой личный фаворит) — материалы Игоря Усольцева.
👉 https://iusoltsev.wordpress.com/profile/individual-sql-and-cbo/cbo-hints/

Там всё настолько кратко и по делу, что даже добавлять особо нечего.

Хотел бы спросить у читателей:
👀 Интересен ли вам разбор хинтов прямо на канале и их применение на практике?

👍 Палец вверх — разберём на примерах.
👎 Палец вниз — оставим, как есть, и будем ссылаться на первоисточник.

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

💡 Делитесь своими любимыми авторами там. 👇
#tools
👍28👎1💊1