SQL Ready | Базы Данных
Авторский канал про Базы Данных и SQL Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_it РКН: https://clck.ru/3QREBc Реклама на бирже: https://telega.in/c/sql_ready
Показати більше📈 Аналітичний огляд Telegram-каналу SQL Ready | Базы Данных
Канал SQL Ready | Базы Данных (@sql_ready) у мовному сегменті Російська є активним учасником. На даний момент спільнота об'єднує 15 560 підписників, посідаючи 8 395 місце в категорії Технології та додатки та 43 172 місце у регіоні Росія.
📊 Показники аудиторії та динаміка
З моменту свого створення невідомо, проект продемонстрував стрімке зростання, зібравши аудиторію у 15 560 підписників.
За останніми даними від 10 червня, 2026, канал демонструє стабільну активність. Хоча за останні 30 днів спостерігається зміна кількості учасників на 57, а за останні 24 години на -8, загальне охоплення залишається високим.
- Статус верифікації: Не верифікований
- Рівень залученості (ER): Середній показник залученості аудиторії становить 11.95%. Протягом перших 24 годин після публікації контент зазвичай збирає 6.07% реакцій від загальної кількості підписників.
- Охоплення публікацій: В середньому кожен допис отримує 1 860 переглядів. Протягом першої доби публікація в середньому набирає 945 переглядів.
- Реакції та взаємодія: Аудиторія активно підтримує контент: середня кількість реакцій на один пост – 25.
- Тематичні інтереси: Контент зосереджений навколо ключових тем, таких як sql, строка, user_id, created_at, desc.
📝 Опис та контентна політика
Автор описує ресурс як майданчик для висловлення суб'єктивної думки:
“Авторский канал про Базы Данных и SQL
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!
Автор: @energy_it
РКН: https://clck.ru/3QREBc
Реклама на бирже: https://telega.in/c/sql_ready”
Завдяки високій частоті оновлень (останні дані отримано 11 червня, 2026), канал підтримує актуальність та високий рівень охоплення публікацій. Аналітика показує, що аудиторія активно взаємодіє з контентом, що робить його важливою точкою впливу в категорії Технології та додатки.
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 | #практикаversion или updated_at). Pessimistic locking наоборот сразу ставит блокировку (SELECT ... FOR UPDATE) и заставляет другие транзакции ждать.
На картинке — как два подхода ведут себя при одновременном обновлении одной строки: в одном случае получаем conflict, в другом — очередь.
Сохрани, чтобы не потерять!
➡️ SQL Ready | #ресурс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 | #советОставляю ссылочку: GitHub 📱➡️ SQL Ready | #репозиторий
FILTER позволяет задать условие прямо для SUM, COUNT, AVG — без вложенных подзапросов и лишнего шума. Код получается чище, короче и проще читается.
Что важно знать:
• FILTER работает внутри агрегата — условие применяется только к нему. • Отлично подходит для отчётных таблиц с множеством условий. • Заменяет CASE WHEN в 90% ситуаций, где раньше казалось без него никак.Поэтому, это инструмент, с которым SQL-запросы становятся короче и понятнее. ➡️ SQL Ready | #гайд
SELECT user_id, email, created_at
FROM users
WHERE user_id IS NULL
OR email IS NULL;
Проверяем дубликаты по уникальному полю и сразу классифицируем их:
SELECT email, COUNT(*) AS cnt,
CASE WHEN COUNT(*)>1 THEN 'Duplicate' ELSE 'Unique' END AS status
FROM users
GROUP BY email;
Ищем аномалии в числовых полях (например, сумма заказа < 0):
SELECT order_id, total_amount
FROM orders
WHERE total_amount < 0;
🔥 Это позволяет отслеживать качество данных, предотвращать ошибки аналитики и готовить отчёты для команды разработки.
➡️ SQL Ready | #практикаUNIQUE в SQL пропускает несколько NULL, потому что NULL не считается равным другому. Из-за этого ограничение часто формально есть, а правило на самом деле не соблюдается:
UNIQUE (telegram_id)
Если колонка опциональная, но по смыслу значение всё равно должно быть уникальным, стандартный UNIQUE даёт дыру в данных.
UNIQUE NULLS NOT DISTINCT (telegram_id)
Эта форма говорит PostgreSQL считать NULL обычным сравнимым значением именно для проверки уникальности.
То есть второй NULL уже не пройдёт, как и дубликат обычного значения.
UNIQUE NULLS NOT DISTINCT (tenant_id, external_id)
Особенно полезно для nullable внешних идентификаторов, one-to-one связей, интеграционных ключей и любых полей, где NULL тоже должен быть единственным допустимым состоянием.
🔥 UNIQUE NULLS NOT DISTINCT закрывает один из источников грязных данных и заменяет триггеры.
➡️ SQL Ready | #советORDER BY … DESC нужен отдельный DESC-индекс — но это не всегда так.
В PostgreSQL обычный B-tree индекс:
CREATE INDEX idx_orders_user_created
ON orders (user_id, created_at);
уже может использоваться для ORDER BY created_at DESC через backward scan, без дополнительной сортировки.
Если нужен смешанный порядок (например (user_id ASC, created_at DESC)), тогда имеет смысл явно указать направление:
CREATE INDEX idx_orders_user_created_desc
ON orders (user_id, created_at DESC);
🔥 В таких случаях PostgreSQL сможет читать данные сразу в нужном порядке.
➡️ SQL Ready | #советWHERE фильтрует строки до агрегации, а HAVING — уже после GROUP BY.
На картинке — основные конструкции: разница между WHERE и HAVING, оконные функции против обычной агрегации, а также основы партицирования для больших таблиц.
Сохрани, чтобы не потерять!
➡️ SQL Ready | #ресурсcustomers(id, last_order_at)
orders(id, customer_id, created_at)
Интуитивный вариант:
UPDATE customers c
SET last_order_at = o.created_at
FROM orders o
WHERE o.customer_id = c.id;
Запрос выполнится, но если у клиента несколько заказов — возникает вопрос: какая именно дата попадёт в last_order_at? Ответ — это не гарантируется.
Запрос корректен синтаксически, но логически небезопасен. Если JOIN даёт несколько строк для одной записи в customers, PostgreSQL не гарантирует, какую строку он использует при обновлении.
Поэтому сначала нужно свести соответствие к одной строке на клиента. Через агрегат:
UPDATE customers c
SET last_order_at = t.max_created_at
FROM (
SELECT
customer_id,
MAX(created_at) AS max_created_at
FROM orders
GROUP BY customer_id
) t
WHERE t.customer_id = c.id;
Теперь для каждого клиента ровно одна строка — обновление становится детерминированным.
Вариант через DISTINCT ON:
UPDATE customers c
SET last_order_at = t.created_at
FROM (
SELECT DISTINCT ON (customer_id)
customer_id,
created_at
FROM orders
ORDER BY customer_id, created_at DESC
) t
WHERE t.customer_id = c.id;
Здесь тоже выбирается одна строка на клиента за счёт сортировки. Если возможны одинаковые created_at, лучше добавить tie-breaker:
ORDER BY customer_id, created_at DESC, id DESC
Коррелированный подзапрос:
UPDATE customers c
SET last_order_at = (
SELECT MAX(o.created_at)
FROM orders o
WHERE o.customer_id = c.id
);
Плюс: гарантированно одно значение. Нюанс: обновятся все строки в customers, и у клиентов без заказов будет NULL.
Пример ловушки:
UPDATE customers c
SET last_order_at = t.created_at
FROM (
SELECT customer_id, created_at
FROM orders
) t
WHERE t.customer_id = c.id;
С виду безопасно, по факту — та же проблема: соответствие не уникально.
Практическая проверка: замените UPDATE на SELECT с тем же JOIN и посмотрите, есть ли дубли:
SELECT c.id, t.*
FROM customers c
JOIN ...
Если дубли есть — такой UPDATE уже небезопасен. Про индексы:
CREATE INDEX idx_orders_customer_created
ON orders (customer_id, created_at);
Помогает и для MAX, и для ORDER BY. Итог: UPDATE ... FROM — крутой инструмент, но он не проверяет однозначность соответствия.
🔥 Если JOIN возвращает несколько строк на одну обновляемую запись, результат не гарантируется. Сначала фиксируем одну строку на ключ — потом обновляем.
➡️ SQL Ready | #практика
Вже доступно! Дослідження Telegram за 2025 — головні інсайти року 
