sql_ready | Unsorted

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

7786

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

Subscribe to a channel

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


Хотя пользователи без бана есть. Почему так происходит — SQL сравнивает условие примерно как:
id <> 1
AND id <> 2
AND id <> NULL


Но:
id <> NULL


не даёт TRUE или FALSE. Результат: UNKNOWN. А в WHERE проходят только TRUE. Из-за этого всё условие целиком перестаёт выполняться.

Это особенность трёхзначной логики SQL: TRUE, FALSE, UNKNOWN

Любое сравнение с NULL даёт UNKNOWN:
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.

Ещё вариант — явно убрать NULL:
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.

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

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

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

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 могут иметь абсолютно разную нагрузку на систему.

➡️ SQL Ready | #совет

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

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

Генерация пропущенных дат в отчётах: почему 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 сама по себе не помогает — без таблицы дат нечего достраивать. Правильный подход — сначала явно сгенерировать календарь, а потом присоединить данные.

В PostgreSQL для этого есть 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'


Это позволяет использовать индекс и ускоряет запрос на больших таблицах.

Расширение задачи — накопительный итог по дням (используется тот же CTE calendar):
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.

🔥 Если в отчёте есть время — сначала формируйте ось времени, потом присоединяйте данные. Иначе вы работаете не с нулями, а с отсутствием строк, что даёт искажённую картину.

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

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

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

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;


Так удобно анализировать рост, падение и аномалии в данных без дополнительных JOIN.

Можно находить моменты изменения значения, например смену статуса:
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() часто используют для анализа трендов, поиска изменений, расчёта дельт и работы с временными рядами.

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

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

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

Исключающие ограничения: гарантия отсутствия пересечений на уровне БД!

Проверять пересекаются ли интервалы через 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;


PostgreSQL решает это на уровне ограничений через exclusion constraint, который работает корректно даже при конкуренции:
ALTER TABLE bookings
ADD CONSTRAINT no_overlap
EXCLUDE USING gist (
room_id WITH =,
tsrange(start_at, end_at) WITH &&
);


🔥 exclusion constraint — это способ переложить сложную логику (пересечения, диапазоны, уникальность по условиям) с приложения на уровень СУБД с полной защитой от состояния гонки.

➡️ SQL Ready | #совет

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

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

Диапазоны как тип данных: убираем двойные условия и баги!

Когда работаешь с интервалами, обычно пишут два условия, и почти всегда где-то ошибаются с границами:

WHERE created_at <= now()
AND shipped_at > now()


Такой код легко сломать и плохо масштабируется:
tstzrange(created_at, shipped_at)


PostgreSQL имеет встроенные range-типы, которые позволяют хранить и сравнивать интервалы как единое значение.

tstzrange — для timestamptz, tsrange — для timestamp:
@> now()


Оператор @> проверяет содержит ли диапазон значение, заменяя сразу два условия.

По умолчанию границы [ ) — аналог created_at <= x AND shipped_at > x:
CREATE INDEX ON orders USING GIST (tstzrange(created_at, shipped_at));


С GiST-индексом такие запросы работают эффективно даже на больших объёмах.

🔥range-типы — это способ убрать класс ошибок с датами и упростить сложную логику.

➡️ SQL Ready | #совет

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

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

😍 SQL PostgreSQL Course — информативный курс по SQL с практикой!

Это структурированное обучение по PostgreSQL: от базовых запросов до JOIN’ов, подзапросов и проектирования базы данных. Материал выстроен последовательно, поэтому легко идти шаг за шагом и не теряться в теме. Здесь есть практика с заданиями и ответами, за счёт чего обучение закрепляется.

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


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

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

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

Дубликаты после 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.

Для связи 1:N это абсолютно ожидаемо. Где начинаются проблемы — агрегация:
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 здесь вообще лишний. Лучше контролировать кардинальность до JOIN

Если задача — проверить наличие успешной оплаты, проще и дешевле использовать EXISTS:
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.

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

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

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

☕️ Полезную статью нашёл на Хабре: «Ваш RAG не умеет думать. А мой умеет»!

В этой статье:
• Разбирается, почему классические RAG-системы на базе векторных БД часто ограничиваются поверхностным поиском;
• Показан подход HippoRAG 2, где память и связи между фактами организованы ближе к графовой модели;
• Объясняется, как эволюционирует работа с данными;
• Рассматривается, как это влияет на SQL/NoSQL слой, архитектуру хранения и построение более осмысленных запросов к данным.


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


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

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

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

Проверяем дубликаты и считаем уникальные значения!

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

Создадим таблицу пользователей:

CREATE TABLE users (
user_id INT,
email VARCHAR(100)
);

INSERT INTO users VALUES
(1, 'alice@mail.com'),
(2, 'bob@mail.com'),
(3, 'alice@mail.com'),
(4, 'carol@mail.com'),
(5, 'bob@mail.com');


Запрос для выявления дубликатов и подсчёта уникальных email:
SELECT email, COUNT(*) AS cnt,
CASE WHEN COUNT(*)>1 THEN 'Duplicate' ELSE 'Unique' END AS status
FROM users
GROUP BY email;


Функция COUNT() + GROUP BY группирует одинаковые значения, а CASE сразу классифицирует их как дубликаты или уникальные.

Результат:
email           | cnt | status
-----------------------------
alice@mail.com | 2 | Duplicate
bob@mail.com | 2 | Duplicate
carol@mail.com | 1 | Unique


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

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

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

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

❤️ Нашел вам html5css — простой и наглядный справочник по SQL на русском!

Если хочется быстро разобраться в командах — этот сайт отлично подойдёт. Конструкции объяснены на примерах, с чётким и кратким синтаксисом. Удобно использовать как справочник или экспресс‑повторение.

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

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

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

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

Совет на 2026 год — переходите в ML.

Пока обычные разрабы конкурируют с ИИ-копилотами, ML-инженеры эти самые нейронки создают.

В эпоху нейростей это самые востребованые люди в мире программирования. Зарплаты мидлов начинаются от 250 000 ₽, а у сеньоров в BigTech доходят до 700 000 ₽.

А чтобы освоить его всего за 4 месяца без лишней суеты — изучите канал Артема Алехина.

Его бэкграунд: Руководитель команды в Сбере, валютная удаленка. К 22 годам вышел на доход 1 000 000+ ₽ в месяц.

На канале вы найдёте:

— Всё про самые востребованные стеки(Python, ИИ-агенты, NLP) и почему математика — это не страшно, если учить только нужное.

— Как оформить резюме, чтобы оно пролетало через любые LLM-фильтры и ATS-системы прямо к тимлидам.

— Скрипты переговоров, которые помогли его ученикам прыгнуть с 0 до 360к всего за 8 месяцев.

Во времена острой нехватки ML-разработчиков, это лучшее время, чтобы перекатиться. Переходи и изучай: /channel/+qG_ihzUEkHJlOWVi

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

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

🖥 Когда система нагружена сильнее всего?

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

Сегодня в задаче:

• Преобразуем интервалы в точки начала и конца, чтобы работать с ними как с потоком событий;

• Посчитаем текущую нагрузку через накопительную сумму по времени;

• Найдём момент максимального количества одновременных событий — пик нагрузки системы.


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

➡️ SQL Ready | #задача

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

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

☕️ Годную статью нашёл на Хабре: «Как работает распределённый SQL в YDB: от запроса до выполнения»!

В этой статье:
• Показано, как в YDB обрабатывается SQL-запрос — от парсинга до распределённого исполнения по узлам;
• Разбирается, как устроены планировщик, оптимизатор и механизмы шардинга при работе с большими данными;
• Объясняется, как достигаются консистентность, отказоустойчивость и масштабируемость в распределённой базе.


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


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

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

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

📂 Напоминалка по блокировкам и транзакциям в SQL!

Optimistic locking позволяет работать без блокировок — конфликт проверяется при записи (например, через version или updated_at). Pessimistic locking наоборот сразу ставит блокировку (SELECT ... FOR UPDATE) и заставляет другие транзакции ждать.

На картинке — как два подхода ведут себя при одновременном обновлении одной строки: в одном случае получаем conflict, в другом — очередь.

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

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

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

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

📂 Напоминалка по Data Engineering и аналитической архитектуре!

Например, Data Warehouse хранит уже очищенные и структурированные данные для BI и отчетности, Data Lake позволяет складировать сырые данные любого типа и обрабатывать их по мере необходимости, а Data Mesh распределяет владение данными между бизнес-доменами и превращает данные в полноценные data products.

На картинке — основные отличия: как хранятся данные, кто ими управляет, как происходит обработка и для каких задач лучше подходит каждый подход.

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

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

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

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

😍 SQL шпаргалка для начинающих — база по работе с бд!

Удобный и понятный справочник, где разобраны основные SQL-запросы и конструкции: SELECT, WHERE, JOIN, INSERT, UPDATE и другие ключевые команды. Всё объясняется простым языком с примерами, чтобы быстро понять логику работы с данными и начать применять это в реальных задачах

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

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

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

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

📂 Напоминалка по DB Sharding!

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

Например, key-based sharding использует hash-функцию для равномерного распределения записей, а range-based sharding делит данные по диапазонам значений.

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

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

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

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

🐱 Awesome Postgres — огромная база всего, что связано с PostgreSQL!

В этом репозитории собраны лучшие инструменты, библиотеки, GUI-клиенты, ORM, статьи, курсы и сервисы для работы с PostgreSQL. По сути — это подборка всего полезного для backend-разработчика и DBA в одном месте. Внутри есть материалы не только по базовой работе с БД, но и по мониторингу, репликации, производительности, миграциям и архитектуре.

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


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

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

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

📂 Напоминалка по тому, как работает PostgreSQL!

Например, каждое подключение создаёт отдельный процесс, а любые изменения сначала попадают в WAL, и только потом гарантированно пишутся в данные.

На картинке — базовая архитектура: подключения и backend-процессы, shared memory, background и auxiliary процессы и физическое хранение.

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

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

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

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

Отклики, после которых зовут на собеседования

Главный фокус команды ии-ассистента Софи:

1. Ребята переписали парсер - теперь собирают вакансии из 110+ источников: Telegram, Хабр, LinkedIn и другие.

2. Параллельно сделали умный мэтчер - Софи теперь показывает не всё подряд, а то, что подходит именно вам
(локация, стек, грейд + обучение на фидбэке юзеров).

На этом строится основа автооткликов - быстрых, точных и стабильных.

Следующий шаг - откалибровать мэтчер до точности 80%+, но это невозможно без реальных пользователей.

Поэтому в ближайшие дни они открывают доступ к бесплатному тестированию.

Мест будет немного, так как фокус на качестве.

➡️Если хочешь попасть - подписывайся на канал, там будет анонс о наборе.

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

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

😎 SQL Server Kit — практическая база инструментов и материалов!

Репозиторий представляет собой обширную коллекцию скриптов, статей, рекомендаций и утилит для администрирования и разработки на SQL. Внутри собраны решения для диагностики производительности, оптимизации запросов, мониторинга, резервного копирования, работы с индексами и анализа состояния базы данных. Также представлены ссылки на проверенные источники и лучшие практики работы с СУБД.

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


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

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

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

Совет на всю жизнь — начните изучать вайбкодинг.

Нейронки уже собирают проекты от идеи до релиза, пишут код, находят и исправляют баги лучше команды айтишников. Это сэкономит вам десятки часов работы.

А чтобы научиться вайбкодить и не совершать ошибки - читайте канал AI-архитектор, где есть:

➖Инструкции по Antigravity и n8n.
➖Связки, которые приносят от 100 000 ₽ за проект
➖Реальный опыт программиста и вайбкодера, который настраивает автоматизацию ИИ для гос. компаний.

Подписывайтесь и смотрите в закрепе, как за 2 дня собрать ИИ-автоматизацию без единой строчки кода: /channel/+eBc7ivvcY34xZTli

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

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

Использование 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-запроса.

➡️ SQL Ready | #совет

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

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

📂 Напоминалка по реляционной модели и SQL!

Например, Primary Key гарантирует уникальность записей, Foreign Key — обеспечивает ссылочную целостность, а JOIN’ы позволяют собирать распределённые данные в единую выборку.

На картинке — основные концепции, которые используются при проектировании и работе с БД.

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

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

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

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

🖥 Напоминалка по продвинутым функциям работы с датами!

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

➡️ SQL Ready | #шпора

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

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

😎 SQLServerCentral — крупнейшее сообщество и база знаний по Microsoft SQL Server!

Здесь публикуются ежедневные статьи, обучающие серии Stairway, подборки скриптов, обзоры книг, а также активные форумы и блоги для администраторов БД и разработчиков.

📌 Оставляю ссылочку: sqlservercentral

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

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

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

🔥База платных курсов и книг по программированию на весь 2026 год!🔥

Канал новый, потому что слишком много людей ждут халявы.

Тут всё, что обычно стоит тысячи:
— Авторские курсы, которые не найти в открытом доступе
— Самые свежие книги для старта
— Всё, что нужно, чтобы за полгода вырасти от мидла до сеньора

➡️ Залетайте, пока есть доступ!

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

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

Почему после JOIN внезапно растут агрегаты!

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

Классическая ситуация. Есть таблицы:

orders(id, customer_id, amount)
order_items(id, order_id, product_id, quantity)


Допустим, хотим посчитать сумму заказов. На первый взгляд кажется, что такой запрос окей:
SELECT SUM(o.amount) AS total_revenue
FROM orders o
JOIN order_items i
ON i.order_id = o.id;


Но тут и начинается подвох.

Если у одного заказа 3 позиции в order_items, то строка из orders после JOIN повторится 3 раза. И o.amount тоже попадёт в расчёт 3 раза. В итоге сумма завышается. Это как раз тот самый fan-out: одна строка размножается после JOIN.

Быстрая проверка, есть ли проблема:
SELECT
COUNT(*) AS rows_after_join,
COUNT(DISTINCT o.id) AS unique_orders
FROM orders o
JOIN order_items i
ON i.order_id = o.id;


Если строк после JOIN стало больше, чем уникальных заказов, значит у вас fan-out по уровню orders. Что делать правильно — зависит от задачи.

Если вам нужна просто сумма по заказам, то JOIN вообще не нужен:
SELECT SUM(amount)
FROM orders;


Если JOIN нужен только для фильтрации, например по конкретному товару, безопаснее использовать EXISTS:
SELECT SUM(o.amount)
FROM orders o
WHERE EXISTS (
SELECT 1
FROM order_items i
WHERE i.order_id = o.id
AND i.product_id = 10
);


Почему это хорошо: гранулярность orders не ломается. Один заказ остаётся одной строкой.

Ещё нормальный вариант — сначала убрать дубли на стороне order_items:
SELECT SUM(o.amount)
FROM orders o
JOIN (
SELECT DISTINCT order_id
FROM order_items
WHERE product_id = 10
) i ON i.order_id = o.id;


Тут мы заранее приводим данные к уровню одна строка = один заказ, и только потом джойним.

А если задача вообще на уровне позиций, например нужно посчитать общее количество товаров, тогда считать надо уже по order_items:
SELECT SUM(i.quantity)
FROM order_items i;


То есть важный момент очень простой: агрегировать нужно на том уровне, где реально живёт ваша метрика.

Отдельно про популярный костыль:
SELECT SUM(DISTINCT o.amount)
FROM orders o
JOIN order_items i
ON i.order_id = o.id;


С виду кажется, что DISTINCT сейчас всё починит, на деле — нет. Почему это плохая идея: если у двух разных заказов одинаковый amount, один из них просто схлопнется; запрос начинает давать вроде бы правдоподобный, но неверный результат.

Ещё неприятнее, когда JOIN не один, а цепочка: orders — order_items — products — categories

Практический способ быстро это поймать — смотреть количество строк после каждого шага:
SELECT COUNT(*) FROM orders;

SELECT COUNT(*)
FROM orders
JOIN order_items ON order_items.order_id = orders.id;

SELECT COUNT(*)
FROM orders
JOIN order_items ON order_items.order_id = orders.id
JOIN products ON products.id = order_items.product_id;


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

🔥 Итог простой: перед тем как писать JOIN, всегда держите в голове гранулярность данных. Что у вас является одной строкой: заказ, позиция заказа, клиент? Сначала определяете уровень данных — потом джойните и агрегируете.

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

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

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

Как вернуть строки в том же порядке, в котором пришли id?

Когда из приложения прилетает список id, обычный WHERE id = ANY(...) находит нужные строки, но порядок входного массива не сохраняет:

SELECT *
FROM users
WHERE id = ANY(ARRAY[42, 7, 99]);


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

WITH ORDINALITY добавляет каждой строке её позицию во входном наборе:
unnest(ARRAY[42, 7, 99]) WITH ORDINALITY


Дальше это уже обычная таблица, которую можно джойнить, фильтровать и сортировать:
ORDER BY x.ord


В итоге база сама возвращает строки в нужном порядке, без CASE, без ручной сортировки в коде и без лишнего постобработчика.

🔥 WITH ORDINALITY — это простой способ сохранить порядок входных данных в SQL, он хорошо заходит в API и массовые выборки.

➡️ SQL Ready | #совет

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