DB developers channel
804 subscribers
2 photos
46 files
102 links
💡 Канал для разработчиков баз данных: Oracle, PostgreSQL
📌 Интересные задачи, фрагменты кода, лучшие практики, архитектура, оптимизация
🔄 Присоединяйся к сообществу — развивайся вместе с нами!
#SQL #Oracle #PostgreSQL #PL/SQL #PL/pgSQL #DB
Download Telegram
А вы со всем согласны?
Если не согласны, то с чем?
Forwarded from Пишем на SQL (Ilia Khokhlov 👨‍💻)
Рекомендации по написанию программного кода хранимых функций и процедур

Чем больше нагрузка и больше одновременно выполняющихся транзакций, тем больше и ответственности при создании хранимых функций и процедур. Хорошо написанный программный код легко сопровождаем и не боится параллельной работы! Ниже собрал для вас основные рекомендации, которые важно учитывать при разработке хранимок (функций, процедур, триггеров).

1. Максимально эффективное обращение к данных (желательно всегда по первичным ключам и индексам);

2. Разделение сложных операций на более простые (сложный update, insert или delete с подзапросами можно упростить - сделать его по ключу, а данные, получаемые подзапросами, можно предварительно положить во временную таблицу или таблицу временного хранения данных);

3. Соблюдение идентичной последовательности работы с таблицами в разных функциях и процедурах (это позволит минимизировать вероятность взаимных блокировок);

4. Форматирование кода (соблюдение отступов, единое оформление, принятое в компании или в конкретной АС), использование регламентов оформления кода;

5. Соблюдение соответствия программы кода хранимки её назначению (процедура, открывающая вклад клиенту, должна открывать вклад клиенту и не должна дополнительно закрывать кредит);

6. Краткое и понятное комментирование неочевидных участков кода;

7. Идеальный код одной хранимки помещается на одном экране, чтобы не пришлось его прокручивать (конечно, на практике, это далеко не всегда так, но надо к этому стремиться). Если алгоритм большой, то его нужно разбивать на дополнительные функции и процедуры, которые можно использовать в коде основной функции/процедуры.
1👎1🤮1
Пишем на SQL
Рекомендации по написанию программного кода хранимых функций и процедур Чем больше нагрузка и больше одновременно выполняющихся транзакций, тем больше и ответственности при создании хранимых функций и процедур. Хорошо написанный программный код легко сопровождаем…
🎵«За що же ж мы боролись
За що же ж мы страждали
За що ж мы проливали нашу кровь?»
— (приписывается Борису Тимофееву)

Извините, но сегодня я включил режим "Душнилы" 🤓
Критиковать всегда проще, чем составить текст.
Все указанные правила — либо верны полностью, либо частично.
Мне стало интересно добавить другой формат — критический разбор.

1️⃣ Максимально эффективное обращение к данным

Тезис правильный 👍, но как это относится к коду? Никак.
Да, индексы и структура данных важны, но это скорее про хранение и доступ, а не про сам код.
Изначально индексы создаются с расчётом, что их будут использовать в коде. Но возможен и обратный анализ: индексы нужно добавлять, изменять или удалять, исходя из анализа кода.

2️⃣ Разделение сложных операций на простые

Операции не должны быть «простыми» или «сложными», они должны быть эффективными .
Если сложный запрос работает быстрее — переделывать его в «более простой» не нужно.
Вывод: всегда нужно проверять варианты и выбирать оптимальный. «Сложность» и «простота» — субъективны.

3️⃣ Единая последовательность работы с таблицами

В целом верно ✔️: одинаковая последовательность доступа к таблицам действительно снижает риск взаимных блокировок.
Но:
Не всегда это возможно.
Это лишь один из способов минимизации блокировок.
Закономерность есть, но правило не универсальное.

4️⃣ Форматирование кода

Тут согласен на 💯.
У каждой компании есть своё «самое лучшее» форматирование. Но к любому стилю быстро привыкаешь.
Единый стиль — это уважение к коллегам и к будущему себе.

5️⃣ Соответствие кода назначению

Полностью согласен 👍.
Метод должен делать ровно то, что написано в его имени.
Хочешь «открыть вклад» и «закрыть кредит»? Сделай три метода:

открыть_вклад
закрыть_кредит
открыть_вклад_и_закрыть_кредит

Да, странный метод 🤷, но принцип понятен: каждый метод выполняет свою задачу.

6️⃣ Комментарии в коде

Согласен, но с уточнением ✍️.
В 90% случаев комментарии появляются из-за проблем с неймингом или структурой кода.
Прежде чем написать комментарий, спроси себя:

не повторяю ли я имя метода?
не лучше ли переименовать или упростить код?

👉 В хорошем коде комментариев мало, но они цепляют внимание.
👉 В плохом коде комментариев много, и это выглядит как спам.

7️⃣ Код на одном экране

Здесь не согласен .
Стремиться надо не к минимизации, а к простоте и читаемости.
Если метод состоит из 10 логических этапов, пусть будет 10 вызовов.
А вот искусственное деление ради «одного экрана» делает код трудным для анализа и изменений.

💬 Если есть возражение, согласие или негодование — милости просим в комментарии!

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

#CodeArchitecture
👍1👎1🔥1
🎵«Я спросил у ясеня
Где моя любимая?
Ясень не ответил мне
Качая головой» — Сергей Никитин

Разбор задачи: найти максимальную глубину бинарного "ясеня".

Никогда прежде не имел опыта работы с такого рода объектами в PL/SQL.
Многому научился.😊
Возможно, правильнее все методы инкапсулировать в OBJECT node. Но для меня для тестовой задаче это уже слишком.

CREATE OR REPLACE TYPE node IS OBJECT (
value NUMBER,
left REF node,
right REF node
);
CREATE TABLE nodes OF node;

DECLARE
root REF node;

PROCEDURE init_tree (node_amount IN INTEGER, root OUT REF node) IS
result REF node;

PROCEDURE insert_node (root IN OUT REF node, new_value IN NUMBER) IS
current node;
BEGIN
IF root IS NULL THEN
-- создаём корень
INSERT INTO nodes n
VALUES (node (VALUE => insert_node.new_value, left => NULL, right => NULL))
RETURNING REF (n)
INTO insert_node.root;
ELSE
SELECT DEREF (root) INTO current FROM DUAL;

IF new_value < current.VALUE THEN
insert_node (root => current.left, new_value => new_value);

-- обновляем left
UPDATE nodes n
SET n.left = current.left
WHERE REF (n) = insert_node.root;
ELSE
insert_node (root => current.right, new_value => new_value);

-- обновляем right
UPDATE nodes n
SET n.right = current.right
WHERE REF (n) = insert_node.root;
END IF;
END IF;
END insert_node;
BEGIN
FOR i IN 1 .. node_amount LOOP
DECLARE
newValue NUMBER := TRUNC (DBMS_RANDOM.VALUE (1, 100));
BEGIN
insert_node (root => result, new_value => newValue);
END;
END LOOP;

root := result;
END init_tree;

PROCEDURE print_tree (root IN OUT REF node) IS
current node;
skip EXCEPTION;
BEGIN
IF root IS NULL THEN
RAISE skip;
END IF;

SELECT DEREF (root) INTO current FROM DUAL;

DBMS_OUTPUT.put_line (
'node('
|| TO_CHAR (current.VALUE)
|| ','
|| CASE WHEN current.left IS NOT NULL THEN 'left = yes' ELSE 'left = no' END
|| ','
|| CASE WHEN current.right IS NOT NULL THEN 'right = yes' ELSE 'right = no' END
|| ')');

IF current.left IS NOT NULL THEN
print_tree (root => current.left);
END IF;

IF current.right IS NOT NULL THEN
print_tree (root => current.right);
END IF;
EXCEPTION
WHEN skip THEN
NULL;
END print_tree;

FUNCTION max_depth (root IN REF node)
RETURN INTEGER IS
current node;
leftDepth INTEGER;
rightDepth INTEGER;
BEGIN
IF root IS NULL THEN
RETURN 0;
END IF;

SELECT DEREF (root) INTO current FROM DUAL;

leftDepth := max_depth (current.left);
rightDepth := max_depth (current.right);

IF leftDepth > rightDepth THEN
RETURN leftDepth + 1;
ELSE
RETURN rightDepth + 1;
END IF;
END max_depth;
BEGIN
init_tree (node_amount => 10, root => root);

print_tree (root => root);

DBMS_OUTPUT.put_line ('Max depth is ' || TO_CHAR (max_depth (root => root)));

ROLLBACK;
END;
👎1🤬1
1️⃣ Подготовка структуры данных
Тип объекта:
node(value NUMBER, left REF node, right REF node) — значение и ссылки на детей.
Таблица хранения:
CREATE TABLE nodes OF node; — каждая строка — объект node.

Почему так: используется объектная модель Oracle: связи не по ID, а через REF/DEREF.🧩

2️⃣ Инициализация дерева — init_tree 🌱
Процедура init_tree (node_amount IN INTEGER, root OUT REF node) создаёт дерево и возвращает root.

Обратите внимание: в текущем виде цикл фиксирован на FOR i IN 1 .. node_amount

Внутри — вложенная процедура
insert_node(root IN OUT REF node, new_value IN NUMBER), которая строит бинарное дерево (вставляет меньшее влево, большее/равное вправо).

🧩 Ключевые моменты insert_node:
Если root IS NULL → создаём корень:
INSERT INTO nodes ... RETURNING REF(n) INTO insert_node.root;

Иначе читаем текущий узел:
SELECT DEREF(root) INTO current FROM DUAL;

Рекурсивно вставляем в current.left или current.right:

insert_node(root => current.left, new_value => new_value);
Затем фиксируем изменённую ссылку в таблице:
UPDATE nodes n SET n.left = current.left WHERE REF(n) = insert_node.root; (или n.right = current.right).
Это важно, потому что мы меняли current как локальную копию, и нужно записать обновлённый REF обратно. 🧷

3️⃣ Печать дерева — print_tree 🖨️
print_tree(root IN OUT REF node) делает простой прямой обход (node → left → right):
Пропуск NULL через локальное исключение skip.

Для каждого узла — вывод value и флагов наличия детей (left = yes/no, right = yes/no).

Рекурсивный вызов для current.left и current.right.

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

4️⃣ Алгоритм глубины — max_depth 📏
FUNCTION max_depth(root IN REF node) RETURN INTEGER:

База рекурсии: root IS NULL → 0.

Иначе DEREF(root) → current.

Рекурсивно считаем:
leftDepth := max_depth(current.left);
rightDepth := max_depth(current.right);

Возвращаем max(leftDepth, rightDepth) + 1.

Сложность:
По времени — O(n) (каждый узел посещаем ровно один раз).
По памяти стека — O(h), где h — высота дерева.
Поскольку дерево строится случайно (DBMS_RANDOM), оно не сбалансировано, и h может быть близко к n в худшем случае. 🎲

5️⃣ Точка входа блока 📦
init_tree(...) — строим дерево, получаем root.
print_tree(root) — показываем структуру.
DBMS_OUTPUT.put_line('Max depth is ' || max_depth(root)) — вывод глубины.

ROLLBACK; — данные не сохраняются: демонстрация без «мусора» в базе.

6️⃣ Что важно помнить ⚠️
Работа с REF/DEREF требует обновления родителя после рекурсивной вставки — это и делает UPDATE ... WHERE REF(n) = insert_node.root;.

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

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

Дамы и Господа, выбор всегда за вами 😉
#sql #Oracle #PLSQL
👍2🤯2👎1
🎵«Пусть нету ни кола и ни двора,
Зато не платят Королю налоги
Работники ножа и топора,
Романтики с большой дороги.» — м/ф Бременские музыканты

🚨 Разбор задачи: Дом-вор 🏠💰

Условие:
Даны дома в ряд, каждый из которых имеет некоторую сумму денег.
Нельзя грабить два смежных дома.
💡 Задача: найти максимальную сумму, которую можно украсть.

Пример:

Дома: 2 7 9 3 1
Максимальная сумма: 12
DECLARE
TYPE matrix_tab IS TABLE OF NUMBER;

houses matrix_tab := matrix_tab (2, 7, 9, 3, 1);
matrix matrix_tab := matrix_tab ();

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;

PROCEDURE get_thief_matrix (houses IN OUT NOCOPY matrix_tab, matrix IN OUT NOCOPY matrix_tab) IS
BEGIN
FOR i IN 1 .. houses.COUNT LOOP
IF i = 1 THEN
matrix.EXTEND;
matrix(matrix.COUNT) := houses(1);
ELSE
matrix.EXTEND;
matrix(matrix.COUNT) := GREATEST(
matrix(i-1),
CASE WHEN i <= 2 THEN 0 ELSE matrix(i-2) END + houses(i)
);
END IF;
END LOOP;
END get_thief_matrix;
BEGIN
print_matrix(matrix => houses);

get_thief_matrix(houses => houses, matrix => matrix);

print_matrix(matrix => matrix);
END;

Формула из кода для каждого дома i:
matrix(i) := GREATEST(
matrix(i-1),
CASE
WHEN i <= 2 THEN 0
ELSE matrix(i-2)
END + houses(i)
);

💡 Пояснение:
matrix(i-1) — пропускаем текущий дом.
matrix(i-2) + houses(i) — грабим текущий дом, добавляем к сумме через один дом.
GREATEST выбирает максимальный вариант.

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

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

Дамы и Господа, выбор всегда за вами 😉
#sql #Oracle #PLSQL
👍3👎2
🎵«Если вы уже устали —
Сели-встали, сели-встали.
Не страшны вам Арктика с Антарктикой.» - В.С. Высоцкий

🚀 Простая задача с уникальными ключами и NULL 📝
Такая задача, вполне, может быть на собесе.
Спасибо подписчику Андрею. 😉

Условие:
Создаем таблицу с уникальным ограничением на два столбца и заполняем её NULL-значениями.
Нужно понять, как работает UNIQUE с NULL.

CREATE TABLE dropme_tt(
id INT,
a INT,
b INT,
CONSTRAINT tt_uk UNIQUE (a, b)
);

INSERT INTO dropme_tt (id, a, b)
SELECT level AS id, NULL AS a, NULL AS b
FROM dual
CONNECT BY level < 10;

SELECT * FROM dropme_tt;

Какой будет результат?

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

Ваши решения и сомнения туда👇
#sql #Oracle #PostgreSQL #PLSQL #PLpgSQL
👍1👎1
💬 Резюме: ИИ и будущее разработки

Вчера проходил собес (скорее, это была ознакомительная встреча) с CEO одной международной компании.
Мы также поговорили про влияние ИИ на разработку. Оказалось, что мой анализ ситуации полностью совпадает с его анализом и я решил свой анализ здесь озвучить.

🚀 Что умеет ИИ:
Решает SQL-задачи на уровне очень крепкого джуна / начинающего миддла.
Эффективен и быстрый в простых и стандартных задачах.
Инструмент для ускорения работы и тестирования.

⚠️ Ограничения:
В сложных решениях иногда ошибается.
Нужен синьор или эксперт для проверки и корректировки кода. (Пока нужен!!!)

💡 Влияние на рынок IT:
GPT, Gemini, Deepseek и другие меняют рынок:
Рутинная работа выполняется быстрее и качественнее, чем человеком.
В командах остаётся минимум людей (синьоров, экспертов).

💰 Карьерные выводы:
Зарплаты синьоров и экспертов будут расти 🚀.
Джуны будут работать за еду, в надежде когда-то стать синьорами.

Общий вывод:
Ребята! Пока эта канитель внедряется в международных компаниях, но на местах она придет чуть позже.
Пока есть время!
Учитесь, перенимайте чужой опыт, не бойтесь сложных задач.
Именно, решение сложных задач поднимает из рутины.

💎 Поддержка канала⁉️
👍2👎1
🚀 Разбор простой, но коварной задачи про UNIQUE и NULL
(идея от подписчика Андрея — спасибо! 😉)

Вопрос: что произойдет при таком коде?
CREATE TABLE dropme_tt(
id INT,
a INT,
b INT,
CONSTRAINT tt_uk UNIQUE (a, b)
);

INSERT INTO dropme_tt (id, a, b)
SELECT level, NULL, NULL
FROM dual
CONNECT BY level < 10;

🔎 Разбор:

В UNIQUE участвуют (a, b).
В SQL NULL — это «неизвестное значение».
Два NULL не равны друг другу.

👉 Значит, для UNIQUE это разные значения, и конфликтов нет.
Да и на все NULL значения индекс B-tree не строится.
Итог: все 9 строк спокойно вставятся. Ошибки не будет.

📌 Результат в Oracle и PostgreSQL одинаковый:
Таблица заполнится строками с id = 1..9, а (a, b) везде будут (NULL, NULL).

Важные замечания
Если бы на колонках стоял NOT NULL, фокус не прошёл бы.
👉 С этого и начинается консистентность данных — всегда анализируйте, может ли поле быть пустым.

На практике ограничения (CONSTRAINT ... UNIQUE) чаще создают отдельными командами:
CREATE TABLE dropme_tt
(
id INTEGER,
a INTEGER,
b INTEGER
);

CREATE UNIQUE INDEX dropme_tt_u01
ON dropme_tt (a, b);

Для DB разницы нет, а вот читать код человеку так проще.
Да и мы контролируем наименование индекса и не позволяем присваивать системные имена.

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

#RealInterviewTasks #sql #Oracle #PostgreSQL #PLSQL #PLpgSQL
👍2👎1
🎵 «Я вытяну счастливую карту…» — Земфира

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

🔗 Diffchecker (https://www.diffchecker.com/)
— простое и удобное web-средство для сравнения текста.

📌 Где может пригодиться:
быстро проверить изменения в SQL-скрипте или конфигурации;
сравнить два варианта кода;
увидеть, что именно поменялось в тексте документа.

Работает прямо в браузере, без установки.

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

💡 Если у Вас есть свои полезные ссылки — не жадничайте, делитесь в комментах! 😉
#tools
👍3👎1
🎵 «На ель она взлетела,
А может, не взлетела,
А может быть, на пальму
Ворона взобралась» - Э. Успенский

Сегодня воскресенье, а значит — без задач

Сегодня про теоретические вопросы на собесах.
Честно говоря, я не считаю это валидным способом проверки разработчика.
(Может быть, потому что сам не смогу перечислить все формы нормализации данных — они мне интуитивно понятны, но выучить не получается 🤷‍♂️).

Тем не менее, когда я сам был интервьюером, не раз приходилось задавать классический вопрос:
👉 «Что такое ACID?»

📌 Обычно все знают расшифровку:

Atomicity (атомарность)
Consistency (консистентность)
Isolation (изоляция)
Durability (постоянство)

Но вот зачем эти критерии нужны и как именно понимать Consistency — отвечают далеко не все.

💡 В итоге у нас есть два вопроса для обсуждения:

Зачем вообще нужны критерии ACID?
Как именно вы понимаете Consistency (консистентность)?

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

💬 Пишите в комментах, всё что в голову придет👇
#RealInterviewTasks #sql #Oracle #PostgreSQL #PLSQL #PLpgSQL
👎1
DB developers channel
🎵 «На ель она взлетела, А может, не взлетела, А может быть, на пальму Ворона взобралась» - Э. Успенский Сегодня воскресенье, а значит — без задач Сегодня про теоретические вопросы на собесах. Честно говоря, я не считаю это валидным способом проверки разработчика.…
Вчера в комментах произошёл очень показательный диалог.
Комменты получились интереснее поста.
Я уважаемому подписчику (DEN) весьма благодарен 🙏 (к сожалению, не знаю, как Вас зовут).
Примерно так или почти так и звучит ответ на простые и «ничего незначащие» вопросы на собеседованиях.

Вот разбор простыми словами.

Про Consistency в ACID:

👉 если две сессии одновременно запускают один и тот же запрос — они должны получить один и тот же результат.

Важно: это не про целостность данных в таблицах (FK, ограничения, каскады).
Это про то, что база показывает всем транзакциям одну и ту же «картину мира».

Зачем нужны критерии ACID?

Чтобы отличать настоящую DB от простого или сложного «файлового хранилища».
База гарантирует в многопользовательской среде корректную обработку и сохранение транзакций.

📌 Но есть системы, которые называются «базами данных», хотя полного ACID в них нет:

MongoDB (в ранних версиях классический ACID не обеспечивался по умолчанию).
Cassandra и другие NoSQL (делают ставку на масштабирование, жертвуя частью гарантий).
Redis (in-memory key-value store, без полноценного ACID).

💡 Плюсы таких решений: скорость и горизонтальное масштабирование.
Минус: «железобетонной гарантии» ACID нет, и это важно учитывать при проектировании.

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

#RealInterviewTasks #sql #Oracle #PostgreSQL #PLSQL #PLpgSQL
👍4👎1🤔1
Допустим, Вы получили входящий параметр income_collection типа IS TABLE OF NUMBER. Вы знаете, что там могут быть дубли, которые нужно проигнорировать. Каким бы способом Вы бы это сделали?
Anonymous Poll
34%
Функция SET
55%
SELECT DISTINCT (с TABLE() оператором)
10%
Я знаю 3 вариант (Могу поделиться в комментах)
👎1
DB developers channel
Допустим, Вы получили входящий параметр income_collection типа IS TABLE OF NUMBER. Вы знаете, что там могут быть дубли, которые нужно проигнорировать. Каким бы способом Вы бы это сделали?
ORACLE_SET_TEST.sql
11.1 KB
🔥 Небольшой эксперимент с Oracle коллекциями

Я решил проверить, что реально происходит «под капотом», когда мы убираем дубликаты из коллекции TABLE OF NUMBER.
Сравнил два способа:

1️⃣ SET(collection)
2️⃣ SELECT DISTINCT FROM TABLE(collection)

Для чистоты — прогнал на миллионе элементов, собирал статистику из v$mystat (consistent gets, pga/uga, sorts, cpu и т.д.).

📊 Результаты
1️⃣ SET(collection)
time: +000000000 00:00:00.131000000
2️⃣ SELECT DISTINCT FROM TABLE(collection)
time: +000000000 00:00:00.125000000

💡Результат удивительный. Второй вариант быстрее, несмотря на смену контекста с PL/SQL на SQL, выгрузку BULK COLLECT.
У функции SET явно есть проблемы.

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

#Cases
🔥7👍2👎1
🎵 «Ваше благородие, госпожа победа,
значит, моя песенка до конца не спета!
Перестаньте, черти,
клясться на крови...
Не везёт мне в смерти,
повезёт в любви.» — Булат Окуджава

Друзья, канал набрал 100 подписчиков меньше чем за месяц 🎉
Для меня это победа — он уже вышел далеко за рамки круга моих знакомых.

Изначально это задумывалось как публичная записная книжка, но постепенно канал стал чем-то большим — отдельным событием, живым и самостоятельным 🚀.

👋 Немного обо мне:
Меня зовут Поликарпов Кирилл Вадимович, я Senior DB Developer.
💾 Опыт — Oracle, PostgreSQL, MS SQL.
Но честно признаюсь — Oracle мне ближе всего ❤️.

🏦 СКБ-Банк — моя первая профессиональная работа как разработчика DB (2017 год).
Воспоминания тёплые: весёлое и живое время, отличная команда, дружеская атмосфера. Ребята, спасибо за старт! 🙌

💍 Кольцо Урала — для меня это особенное место. Для меня это лучшее место на земле. Я именно там состоялся как разработчик и понял что это моё.
Коллеги из Кольца я Вас не просто люблю - я Вас обожаю, преобожаю. Нигде и никогда мне не было так интересно, продуктивно и комфортно, как работать рядом с Вами.

🐹 Банк Хоум Кредит («Хомяк») — большой банк со своими плюсами и минусами. Здесь я встретил много хороших людей и настоящего друга — Ирека Вафина. Ирек, спасибо тебе, ты крутой человек и сильный разработчик 🤝.

🚕 Такси Максим — отличное место, если хочется много работать и при этом оставаться в позитивной атмосфере. Команда «Бобры» 🦫, которой я имел честь руководить, навсегда в моём сердце. И да, связь с аватаркой канала не случайна 😉.

🌍 Сейчас я работаю в международной процессинговой компании.
Здесь крутые ребята. Требования высокие, работать тяжело, но невероятно интересно. Спасибо и вам, коллеги 🙏.

Особое спасибо подписчикам, которых я не знаю лично. Рад видеть вас среди своих читателей 💫.

🎯 Цель: 1000 подписчиков до конца года.

⚙️ Как пишутся посты?
Я пишу «рыбу» ✍️, а затем прошу ИИ помочь с оформлением, эмодзи и упрощением сложных формулировок. Мы с ИИ — сильная команда 🤖.

Если хотите помочь каналу:
1️⃣ Главное — предлагайте задачи, темы и фичи. Моего материала хватит ещё на 3–4 месяца, а Ваши идеи сделают канал интереснее.
2️⃣ Если у вас есть знакомые, которым близка тема БД, — поделитесь с ними ссылкой.

Спасибо каждому из вас ❤️
👍9🔥5👏2👎1
DB developers channel
📌 Из предыдущего опроса стало ясно, что задачи на алгоритмы Вам интересны. Я прорешал около 💯 задач и понял, что почти все они делятся на 5 категорий:
👍5👎1
«Снежная королева:
— Кай, составь слово «Вечность» — и будешь свободен!
Кай:
— Сколько ни пробую, всё время выходит «ЖОПА»…
Снежная королева:
— Ну, в принципе, то же самое.» - детский анекдот.

🏛 Архитектура кода

Как и обещал подписчику Глебу Буланцеву: набрали 💯 — начинаю серию постов про архитектуру кода.

💡 Каждый разработчик уверен: его код пусть и не идеален, но всё же оптимален в данных обстоятельствах. И это абсолютно нормально — иначе можно утонуть в бесконечных сомнениях.

В этих постах я делюсь своими мыслями и опытом и, естественно, не претендую на вселенскую истину.

📌 Задача
Есть база данных (буду рассматривать Oracle, но подход универсален).
Есть несколько приложений, которые её используют.
Нужно смоделировать архитектуру кода так, чтобы он был:
безопасен
контролируем
гибок
удобен и понятен

Согласитесь, задачка непростая.

🧩 Модель архитектуры
Предлагаю делить код на 3 уровня:

1) 🔹 API-уровень
Это «витрина» для приложения.
Для каждого приложения создаём свой пакет с методами (например, APPLICATION_API или APPLICATION_SRV).

Здесь можно:
проверить входящие параметры,
отдавать курсоры наружу,
вызвать операцию из следующего уровня.

Здесь нельзя:
менять данные в таблицах,
хранить бизнес-логику,
выполнять валидации.

Главная цель: разграничение прав доступа и первичная проверка входа.

2) 🔹 OPER-уровень

Здесь живёт бизнес-логика.
Каждый внешний метод = отдельная операция.

Допустим вызов одной операции из другой, но запрещено обращаться к API-методам отсюда.
Иногда уровень делят ещё на подуровни (например LVL1 и LVL2), если нужно предварительно распарсить входящие JSON/XML.

3) 🔹 Табличный уровень

Самый простой, но ключевой.
Это точка работы с одной строкой таблицы:

вставка,
обновление,
удаление,
чтение,
блокировка.

Здесь же дублируется табличная валидация (те же ограничения, что и на уровне таблицы в БД)
Заполнение и или обновление технических полей.

🚫 Запрещено вызывать другие пакеты. Этот уровень должен быть полностью изолирован.

Чуть позже я опубликую упрошенный вариант кода. Где на примере, мы продолжим разговор уже в обратном направлении от таблиц до API уровня.

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

💬 Если есть возражение, согласие или негодование — не сдерживайте себя👇
#CodeArchitecture
👍1👎1
"Там, где, сливаяся, шумят,
Обнявшись, будто две сестры,
Струи Арагвы и Куры" — М.Ю. Лермонтов 🌊💫

📌 Разбор задачи про объединение массивов 🛠️

Объединение массивов: Объедините два отсортированных массива в один отсортированный массив.

Даны два отсортированных массива с повторами:
set_1 = (1,2,2,5,7,9)
set_2 = (1,2,3,4,5,5,6,8)

Нужно получить массив: 1 2 3 4 5 6 7 8 9

🛠️ Алгоритм интуитивно понятен.
Идём по двум массивам одновременно,
в новый массив попадает только меньшее или равное значение.
Счетчики массивов либо по одному, либо по другому увеличиваются +1
sql
🔄
DECLARE
TYPE set_tab IS TABLE OF INTEGER;
set_1 set_tab := set_tab (1, 2, 2, 5, 7, 9);
set_2 set_tab := set_tab (1, 2, 3, 4, 5, 5, 6, 8);
index_1# INTEGER := 1;
index_2# INTEGER := 1;
newSet set_tab := set_tab ();
BEGIN
WHILE (index_1# <= set_1.COUNT OR index_2# <= set_2.COUNT) LOOP
IF index_1# > set_1.COUNT THEN
newSet.EXTEND;
newSet(newSet.COUNT) := set_2(index_2#);
index_2# := index_2# + 1;
CONTINUE;
END IF;
IF index_2# > set_2.COUNT THEN
newSet.EXTEND;
newSet(newSet.COUNT) := set_1(index_1#);
index_1# := index_1# + 1;
CONTINUE;
END IF;
IF set_1(index_1#) <= set_2(index_2#) THEN
IF newSet IS EMPTY OR newSet(newSet.COUNT) != set_1(index_1#) THEN
newSet.EXTEND;
newSet(newSet.COUNT) := set_1(index_1#);
END IF;

index_1# := index_1# + 1;
CONTINUE;
END IF;
IF set_1(index_1#) > set_2(index_2#) THEN
IF newSet IS EMPTY OR newSet(newSet.COUNT) != set_2(index_2#) THEN
newSet.EXTEND;
newSet(newSet.COUNT) := set_2(index_2#);
END IF;

index_2# := index_2# + 1;
CONTINUE;
END IF;
END LOOP;
DECLARE
str VARCHAR2(4000);
BEGIN
FOR i IN 1 .. newSet.COUNT LOOP
str := str ' ' TO_CHAR(newSet(i));
END LOOP;
DBMS_OUTPUT.put_line(str);
END;
END;
Сама по себе задача простая, но она перекликается с известным способом соединения таблиц MERGE 🔗

Merge Join работает именно по такому принципу: оба набора данных сначала сортируются, а потом сканируются «с двух концов» одновременно 🔄.
В чём отличие — он сравнивает не сами значения, а по условию равенства вычисляет хэш-функции (STANDARD_HASH('myValue', 'MD5')) 🧩.

Для новичков приведу пример:
SQL
CREATE TABLE table_1
(
id NUMBER
);
CREATE TABLE table_2
(
id NUMBER
);
INSERT INTO table_1 (id)
SELECT LEVEL AS id
FROM DUAL
CONNECT BY LEVEL < 10;
INSERT INTO table_2 (id)
SELECT LEVEL AS id
FROM DUAL
CONNECT BY LEVEL < 100;
SELECT /*+ LEADING (t1 t2) USE_MERGE(t2)*/
t1.id
FROM table_1 t1 INNER JOIN table_2 t2 ON (t1.id = t2.id);

Соединим таблицы и добавим хинт USE_MERGE, подсказывающий оптимизатору использовать MERGE JOIN:
SQL
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 54 | 8 | 00:00:01 |
| 1 | MERGE JOIN | | 9 | 54 | 8 | 00:00:01 |
| 2 | SORT JOIN | | 9 | 27 | 4 | 00:00:01 |
| 3 | TABLE ACCESS FULL | TABLE_1 | 9 | 27 | 3 | 00:00:01 |
| * 4 | SORT JOIN | | 99 | 297 | 4 | 00:00:01 |
| 5 | TABLE ACCESS FULL | TABLE_2 | 99 | 297 | 3 | 00:00:01 |
--------------------------------------------------------------------------

Видим, что Oracle отсортировал данные и применил алгоритм, похожий на наш первоначальный 👏

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

Пишите, если есть что писать. Ставьте эмодзи если вызывает эмоции👇
#sql #Oracle #PLSQL
👍8👎1🤓1
🎵 «Позабыты хлопоты
Остановлен бег
Вкалывают роботы
Счастлив человек!» — из к/ф "Приключения Электроника"

📌 Разбор задачи: Переворот строки

Сегодня у нас на разборе задача: 🔄 перевернуть слова в строке, сохраняя пробелы на своих местах.
⚙️ Важное ограничение: решение процедурными методами.

Исходная строка и нужный результат:

' 123 456 234 789 9 '
' 321 654 432 987 9 '

👉 Алгоритм:
Идём посимвольно по строке.
Если встречаем пробел — просто добавляем его в результат.
Если начинается слово — ищем его длину.
Переписываем слово в обратном порядке.
Двигаем указатель дальше.

📜 Решение на PL/SQL (первый вариант):
DECLARE
str VARCHAR2 (4000) := ' 123 456 234 789 9 ';
indexStart INTEGER;
newStr VARCHAR2 (4000);
index# INTEGER := 1;
BEGIN
WHILE index# < LENGTH (str) LOOP
IF SUBSTR (str, index#, 1) = ' ' THEN
newStr := newStr || ' ';
index# := index# + 1;
ELSE
indexStart := 0;

FOR j IN 1 .. LENGTH (str) LOOP
IF SUBSTR (str, index# + j - 1, 1) = ' ' THEN
EXIT;
ELSE
indexStart := indexStart + 1;
END IF;
END LOOP;

FOR q IN 1 .. indexStart LOOP
newStr := newStr || SUBSTR (str, index# + indexStart - q, 1);
END LOOP;

index# := index# + indexStart;
END IF;
END LOOP;

DBMS_OUTPUT.put_line (str);
DBMS_OUTPUT.put_line (newStr);
END;

🤖 Но машина оказалась умнее. ЧатGPT выдал решение оптимальнее.
Иногда так и хочется быть Джоном Коннором!
Сцену, когда Арни опускается в раскалённый металл, воспринимаю по-другому чем прежде.😉

📜 Оптимизированный вариант:
DECLARE
str VARCHAR2(4000) := ' 123 456 234 789 9 ';
word VARCHAR2(4000);
newStr VARCHAR2(4000) := '';
i PLS_INTEGER := 1;
ch CHAR(1);
BEGIN
WHILE i <= LENGTH(str) LOOP
ch := SUBSTR(str, i, 1);

IF ch = ' ' THEN
newStr := newStr || ch;
i := i + 1;
ELSE
word := '';
WHILE i <= LENGTH(str) AND SUBSTR(str, i, 1) <> ' ' LOOP
word := SUBSTR(str, i, 1) || word;
i := i + 1;
END LOOP;
newStr := newStr || word;
END IF;
END LOOP;

DBMS_OUTPUT.put_line(str);
DBMS_OUTPUT.put_line(newStr);
END;

Отличия от первого варианта:
🚫 Нет вложенного цикла для подсчёта длины слова.
🔄 Переворот делаем "на лету" при сборке.
📖 Код читается проще и выглядит короче.

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

🤔 А какое чувство к машинам испытываете вы? Поделитесь в комментах 👇
#sql #Oracle #PLSQL
👍4🤓2👾2👎1
🎵 «Хочешь сладких апельсинов?
Хочешь вслух рассказов длинных?
Хочешь, я взорву все звёзды
Что мешают спать?» — Земфира

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

Есть желание «вскрыть» содержимое wrapped-пакетов?
👉 https://www.codecrete.net/UnwrapIt/

Копируем и вставляем содержимое поля text.
⚠️В ресурс вставляем не сам запрос, а содержимое поля из выполненного в базе запроса:
SELECT  line, text
FROM ALL_SOURCE
WHERE owner = 'SYS'
AND name = 'DBMS_SCHEDULER'
AND type = 'PACKAGE BODY'
ORDER BY line;


И наслаждаемся «индусским» кодом 😎

Работает прямо в браузере, без установки.

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

💡 Если у вас есть свои полезные ссылки — не жадничайте, делитесь в комментариях!
#tools #Oracle #PLSQL
🆒4👎1
🎵 «Труд мой
    любому
        труду
           родствен.
Взгляните —
      сколько я потерял,
какие
   издержки
        в моем производстве
и сколько тратится
         на материал.»
— В.В. Маяковский

📌 Разбор задачи: Максимальный квадрат (процедурными методами)

🧩 Условие:
Дана двоичная матрица (элементы 0 и 1).
Нужно найти самый большой квадрат, состоящий только из единиц.

🧩 Пример матрицы

0 1 1 0 1 0 1
1 1 1 1 1 0 0
0 1 1 1 1 1 0
0 1 1 1 1 1 0
0 0 1 1 1 0 0

🔵 В этой матрице максимальный квадрат состоит из 3x3 единиц (сверху слева от позиции (2,2)):

0 1 1 0 1 0 1
1 🔵 🔵 🔵 1 0 0
0 🔵 🔵 🔵 1 1 0
0 🔵 🔵 🔵 1 1 0
0 0 1 1 1 0 0

💻 Решение (PL/SQL):
SQL
DDECLARE
TYPE row_type IS TABLE OF NUMBER;

TYPE matrix_type IS TABLE OF row_type;

matrix matrix_type
:= matrix_type (row_type (0, 1, 1, 0, 1, 0, 1),
row_type (1, 1, 1, 1, 1, 0, 0),
row_type (0, 1, 1, 1, 1, 1, 0),
row_type (0, 1, 1, 1, 1, 1, 0),
row_type (0, 0, 1, 1, 1, 0, 0));

TYPE square IS RECORD
(
i INTEGER,
j INTEGER,
side_amount INTEGER
);

maxSquare square;
currentSquare square;
BEGIN
FOR i IN 1 .. matrix.COUNT LOOP
FOR j IN 1 .. matrix (i).COUNT LOOP
IF matrix (i) (j) = 1 THEN
currentSquare.i := i;
currentSquare.j := j;

--DBMS_OUTPUT.put_line ('--');
--DBMS_OUTPUT.put_line ('(' || TO_CHAR (i) || ', ' || TO_CHAR (j) || ')');

DECLARE
maxSide INTEGER := 1;
skip EXCEPTION;
BEGIN
WHILE maxSide <= LEAST (matrix.COUNT - i, matrix (i).COUNT - j) LOOP
/*
j
i 1 1 1 ..
1 1 0 ..
1 1 1 ..
. . . ..
*/
FOR q IN 0 .. maxSide LOOP
--DBMS_OUTPUT.put_line ('(' || TO_CHAR (i + maxSide) || ', ' || TO_CHAR (j + q) || ')');
--DBMS_OUTPUT.put_line ('(' || TO_CHAR (i + q) || ', ' || TO_CHAR (j + maxSide) || ')');

IF matrix (i + maxSide) (j + q) = 0 OR matrix (i + q) (j + maxSide) = 0 THEN
RAISE skip;
END IF;
END LOOP;

maxSide := maxSide + 1;
END LOOP;

currentSquare.side_amount := GREATEST (NVL (currentSquare.side_amount, 0), maxSide);
EXCEPTION
WHEN skip THEN
currentSquare.side_amount := GREATEST (NVL (currentSquare.side_amount, 0), maxSide);
END;

IF NVL (maxSquare.side_amount, 0) < currentSquare.side_amount THEN
maxSquare := currentSquare;
END IF;

currentSquare := NULL;
END IF;
END LOOP;
END LOOP;

DBMS_OUTPUT.put_line (
'side_amount = ' || TO_CHAR (maxSquare.side_amount) || ', i = ' || TO_CHAR (maxSquare.i) || ', j = ' || TO_CHAR (maxSquare.j));
END;

Итог: выводит координаты i, j и размер стороны найденного квадрата.

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

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

#️⃣ #sql #Oracle #PLSQL
👍7🤔2👎1