sql_ready | Unsorted

Telegram-канал sql_ready - SQL Ready | Базы Данных

7786

Авторский канал про Базы Данных и SQL Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_it

Subscribe to a channel

SQL Ready | Базы Данных

☕️ Смотрите что нашел — Explainshell.

Вводишь любую команду в терминале, и он по частям объясняет, что делает каждая часть. Не просто man-ка, а понятный синтакс-анализ.

Особенно кайф для тех, кто работает в Linux/DevOps/CI среде и хочет разбираться, а не наугад копипастить из StackOverflow.

📌 Оставляю ссылочку: explainshell.com

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

📂 Напоминалка по нормальным формам баз данных!

Например, 1NF требует атомарных значений, 2NF убирает частичные зависимости, а 3NF избавляет от транзитивных зависимостей.

На картинке — основные нормальные формы, которые полезны при проектировании SQL-баз данных.

Сохрани, чтобы не забыть!

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

Оконные фреймы (ROWS и RANGE) — управление областью расчёта!

В оконных функциях важны не только PARTITION BY и ORDER BY, но и то, какие строки попадают в окно вычисления. За это отвечают оконные фреймы — ROWS и RANGE.

Таблица операций:

transactions(id, user_id, amount, created_at)


Накопительная сумма по пользователю:
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY created_at
)


При наличии ORDER BY и отсутствии явного фрейма в большинстве СУБД используется RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.

Если значения created_at совпадают, сумма считается сразу по группе строк (peers).

Среднее по последним 3 операциям:
AVG(amount) OVER (
PARTITION BY user_id
ORDER BY created_at, id
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
)


ROWS формирует окно по количеству строк. Детерминированный ORDER BY необходим для воспроизводимого результата.

Сумма за последние 7 дней (пример для PostgreSQL):
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY created_at
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW
)


RANGE формирует окно по значениям ORDER BY. Поддержка и синтаксис интервалов зависят от СУБД; часто допускается только одно выражение в ORDER BY.

🔥 Важно помнить: ROWS — предсказуем по количеству строк, RANGE — удобен для временных интервалов и peer-групп, неявный фрейм может менять результат вычислений

➡️ SQL Ready | #практика

Читать полностью…

SQL Ready | Базы Данных

Как выполнять сложные data-миграции, не ломая UNIQUE-ограничения?

Иногда нужно массово менять данные, временно нарушая UNIQUE, но итоговое состояние корректно:

BEGIN;


Включаем отложенную проверку конкретного ограничения, оно проверится только при COMMIT:
SET CONSTRAINTS users_email_uniq DEFERRED;


Теперь можно выполнять UPDATE/INSERT, даже если в процессе появляются дубликаты:
UPDATE users
SET email = lower(email);


Фиксация происходит один раз, если финальное состояние валидно, транзакция коммитится:
COMMIT;


Если уникальность нарушена в конце будет ROLLBACK, без частичных изменений.

🔥 Отложенные ограничения позволяют безопасно менять данные внутри транзакции, сохраняя целостность и контроль без временного снятия UNIQUE.

➡️ SQL Ready | #совет

Читать полностью…

SQL Ready | Базы Данных

UNION и UNION ALL — когда строки исчезают без ошибок!

UNION удаляет дубликаты по всем выбранным колонкам, сравнивая строки целиком, поэтому результат может отличаться от ожиданий.

SELECT message FROM logs_app
UNION
SELECT message FROM logs_system;


Одинаковые message схлопнутся в одну строку, даже если это разные события — ошибки не будет.

UNION ALL не удаляет строки из результата запросов, поэтому почти всегда работает быстрее:
SELECT message FROM logs_app
UNION ALL
SELECT message FROM logs_system;


Для логов полезно явно добавлять источник:
SELECT 'app' AS src, id, message FROM logs_app
UNION ALL
SELECT 'system' AS src, id, message FROM logs_system
ORDER BY src, id;


Пересечение (INTERSECT) в MySQL не поддерживается, альтернатива:
SELECT DISTINCT a.message
FROM logs_app a
WHERE EXISTS (SELECT 1 FROM logs_system s WHERE s.message = a.message)
LIMIT 10;


Если хотите увидеть, какие строки пропадут при UNION из-за совпадений между таблицами, используйте UNION ALL и проверяйте строки, встречающиеся в обоих источниках:
SELECT message, COUNT(*) AS cnt, COUNT(DISTINCT src) AS sources
FROM (
SELECT message, 'app' AS src FROM logs_app
UNION ALL
SELECT message, 'system' AS src FROM logs_system
) t
GROUP BY message
HAVING sources > 1;


🔥 UNION — когда нужна дедупликация, UNION ALL — когда важна каждая строка и производительность.

➡️ SQL Ready | #практика

Читать полностью…

SQL Ready | Базы Данных

Сравнение наборов и вычисление пересечения по ключу!

Нужно быстро получить пересечение двух таблиц по ключу? Используйте JOIN по колонке, которая логически должна совпадать в обоих наборах:

JOIN table_b b ON a.id = b.id


Выберите только ключи, чтобы исключить лишние проекции и ускорить анализ наборов:
SELECT a.id


Проверьте сами на интерактивном примере:
CREATE TABLE table_a(id int);
CREATE TABLE table_b(id int);
INSERT INTO table_a VALUES (1),(2),(3);
INSERT INTO table_b VALUES (2),(3),(4);


Повторите исходный JOIN, вы увидите только 2,3 как пересечение наборов.

🔥 Пригодится, когда нужно сверить итерации, найти общие сущности перед миграциями и тд.

➡️ SQL Ready | #совет

Читать полностью…

SQL Ready | Базы Данных

Find + Update в одном проходе!

Найдём строки с невозможными значениями и сразу подготовим их к UPDATE:

WITH c AS (SELECT id FROM products WHERE price < 0 LIMIT 3)


CTE c вернёт проблемные id.

Теперь обновим только найденные строки, не дублируя условие в клиенте:
UPDATE products SET price = 0


Ссылаемся на выборку из CTE, чтобы не рассинхронизировать find/update:
WHERE id IN (SELECT id FROM c);


Попробуйте сами на тестовой таблице:
CREATE TABLE products(id int PRIMARY KEY, price int);
INSERT INTO products VALUES (1,-10),(2,-5),(3,100);


Запустите find+update снова и убедитесь, что исправление точечное и атомарное.

🔥 Экономит время выравнивания таблиц при интеграциях, корректировки классификаторов или миграций и др.

➡️ SQL Ready | #совет

Читать полностью…

SQL Ready | Базы Данных

Транзакция, которая берёт только свободные строки!

В конкурентной обработке задач важно не брать строку, уже заблокированную другой транзакцией, и не ждать её освобождения.

PostgreSQL делает это через:

FOR UPDATE SKIP LOCKED


Строки, которые уже залочены, исключаются из выборки немедленно, без ожидания.

Хотите повторить интерактивно? Откройте 2 SQL-сессии:
Сессия A:
BEGIN;
SELECT id FROM jobs WHERE status='queued' FOR UPDATE;


Сессия B (параллельно):
BEGIN;
SELECT id FROM jobs WHERE status='queued' FOR UPDATE SKIP LOCKED LIMIT 1;


B никогда не вернёт строку, залоченную A, и не будет ждать, вы увидите разные результаты в разных сессиях.

🔥 База данных сама может быть механизмом синхронизации, если выборка делается с SKIP LOCKED. Нет двойной обработки, ожиданий и внешних зависимостей.

➡️ SQL Ready | #совет

Читать полностью…

SQL Ready | Базы Данных

Транзакции в SQL — почему частичные изменения хуже ошибки!

Работа с несколькими связанными таблицами в одном сценарии должна быть атомарной. Без TRANSACTION при автокоммите каждый UPDATE фиксируется отдельно — и есть риск получить несогласованное состояние.

Таблицы:

wallet(user_id, balance)
payments(id, user_id, amount, status)


Сценарий: списать баланс и перевести платёж в done.

Если соединение обрывается между запросами:
UPDATE wallet SET balance = balance - 100 WHERE user_id = 7;
-- сбой соединения тут
UPDATE payments SET status = 'done' WHERE id = 55;


Первое изменение уже сохранено, второе — нет. Итог: деньги списаны, платёж не подтверждён.

Правильный подход — транзакция:
BEGIN;
UPDATE wallet SET balance = balance - 100 WHERE user_id = 7;
UPDATE payments SET status = 'done' WHERE id = 55;
COMMIT;


До COMMIT изменения видны только в текущей сессии и не становятся устойчивыми. Если все шаги успешны — фиксируется сразу всё.

Откат при ошибке:
BEGIN;
UPDATE wallet SET balance = balance - 100 WHERE user_id = 7;
UPDATE payments SET status = 'done' WHERE id = 55;
ROLLBACK;


Пока сессия жива, открытая транзакция не откатывается сама по себе и может удерживать блокировки. Автоматический ROLLBACK происходит именно при разрыве соединения или явном откате.

Нюанс для защиты от гонок и повторной обработки:
UPDATE wallet
SET balance = balance - 100
WHERE user_id = 7 AND balance >= 100;

UPDATE payments
SET status = 'done'
WHERE id = 55 AND status = 'pending';


Оба UPDATE выполняем в одной транзакции, затем проверяем rowcount: если любой запрос затронул 0 строк — ROLLBACK и обработка как ошибка.

🔥 SELECT ... FOR UPDATE также работает только в той же транзакции, где будет обновление. Любая операция списания + фиксации платежа = транзакция.

➡️ SQL Ready | #практика

Читать полностью…

SQL Ready | Базы Данных

UPDATE без холостых перезаписей!

Обычный UPDATE может перезаписывать строку, даже если значение не изменилось.

IS DISTINCT FROM сравнивает значения NULL-безопасно и без UNKNOWN:

AND u.name IS DISTINCT FROM 'Alice';


Если name уже Alice — условие ложно, и строка не обновляется вообще.

Это правило можно применить к любым колонкам:
AND u.email IS DISTINCT FROM EXCLUDED.email;


Паттерн остаётся тем же: обновление только при расхождении.

Проверьте сами на тесте:
CREATE TABLE users(id int PRIMARY KEY, name text);
INSERT INTO users VALUES (1, 'Bob');


Запустите 2 раза UPDATE сверху, второй раз таблица не изменится и ничего не запишет.

🔥 UPDATE должен менять только то, что отличается и SQL уже даёт для этого инструменты.

➡️ SQL Ready | #совет

Читать полностью…

SQL Ready | Базы Данных

Всех с наступающим 2026 годом! 🎄

Поставлю себе цель: набрать более 20 тысяч подписчиков ✍️

Читать полностью…

SQL Ready | Базы Данных

Знали, что NOT IN — главный источник тихих багов, если в подзапросе есть NULL?

Этот запрос выглядит логично, но опасен:

WHERE user_id NOT IN (SELECT id FROM users);


Если подзапрос вернёт хотя бы один NULL, условие станет UNKNOWN, и не вернётся ни одной строки.

Правильнее и безопаснее паттерн NOT EXISTS:
WHERE NOT EXISTS (SELECT 1 FROM users u WHERE u.id = orders.user_id);


Он корректно работает при NULL, не ломает логику и читается однозначно.

Хотите повторить? Проверьте разницу сами:
CREATE TABLE users(id int);
INSERT INTO users VALUES (1), (2), (NULL);


Теперь запустите:
SELECT 1 AS test WHERE 3 NOT IN (SELECT id FROM users);


Результат будет пустым.

🔥 А если переписать через NOT EXISTS, логика вернётся в норму.

➡️ SQL Ready | #совет

Читать полностью…

SQL Ready | Базы Данных

🖥 PostgreSQL: работа с массивами!

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

➡️ SQL Ready | #шпора

Читать полностью…

SQL Ready | Базы Данных

Оконные функции ROW_NUMBER() в SQL — нумерация строк для аналитики!

ROW_NUMBER() присваивает уникальный порядковый номер строкам внутри логического окна. Данные не объединяются в группы, строки остаются как есть — это ключевое отличие от GROUP BY.

Таблица:

payments(id, user_id, amount, created_at)


Нумеруем платежи каждого пользователя. Окно создаётся по user_id, а нумерация идёт по дате от старых к новым:
SELECT id, user_id, amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at ASC) AS rn
FROM payments;


PARTITION BY разбивает данные на сегменты (в данном случае — по пользователю). ORDER BY внутри OVER() задаёт, в каком порядке будут присваиваться номера.

Чтобы найти последний платёж каждого пользователя, меняем порядок сортировки на DESC. Самая свежая запись получит номер 1 в своём окне:
WITH t AS (
SELECT id, user_id, amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM payments
)
SELECT * FROM t WHERE rn = 1;


Здесь используем CTE (WITH), чтобы сначала пронумеровать строки, а затем отфильтровать только нужный номер.

Следующий пример — логины пользователей:
auth_logs(id, user_id, ip, login_at)


Ищем первый вход каждого пользователя с каждого IP:
WITH t AS (
SELECT id, user_id, ip,
ROW_NUMBER() OVER (PARTITION BY user_id, ip ORDER BY login_at ASC) AS rn
FROM auth_logs
)
SELECT * FROM t WHERE rn = 1;


🔥 ROW_NUMBER() подходит, когда нужен номер строки в сегменте, важно выбрать первую/последнюю запись по логике сортировки,требуется топ-N по категориям или пользователям.

➡️ SQL Ready | #практика

Читать полностью…

SQL Ready | Базы Данных

☕️ Postgrespro — полная и актуальная документация по PostgreSQL!

SQL-синтаксис, индексы, транзакции, планировщик запросов, репликация, расширения и внутренняя архитектура. Здесь подробно описано не только как писать запросы, но и как база данных работает. Полезно, если используешь Postgres в продакшене или хочешь глубже разобраться в производительности.

📌 Оставляю ссылочку: postgrespro.ru

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

EXPLAIN — узнаём, как СУБД реально выполняет ваш запрос!

Команда EXPLAIN показывает, что делает база данных под капотом при выполнении SQL-запроса. Это помогает найти узкие места и понять, почему запрос может работать медленно.

Допустим, у нас есть запрос:

SELECT * FROM books WHERE author = 'Толстой';


Запускаем EXPLAIN, чтобы посмотреть план выполнения:
EXPLAIN SELECT * FROM books WHERE author = 'Толстой';


Если видим Table Scan или Seq Scan — это означает, что происходит полный просмотр таблицы. Такой способ может быть медленным, особенно при большом объёме данных.

Чтобы ускорить выполнение, создаём индекс:
CREATE INDEX idx_author ON books(author);


🔥 Теперь при повторном EXPLAIN запрос может использовать Index Scan, что значительно быстрее.

➡️ SQL Ready | #практика

Читать полностью…

SQL Ready | Базы Данных

Согласованное чтение данных внутри одной транзакции!

Иногда несколько SELECT должны работать с одной и той же версией данных, даже если параллельно идут изменения.

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;


В PostgreSQL это фиксирует MVCC snapshot в момент первого statement внутри транзакции:
SELECT COUNT(*) FROM orders;


Все последующие SELECT читают один и тот же snapshot и не видят новые COMMIT из других сессий, при этом собственные изменения транзакции видны:
SELECT SUM(amount) FROM orders;


Это гарантирует согласованность результатов между запросами без блокировки пишущих транзакций:
COMMIT;


🔥 После COMMIT snapshot освобождается, транзакция завершается.

➡️ SQL Ready | #совет

Читать полностью…

SQL Ready | Базы Данных

💡 ThoughtSpot SQL Tutorial — продуктивное обучение и практика!

Материал построен вокруг реальных примеров и практики: от базовых SELECT, JOIN и агрегатов до сложных подзапросов и аналитических функций. Пояснения идут шаг за шагом, с визуальными примерами и задачами, которые помогают сразу закрепить навыки на практике.

📌 Оставляю ссылочку: thoughtspot.com

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

☕️ Наткнулся на любопытную статью — «Embedded SQL с группировкой запросов: элегантный подход к управлению SQL в Go»!

В этой статье:
• Автор показывает, как хранить и группировать SQL-запросы в одном .sql-файле по сущностям;
• Рассказывается, как использовать go:embed для встраивания этих файлов в бинарник и загружать запросы по имени;
• Представлен парсер, который разбирает .sql с именованными секциями;
• Показаны преимущества подхода: подсветка SQL в IDE, чистый Go-код и др.


🔊 Продолжайте читать на Habr!


➡️ SQL Ready | #статья

Читать полностью…

SQL Ready | Базы Данных

📂 Напоминалка по структурам баз данных!

Например, Skiplist ускоряет поиск в памяти, Hash-индекс даёт доступ за O(1), а B-tree и LSM-дерево оптимизируют работу с диском и записью.

На картинке — 8 структур данных, которые стоит держать под рукой, чтобы понимать, как устроены индексы и хранение в БД.

Сохрани, чтобы не забыть!

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

❤️ SQL-EX — интерактивный тренажёр с задачами!

Если хочешь учиться через практику, этот сайт отличный инструмент. Здесь ты пишешь запросы прямо в браузере, получаешь мгновенную обратную связь и видишь результат. Подойдёт для закрепления теории, подготовки к собеседованию или реальных задач в работе с бд.

📌 Оставляю ссылочку: sql-ex.ru

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

❤️ 30-Days-of-SQL — тридцатидневный челлендж в обучении!

Репозиторий предлагает серию упражнений и заданий на каждый день, которые охватывают ключевые темы: выборки, агрегации, подзапросы, оконные функции и др. Формат один день - одна задача помогает не перегружаться и выстраивать структурированный план обучения. Отлично подходит для практики, подготовки к собеседованиям и укрепления знаний.

Оставляю ссылочку: GitHub 📱


➡️ SQL Ready | #репозиторий

Читать полностью…

SQL Ready | Базы Данных

📂 Напоминалка по HTTP-стеку для разработки и инфраструктуры!

Например, HTTP/2 ускоряет загрузку за счёт мультиплексирования, а HTTP/3 делает обмен ещё быстрее, используя QUIC поверх UDP — уже стандарт для современных браузеров и CDN.

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

Сохрани, чтобы не забыть!

SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

😎 PopSQL Learn — быстрый и структурный старт в SQL!

Если хочешь разобраться с SQL не по документации, а через чёткие объяснения и примеры запросов, этот сайт точно пригодится. Темы здесь идут от базовых до сложных концепций, всё с примерами, которые можно сразу повторить. Удобно, когда нужна практика для проектов или собеседований.

📌 Оставляю ссылочку: popsql.com

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

Партицирование в оконных функциях — когда нужно работать с топами внутри групп!

Оконные функции в SQL — стандартный инструмент аналитики. Один из частых кейсов: найти «лучшие» записи внутри каждой группы, не сворачивая таблицу и не теряя строки.

Представим таблицу:

products(id, category, price)


Нужно выбрать самый дорогой товар в каждой категории, но при этом видеть все колонки строки:
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC, id DESC) AS rn
FROM products
) t
WHERE rn = 1;


PARTITION BY формирует независимые окна для каждой категории.
ROW_NUMBER() нумерует строки внутри каждой партиции, а не по всей таблице.

Если нужны не только первые, а, например, топ-3 в каждой категории:
SELECT id, category, price
FROM (
SELECT
id, category, price,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC, id DESC) AS rn
FROM products
) t
WHERE rn <= 3
ORDER BY category, price DESC;


Тут важно: без PARTITION BY запрос взял бы топ-3 по всей таблице, а не по категориям.

Оконные функции не требуют GROUP BY, потому что не агрегируют (не схлопывают) строки, а дополняют их аналитическими метками.

Ещё полезный трюк — находить максимум в группе без GROUP BY и JOIN, через коррелированный подзапрос:
SELECT *
FROM products
WHERE price = (
SELECT MAX(price)
FROM products p2
WHERE p2.category = products.category
);


Этот запрос не агрегирует основную таблицу и вернёт несколько строк, если в категории есть товары с одинаковой максимальной ценой (ties).

🔥 Используйте PARTITION BY в оконных функциях, когда логика должна применяться внутри каждой группы независимо, а строки нужно сохранить целиком.

➡️ SQL Ready | #практика

Читать полностью…

SQL Ready | Базы Данных

📂 Напоминалка про Modem vs Router!

Модем отвечает за подключение к провайдеру и получение публичного IP, а роутер управляет локальной сетью: маршрутизирует трафик, выполняет NAT и раздаёт адреса устройствам.

На картинке — ключевые отличия и базовая схема работы.

Сохрани, чтобы не забыть!

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

❤️ Red Blob Games — полезный сайт для визуального понимания алгоритмов!

Если тебе сложно понять алгоритмы по учебникам — этот ресурс решает проблему. Автор объясняет графы, геометрию, деревья, меши из геймдева через понятные визуализации и интерактивы. Сложные идеи здесь раскладываются на простые шаги.

📌 Оставляю ссылочку: redblobgames.com

➡️ SQL Ready | #ресурс

Читать полностью…

SQL Ready | Базы Данных

Генерация фейковых данных для тестов с мгновенной вставкой в любую таблицу!

SQL понимает VALUES как виртуальную таблицу, поэтому можно писать много строк прямо в INSERT:

INSERT INTO users (id, email, name) VALUES
(1, 'alice@mail.com', 'Alice'),
(2, 'bob@mail.com', 'Bob'),
(3, 'carol@mail.com', 'Carol');


Это работает мгновенно, просто читается и не создаёт лишних объектов в базе.

Хотите протестировать в своем проекте? Создайте таблицу и вставьте строки выше:
CREATE TABLE users (
id int PRIMARY KEY,
email text,
name text
);


🔥 Пригодится, когда нужно быстро заполнить таблицу для проверки логики.

➡️ SQL Ready | #совет

Читать полностью…

SQL Ready | Базы Данных

Развернуть PostgreSQL в MWS Cloud Platform ⬜️ — быстрее, чем вспомнить пароль от pgAdmin. И точно быстрее, чем объяснить DevOps'у, зачем ещё одна база.

Всего несколько минут и у вас:

⏺️готовая база на сетевых или локальных дисках
⏺️постоянный primary endpoint
⏺️безопасное подключение через Private Link
⏺️автоматические бэкапы и обслуживания по твоему расписанию


🎄🎁 И грант до 10 000 ₽ на запуск — чтобы точно не пришлось вспоминать, как настраивать failover вручную.

➡️Развернуть кластер

Читать полностью…

SQL Ready | Базы Данных

☕️ На Хабре вышла подробная статья про автоматизацию развертывания PostgreSQL-кластеров в изолированной инфраструктуре.

В этой статье:
• Показан реальный подход к автоматическому развёртыванию PostgreSQL в закрытом контуре;
• Разбирается поддержка нескольких ОС, версий СУБД и схем отказоустойчивости;
• Описана автоматическая проверка соответствия требованиям архитектуры;
• Приведён практический кейс внедрения, рассчитанный на эксплуатацию в крупных корпоративных системах.

🔊 Продолжайте читать на Habr!


➡️ SQL Ready | #статья

Читать полностью…
Subscribe to a channel