7786
Авторский канал про Базы Данных и SQL Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_it
✍️ PostgreSQL Querying — практическое изучение SQL на PostgreSQL!
Здесь подробно разбираются запросы, работа с данными, фильтрация, JOIN’ы, агрегации и другие конструкции, которые постоянно используются в разработке. Материал подаётся последовательно и на примерах, поэтому намного проще понять логику запросов и научиться писать их самостоятельно.
Оставляю ссылочку: GitHub 📱
❤️ PostgreSQL Tutorial — подробная документация и учебник по PostgreSQL!
На сайте собрана большая база материалов по PostgreSQL: установка, настройка, SQL-запросы, работа с таблицами, индексами, функциями, транзакциями и администрированием базы данных. Материал подаётся последовательно. Отличный ресурс как для новичков, так и для разработчиков, которым нужен удобный справочник с примерами и практическими объяснениями.
📌 Оставляю ссылочку: postgresql.leopard.in.ua
➡️ SQL Ready | #ресурс
✍️ Информативная статья вышла на Хабре: «Обратная сторона массивов в PostgreSQL»!
В этой статье:• Разбирается, почему массивы в PostgreSQL — это отдельная модель хранения со своими ограничениями и компромиссами;
• Показываются скрытые проблемы массивов: потеря ссылочной целостности, особенности GIN-индексов, TOAST, MVCC и дорогостоящие обновления;
• Объясняется, как правильно работать с массивами, когда использовать JSONB, intarray, pgvector и в каких случаях массивы действительно оправданы.
🔊 Продолжайте читать на Habr!
🎓 PostgreSQL должен быть спроектирован до первого инцидента.
В enterprise-среде PostgreSQL — это уже не просто база данных, а часть критичной инфраструктуры с требованиями к доступности, восстановлению, безопасности и мониторингу.
На курсе «Архитектура PostgreSQL для корпоративных систем» разбираем, как строить устойчивую PostgreSQL-инфраструктуру под реальные корпоративные нагрузки.
📌 В фокусе курса:
• High Availability (высокая доступность)
• Disaster Recovery (аварийное восстановление)
• failover-сценарии
• RPO/RTO
• резервирование и мониторинг
• архитектурные ошибки и точки отказа
🐾 АИСыч напоминает: б бы «Backup — это не стратегия. Стратегия — это проверенное восстановление».
SQLREADY20
Почему индекс может не использоваться, даже если он есть!
Очень частая ситуация: индекс создан, запрос написан вроде нормально, но SQL всё равно делает Seq Scan или Full Table Scan.
Имеем такую таблицу:
users(
id,
email,
created_at
)
CREATE INDEX idx_users_email
ON users(email);
SELECT *
FROM users
WHERE email = 'test@example.com';
SELECT *
FROM users
WHERE LOWER(email) = 'test@example.com';
email. А в условии используется выражение:LOWER(email)
LOWER() к строкам; сравнивать результат; читать гораздо больше данных, чем ожидалось.expression/functional index:CREATE INDEX idx_users_email_lower
ON users(LOWER(email));
SELECT *
FROM orders
WHERE DATE(created_at) = '2025-01-10';
DATE(created_at)
created_at может не помочь, потому что функция применяется к колонке.SELECT *
FROM orders
WHERE created_at >= '2025-01-10'
AND created_at < '2025-01-11';
WHERE user_id::text = '100'
user_id — integer, то здесь преобразование применяется к колонке. В такой ситуации обычный индекс по user_id может не использоваться.WHERE user_id = 100
WHERE user_id = '100'
LIKE:WHERE email LIKE '%gmail.com'
100–1000 строк, то Full Scan может быть дешевле, чем прыжки по индексу.LIKE, OR-условия и устаревшая статистика могут сделать индекс бесполезным или менее выгодным для оптимизатора.
📂 Напоминалка по оптимизации баз данных!
Например, индексы ускоряют поиск данных, а репликация помогает распределять нагрузку и повышать отказоустойчивость.
На картинке — 9 основных подходов для улучшения производительности базы данных, которые полезно держать под рукой.
Сохрани, чтобы не потерять!
➡️ SQL Ready | #ресурс
Почему NOT IN может вернуть пустой результат из-за NULL!
Одна из самых неприятных ловушек SQL — поведение NOT IN при наличии NULL. Запрос выглядит абсолютно корректно, но внезапно перестаёт возвращать строки.
Таблицы:
users(id)
bans(user_id)
bans. Часто пишут так:SELECT *
FROM users
WHERE id NOT IN (
SELECT user_id
FROM bans
);
bans.user_id нет NULL — всё работает нормально. Но представим данные:bans
-------
1
2
NULL
0 rows
id <> 1
AND id <> 2
AND id <> NULL
id <> NULL
WHERE проходят только TRUE. Из-за этого всё условие целиком перестаёт выполняться.NULL = 1
NULL <> 1
NULL = NULL
NOT IN с NULL внутри подзапроса становится опасным.NOT EXISTS:SELECT *
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM bans b
WHERE b.user_id = u.id
);
EXISTS работает нормально: сравнение идёт построчно; NULL не ломает всю проверку; оптимизатор обычно хорошо превращает это в anti-join.SELECT *
FROM users
WHERE id NOT IN (
SELECT user_id
FROM bans
WHERE user_id IS NOT NULL
);
NOT EXISTS обычно считается более безопасным и читаемым решением.NOT IN () и NOT EXISTS могут давать разные планы выполнения в зависимости от СУБД. Но логически для nullable данных: NOT EXISTS почти всегда предпочтительнее.SELECT COUNT(*)
FROM bans
WHERE user_id IS NULL;
NOT IN уже потенциально опасен.NOT IN и NULL плохо сочетаются. Если в подзапросе появляется хотя бы один NULL, условие может перестать возвращать строки вообще. Для таких проверок надёжнее использовать NOT EXISTS.
BUFFERS: как увидеть цену запроса?
Обычный EXPLAIN ANALYZE показывает время выполнения, но почти ничего не говорит о том, сколько данных было прочитано с диска и из памяти.
EXPLAIN (ANALYZE, BUFFERS)
SELECT ...
BUFFERS добавляет статистику по страницам памяти:shared hit — страницы найдены в PostgreSQL shared buffers.shared read — страницы пришлось загрузить в shared buffers; это может быть физический диск или OS page cache.Buffers: shared hit=15000 read=2
Buffers: shared hit=10 read=15000
execution time могут иметь абсолютно разную нагрузку на систему.
Генерация пропущенных дат в отчётах: почему LEFT JOIN не помогает!
Классическая задача — построить отчёт по дням. Например, количество заказов по датам. И внезапно оказывается, что в выдаче нет некоторых дней.
Таблица:
orders(id, created_at)
SELECT
DATE(created_at) AS day,
COUNT(*) AS orders_count
FROM orders
GROUP BY DATE(created_at)
ORDER BY day;
LEFT JOIN сама по себе не помогает — без таблицы дат нечего достраивать. Правильный подход — сначала явно сгенерировать календарь, а потом присоединить данные.generate_series:WITH calendar AS (
SELECT generate_series(
DATE '2026-01-01',
DATE '2026-01-10',
INTERVAL '1 day'
)::date AS day
)
SELECT
c.day,
COUNT(o.id) AS orders_count
FROM calendar c
LEFT JOIN orders o
ON DATE(o.created_at) = c.day
GROUP BY c.day
ORDER BY c.day;
COUNT(o.id) вернёт 0.COUNT(o.id), а не COUNT(*): COUNT(*) посчитает строку календаря даже без совпадений — всегда ≥ 1; COUNT(o.id) считает только реальные заказы — корректный 0 при отсутствии данных.JOIN: ON DATE(o.created_at) = c.day. Так писать удобно, но это ломает использование индекса по created_at.ON o.created_at >= c.day
AND o.created_at < c.day + INTERVAL '1 day'
SELECT
c.day,
COUNT(o.id) AS orders_count,
SUM(COUNT(o.id)) OVER (ORDER BY c.day) AS running_total
FROM calendar c
LEFT JOIN orders o
ON o.created_at >= c.day
AND o.created_at < c.day + INTERVAL '1 day'
GROUP BY c.day
ORDER BY c.day;
WITH bounds AS (
SELECT
MIN(created_at)::date AS min_day,
MAX(created_at)::date AS max_day
FROM orders
),
calendar AS (
SELECT generate_series(min_day, max_day, INTERVAL '1 day')::date AS day
FROM bounds
)
SELECT ...
created_at — timestamptz, границы дня зависят от timezone.
LAG() в SQL — сравниваем строку с предыдущей без JOIN!
Оконная функция LAG() позволяет получить значение из предыдущей строки внутри группы. Это один из самых полезных инструментов для аналитики, логов и временных рядов.
Таблица:
payments(id, user_id, amount, created_at)
Посмотрим предыдущий платёж каждого пользователя:
SELECT
user_id,
amount,
LAG(amount) OVER (
PARTITION BY user_id
ORDER BY created_at
) AS prev_amount
FROM payments;
LAG() сдвигает значение на одну строку назад внутри окна пользователя.SELECT
user_id,
amount,
amount - LAG(amount) OVER (
PARTITION BY user_id
ORDER BY created_at
) AS diff
FROM payments;
SELECT *
FROM (
SELECT
user_id,
status,
LAG(status) OVER (
PARTITION BY user_id
ORDER BY created_at
) AS prev_status
FROM user_logs
) t
WHERE status <> prev_status;
LAG() часто используют для анализа трендов, поиска изменений, расчёта дельт и работы с временными рядами.
Исключающие ограничения: гарантия отсутствия пересечений на уровне БД!
Проверять пересекаются ли интервалы через SELECT перед INSERT — классическая ошибка, которая ломается при конкурентных транзакциях.
SELECT 1
FROM bookings
WHERE room_id = 101
AND start_at < $end
AND end_at > $start;
CREATE EXTENSION IF NOT EXISTS btree_gist;
ALTER TABLE bookings
ADD CONSTRAINT no_overlap
EXCLUDE USING gist (
room_id WITH =,
tsrange(start_at, end_at) WITH &&
);
exclusion constraint — это способ переложить сложную логику (пересечения, диапазоны, уникальность по условиям) с приложения на уровень СУБД с полной защитой от состояния гонки.
Диапазоны как тип данных: убираем двойные условия и баги!
Когда работаешь с интервалами, обычно пишут два условия, и почти всегда где-то ошибаются с границами:
WHERE created_at <= now()
AND shipped_at > now()
tstzrange(created_at, shipped_at)
@> now()
@> проверяет содержит ли диапазон значение, заменяя сразу два условия.created_at <= x AND shipped_at > x:CREATE INDEX ON orders USING GIST (tstzrange(created_at, shipped_at));
GiST-индексом такие запросы работают эффективно даже на больших объёмах.range-типы — это способ убрать класс ошибок с датами и упростить сложную логику.
😍 SQL PostgreSQL Course — информативный курс по SQL с практикой!
Это структурированное обучение по PostgreSQL: от базовых запросов до JOIN’ов, подзапросов и проектирования базы данных. Материал выстроен последовательно, поэтому легко идти шаг за шагом и не теряться в теме. Здесь есть практика с заданиями и ответами, за счёт чего обучение закрепляется.
Оставляю ссылочку: GitHub 📱
Дубликаты после JOIN — откуда берутся и как контролировать!
Одна из частых проблем — внезапное размножение строк после JOIN. Это базовое поведение, если не учтена кардинальность связей.
Таблицы:
orders(id, customer_id, amount)
payments(id, order_id, status)
SELECT
o.id,
o.amount,
p.status
FROM orders o
LEFT JOIN payments p
ON p.order_id = o.id;
orders — payments. То есть один заказ повторится столько раз, сколько у него записей в payments.SELECT
COUNT(*) AS total_orders
FROM orders o
LEFT JOIN payments p
ON p.order_id = o.id;
COUNT(*) считает строки уже после JOIN, а не заказы. Если у заказа 3 платежа — он попадёт в счёт 3 раза. Это одна из самых частых причин кривых метрик.SELECT
COUNT(DISTINCT o.id) AS total_orders
FROM orders o
LEFT JOIN payments p
ON p.order_id = o.id;
DISTINCT — это дополнительная операция, и на больших объёмах она может стоить дорого.payments, JOIN здесь вообще лишний. Лучше контролировать кардинальность до JOINEXISTS:SELECT
o.id,
o.amount
FROM orders o
WHERE EXISTS (
SELECT 1
FROM payments p
WHERE p.order_id = o.id
AND p.status = 'success'
);
EXISTS работает как semi-join: он проверяет факт наличия строки, но не тянет её в результат. За счёт этого одна строка заказа остаётся одной строкой.JOIN всё-таки нужен — агрегируем заранее:SELECT
o.id,
o.amount,
COALESCE(p.has_success, 0) AS has_success
FROM orders o
LEFT JOIN (
SELECT
order_id,
MAX(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS has_success
FROM payments
GROUP BY order_id
) p ON p.order_id = o.id;
payments к одной строке на order_id, и только потом делаем JOIN. После этого результат становится понятным: одна строка на заказ, без раздувания.GROUP BY o.id, o.amount, p.status
GROUP BY просто фиксирует текущую детализацию. Если у заказа было несколько статусов — строки никуда не денутся.JOIN не создаёт дубликаты сам по себе. Он возвращает строки в соответствии с числом совпадений по условию ON. Если после JOIN строк стало больше — значит реальная связь между таблицами не 1:1, а 1:N или даже N:M.
☕️ Полезную статью нашёл на Хабре: «Ваш RAG не умеет думать. А мой умеет»!
В этой статье:• Разбирается, почему классические RAG-системы на базе векторных БД часто ограничиваются поверхностным поиском;
• Показан подход HippoRAG 2, где память и связи между фактами организованы ближе к графовой модели;
• Объясняется, как эволюционирует работа с данными;
• Рассматривается, как это влияет на SQL/NoSQL слой, архитектуру хранения и построение более осмысленных запросов к данным.
🔊 Продолжайте читать на Habr!
NOT VALID constraints — как добавить CHECK и FOREIGN KEY на huge таблицу без долгой блокировки?
Обычно добавление CHECK или FOREIGN KEY на большую таблицу рискованная операция, потому что PostgreSQL начинает сразу сканировать все старые данные.
ALTER TABLE orders
ADD CONSTRAINT orders_user_fk
FOREIGN KEY (user_id)
REFERENCES users(id);
NOT VALID:ALTER TABLE orders
ADD CONSTRAINT orders_price_check
CHECK (price > 0)
NOT VALID;
ALTER TABLE orders
VALIDATE CONSTRAINT orders_price_check;
VALIDATE CONSTRAINT не блокирует обычный concurrent DML как классический ALTER TABLE.FOREIGN KEY:ALTER TABLE orders
ADD CONSTRAINT orders_user_fk
FOREIGN KEY (user_id)
REFERENCES users(id)
NOT VALID;
🖥 Разбираем методы управления пользователями и правами!В этой шпаргалке собраны ключевые команды для создания, изменения и удаления пользователей, назначения и отзыва прав, а также проверки текущих ролей и сессий. Они применяются при управлении безопасностью базы данных, настройке доступа и аналитической работе с ролями.
➡️ SQL Ready | #шпора
PostgreSQL умеет обновлять только реально изменившиеся строки и это может сильно сократить WAL и нагрузку!
Многие приложения делают UPDATE даже тогда, когда данные вообще не изменились.
UPDATE users
SET name = 'Alex'
WHERE id = 1;
name уже равен 'Alex', PostgreSQL всё равно создаст новую версию строки (MVCC), запишет WAL, обновит индексы и увеличит нагрузку на autovacuum.SELECT n_tup_upd
FROM pg_stat_user_tables
WHERE relname = 'users';
UPDATE, можно сравнивать старые и новые значения прямо в WHERE:UPDATE users
SET
name = $1,
email = $2
WHERE id = $3
AND (name, email) IS DISTINCT FROM ($1, $2);
IS DISTINCT FROM безопасно сравнивает даже NULL значения, в отличие от обычного !=:SELECT
NULL = NULL,
NULL IS DISTINCT FROM NULL;
WAL, bloat, количество HOT/non-HOT update и нагрузку на autovacuum без изменения архитектуры.
😎 SQL Cheatsheet — удобная шпаргалка для повседневной работы!
Этот репозиторий хорошо подойдёт тем, кто постоянно работает с базами данных и хочет быстро освежать в памяти нужные SQL-конструкции. Здесь всё подано компактно и по делу: запросы, JOIN’ы, агрегации, подзапросы и др. Особенно удобно использовать для подготовки к собеседованиям.
Оставляю ссылочку: GitHub 📱
PostgreSQL может избежать полной сортировки таблицы при ORDER BY ... LIMIT!
Большинство думает, что ORDER BY всегда сортирует вообще всю таблицу. Но в PostgreSQL для LIMIT есть отдельная оптимизация — Top-N Heap Sort.
SELECT *
FROM orders
ORDER BY created_at DESC
LIMIT 10;
scan.execution plan:Sort Method: top-N heapsort
sort мог бы уйти на диск:SET work_mem = '4MB';
EXPLAIN ANALYZE
SELECT *
FROM events
ORDER BY created_at DESC
LIMIT 50;
work_mem PostgreSQL может избежать полной сортировки всех строк и работать заметно быстрее именно благодаря оптимизации Top-N.CREATE INDEX idx_events_created_at
ON events (created_at DESC);
❤️ SQL DEV — большая база для изучения SQL и PostgreSQL!
Здесь собрано огромное количество материалов по SQL: запросы, JOIN’ы, подзапросы, оконные функции, индексы, оптимизация и работа с PostgreSQL. Репозиторий отлично подходит как для изучения базы, так и для углубления в более сложные темы. Особенно полезно то, что здесь много практических примеров и разборов реальных SQL-конструкций.
Оставляю ссылочку: GitHub 📱
📂 Напоминалка по Data Engineering и аналитической архитектуре!
Например, Data Warehouse хранит уже очищенные и структурированные данные для BI и отчетности, Data Lake позволяет складировать сырые данные любого типа и обрабатывать их по мере необходимости, а Data Mesh распределяет владение данными между бизнес-доменами и превращает данные в полноценные data products.
На картинке — основные отличия: как хранятся данные, кто ими управляет, как происходит обработка и для каких задач лучше подходит каждый подход.
Сохрани, чтобы не потерять!
➡️ SQL Ready | #ресурс
😍 SQL шпаргалка для начинающих — база по работе с бд!
Удобный и понятный справочник, где разобраны основные SQL-запросы и конструкции: SELECT, WHERE, JOIN, INSERT, UPDATE и другие ключевые команды. Всё объясняется простым языком с примерами, чтобы быстро понять логику работы с данными и начать применять это в реальных задачах
📌 Оставляю ссылочку: ermita.one
➡️ SQL Ready | #ресурс
📂 Напоминалка по DB Sharding!
Шардинг помогает масштабировать базу данных, распределяя данные между несколькими серверами. На картинке — основные стратегии партиционирования и шардинга.
Например, key-based sharding использует hash-функцию для равномерного распределения записей, а range-based sharding делит данные по диапазонам значений.
Сохрани, чтобы не потерять!
➡️ SQL Ready | #ресурс
🐱 Awesome Postgres — огромная база всего, что связано с PostgreSQL!
В этом репозитории собраны лучшие инструменты, библиотеки, GUI-клиенты, ORM, статьи, курсы и сервисы для работы с PostgreSQL. По сути — это подборка всего полезного для backend-разработчика и DBA в одном месте. Внутри есть материалы не только по базовой работе с БД, но и по мониторингу, репликации, производительности, миграциям и архитектуре.
Оставляю ссылочку: GitHub 📱
📂 Напоминалка по тому, как работает PostgreSQL!
Например, каждое подключение создаёт отдельный процесс, а любые изменения сначала попадают в WAL, и только потом гарантированно пишутся в данные.
На картинке — базовая архитектура: подключения и backend-процессы, shared memory, background и auxiliary процессы и физическое хранение.
Сохрани, чтобы не потерять!
➡️ SQL Ready | #ресурс
Отклики, после которых зовут на собеседования
Главный фокус команды ии-ассистента Софи:
1. Ребята переписали парсер - теперь собирают вакансии из 110+ источников: Telegram, Хабр, LinkedIn и другие.
2. Параллельно сделали умный мэтчер - Софи теперь показывает не всё подряд, а то, что подходит именно вам
(локация, стек, грейд + обучение на фидбэке юзеров).
На этом строится основа автооткликов - быстрых, точных и стабильных.
Следующий шаг - откалибровать мэтчер до точности 80%+, но это невозможно без реальных пользователей.
Поэтому в ближайшие дни они открывают доступ к бесплатному тестированию.
Мест будет немного, так как фокус на качестве.
➡️Если хочешь попасть - подписывайся на канал, там будет анонс о наборе.
😎 SQL Server Kit — практическая база инструментов и материалов!
Репозиторий представляет собой обширную коллекцию скриптов, статей, рекомендаций и утилит для администрирования и разработки на SQL. Внутри собраны решения для диагностики производительности, оптимизации запросов, мониторинга, резервного копирования, работы с индексами и анализа состояния базы данных. Также представлены ссылки на проверенные источники и лучшие практики работы с СУБД.
Оставляю ссылочку: GitHub 📱
Совет на всю жизнь — начните изучать вайбкодинг.
Нейронки уже собирают проекты от идеи до релиза, пишут код, находят и исправляют баги лучше команды айтишников. Это сэкономит вам десятки часов работы.
А чтобы научиться вайбкодить и не совершать ошибки - читайте канал AI-архитектор, где есть:
➖Инструкции по Antigravity и n8n.
➖Связки, которые приносят от 100 000 ₽ за проект
➖Реальный опыт программиста и вайбкодера, который настраивает автоматизацию ИИ для гос. компаний.
Подписывайтесь и смотрите в закрепе, как за 2 дня собрать ИИ-автоматизацию без единой строчки кода: /channel/+eBc7ivvcY34xZTli
Использование data-modifying CTE для цепочек операций в одном запросе!
Обычно изменения и чтение делают разными запросами, что создаёт лишние round-trip и риск рассинхронизации данных между операциями:
UPDATE orders
SET status = 'processing'
WHERE id = 123
RETURNING *;
RETURNING уже даёт доступ к изменённым строкам, но data-modifying CTE позволяет пойти дальше и использовать их в следующих шагах:WITH updated AS (...)
CTE фиксирует результат изменения и гарантирует, что последующие операции работают с тем же набором строк в рамках одного statement:INSERT INTO audit_log (order_id, new_status)
SELECT id, status
FROM updated;
SELECT.data-modifying CTE — это способ строить сложные, но безопасные цепочки операций внутри одного SQL-запроса.