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 | #практика
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
