DISTINCT — убираем дубликаты прямо в SQL!
Иногда таблица содержит повторы, а нам нужны только уникальные строки.
Выберем все уникальные города из таблицы клиентов:
Теперь получим только уникальные пары "страна + город":
А вот как посчитать количество разных городов:
В PostgreSQL можно выбирать первую уникальную строку по группе значений:
🔥
➡️ SQL Ready | #практика
Иногда таблица содержит повторы, а нам нужны только уникальные строки.
DISTINCT позволяет легко отфильтровать дубликаты на уровне запроса — без дополнительной логики.Выберем все уникальные города из таблицы клиентов:
SELECT DISTINCT city FROM customers;
Теперь получим только уникальные пары "страна + город":
SELECT DISTINCT country, city FROM customers;
А вот как посчитать количество разных городов:
SELECT COUNT(DISTINCT city) FROM customers;
В PostgreSQL можно выбирать первую уникальную строку по группе значений:
SELECT DISTINCT ON (user_id) *
FROM logins
ORDER BY user_id, login_time DESC;
🔥
DISTINCT полезен для отчётов, списков, фильтрации и аналитики. Но помните: он влияет на производительность — особенно при работе с большими таблицами.Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍9🤝8
Например, реляционные базы используют строгую структуру таблиц и отлично подходят для транзакций, а NoSQL — гибкие и масштабируемые, подходят для высоконагруженных приложений.
На картинке — 4 типа баз данных, их особенности, преимущества и где их лучше применять.
Сохрани, чтобы не забыть!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍10🔥8
Индекс с INCLUDE: уменьшаем чтение таблицы!
Даже если у вас есть индекс, PostgreSQL часто всё равно идёт в таблицу за остальными колонками — это лишний I/O, особенно на больших данных:
Теперь все нужные поля есть в индексе, и PostgreSQL может выполнить запрос через
В плане вы увидите
🔥 Один из практических бустов в read-heavy сценариях и частых API-запросах.
➡️ SQL Ready | #совет
Даже если у вас есть индекс, PostgreSQL часто всё равно идёт в таблицу за остальными колонками — это лишний I/O, особенно на больших данных:
CREATE INDEX idx_orders_user_created
ON orders (user_id, created_at DESC)
INCLUDE (total, status);
INCLUDE добавляет дополнительные колонки в индекс (payload), но они не участвуют в поиске и сортировке, часто это выгоднее, чем делать их частью ключа:SELECT user_id, created_at, total, status
FROM orders
WHERE user_id = 42
ORDER BY created_at DESC
LIMIT 1;
Теперь все нужные поля есть в индексе, и PostgreSQL может выполнить запрос через
Index Only Scan, уменьшая или исключая обращения к таблице (heap):EXPLAIN ANALYZE SELECT ...
В плане вы увидите
Index Only Scan (иногда с Heap Fetches) вместо Index Scan — это значит, что обращения к таблице сокращены или отсутствуют.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13👍9❤6🤝2
This media is not supported in your browser
VIEW IN TELEGRAM
Этот компактный и понятный справочник по SQL, где собраны основные команды, конструкции и паттерны, которые используются в работе. Всё оформлено с примерами, поэтому можно быстро вспомнить синтаксис или найти нужную команду без долгого поиска. Отлично подходит как справочник в работе, для повторения базы и подготовки к собеседованиям.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤9🔥9
NULLIF в SQL: простой способ не словить деление на ноль!
Очень частая задача в SQL — нужно что-то на что-то поделить. Например, посчитать средний чек, конверсию или процент выполнения.
Есть таблица:
Самый очевидный вариант:
На первый взгляд всё нормально. Но как только
Обычно в таких местах используют
Что здесь происходит:
Соответственно: если знаменатель не ноль — деление выполняется как обычно; если знаменатель ноль — получится NULL.
И это как раз правильное поведение. Потому что NULLIF не исправляет данные и не подменяет ноль каким-то удобным значением. Он просто говорит: в этой строке результат не определён.
Иногда после этого пишут так:
Так делать можно, но только если 0 здесь действительно имеет смысл.
Потому что: NULL — значение не определено; 0 — значение определено и равно нулю. Это разные вещи. И подмена одного другим — уже не технический, а смысловой выбор.
Ещё пример:
Почему именно 100.0, а не 100? Чтобы не получить целочисленное деление в тех СУБД, где дробная часть иначе отбросится.
То же самое можно записать и через
Это тоже нормальный вариант. Но когда задача именно в защите от деления на ноль,
Ещё момент, который часто путают:
а также:
Это разные расчёты. В первом случае — общая выручка делится на общее число заказов. Во втором — считается среднее от построчных значений, причём
🔥 Итог простой:
➡️ SQL Ready | #практика
Очень частая задача в SQL — нужно что-то на что-то поделить. Например, посчитать средний чек, конверсию или процент выполнения.
Есть таблица:
sales(id, revenue, orders_count)
Самый очевидный вариант:
SELECT
id,
revenue / orders_count AS avg_order_value
FROM sales;
На первый взгляд всё нормально. Но как только
orders_count = 0, начинаются проблемы: в ряде СУБД такой запрос упадёт с ошибкой.Обычно в таких местах используют
NULLIF:SELECT
id,
revenue / NULLIF(orders_count, 0) AS avg_order_value
FROM sales;
Что здесь происходит:
NULLIF(orders_count, 0) вернёт: orders_count, если это не ноль; NULL, если это ноль.Соответственно: если знаменатель не ноль — деление выполняется как обычно; если знаменатель ноль — получится NULL.
И это как раз правильное поведение. Потому что NULLIF не исправляет данные и не подменяет ноль каким-то удобным значением. Он просто говорит: в этой строке результат не определён.
Иногда после этого пишут так:
SELECT
id,
COALESCE(revenue / NULLIF(orders_count, 0), 0) AS avg_order_value
FROM sales;
Так делать можно, но только если 0 здесь действительно имеет смысл.
Потому что: NULL — значение не определено; 0 — значение определено и равно нулю. Это разные вещи. И подмена одного другим — уже не технический, а смысловой выбор.
Ещё пример:
SELECT
id,
success_count * 100.0 / NULLIF(total_count, 0) AS success_percent
FROM stats;
Почему именно 100.0, а не 100? Чтобы не получить целочисленное деление в тех СУБД, где дробная часть иначе отбросится.
То же самое можно записать и через
CASE:SELECT
id,
CASE
WHEN orders_count = 0 THEN NULL
ELSE revenue / orders_count
END AS avg_order_value
FROM sales;
Это тоже нормальный вариант. Но когда задача именно в защите от деления на ноль,
NULLIF обычно короче и читается быстрее.Ещё момент, который часто путают:
SELECT
SUM(revenue) / NULLIF(SUM(orders_count), 0) AS avg_order_value
FROM sales;
а также:
SELECT
AVG(revenue / NULLIF(orders_count, 0)) AS avg_order_value
FROM sales;
Это разные расчёты. В первом случае — общая выручка делится на общее число заказов. Во втором — считается среднее от построчных значений, причём
AVG пропускает NULL.NULLIF(x, 0) — приём, который в реальных запросах спасает. Особенно когда хочется не просто чтобы не упало, а чтобы результат оставался корректным по смыслу.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍9🤝8❤2
This media is not supported in your browser
VIEW IN TELEGRAM
Это гайд по производительности SQL, где подробно объясняется, как работают индексы и как с их помощью ускорять запросы. Материал построен с нуля и ориентирован именно на разработчиков, без лишней теории и перегруженной терминологии. Всё объясняется через практику и примеры.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤8🤝8
Во многих системах есть правило: у пользователя, товара или сущности должна быть только одна активная запись. Если проверять это в коде, при конкурентных запросах правило легко нарушается.
Сегодня в гайде:
• Почему логика обновить + вставить ломается;
• Как зафиксировать правило на уровне базы;
• Как условный уникальный индекс предотвращает дубликаты.
Подход, который убирает класс ошибок с конкурентным доступом и делает данные консистентными независимо от нагрузки.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13👍10❤7🤝1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🔥10🤝8
CHECK-ограничения — валидация данных на уровне базы!
Представим, что мы хотим убедиться, что цена товара всегда больше нуля:
Теперь добавим ограничение, чтобы процент скидки был в пределах от 0 до 100:
И создадим таблицу событий, где дата начала всегда должна быть раньше даты окончания:
🔥 Но помните, что
➡️ SQL Ready | #практика
CHECK позволяет задать правила, которым обязана соответствовать каждая строка в таблице. Это удобно, когда нужно гарантировать корректные значения без сторонней логики.Представим, что мы хотим убедиться, что цена товара всегда больше нуля:
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name TEXT,
price NUMERIC(10,2),
CHECK (price > 0)
);
Теперь добавим ограничение, чтобы процент скидки был в пределах от 0 до 100:
ALTER TABLE discounts
ADD CONSTRAINT percent_range_chk
CHECK (percentage BETWEEN 0 AND 100);
И создадим таблицу событий, где дата начала всегда должна быть раньше даты окончания:
CREATE TABLE events (
id SERIAL PRIMARY KEY,
starts_at TIMESTAMP,
ends_at TIMESTAMP,
CHECK (starts_at < ends_at)
);
🔥 Но помните, что
CHECK проверяет только вставляемые или обновлённые строки. Если вы добавляете ограничение в таблицу с данными, указывайте NOT VALID, чтобы временно обойти проверку.Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤8🔥7
Использование расширенной статистики для повышения точности планировщика!
Бывает, что запрос очевидный, индексы есть, но PostgreSQL всё равно выбирает Seq Scan, потому что считает условия независимыми и сильно ошибается в кардинальности:
Extended statistics позволяют оптимизатору учитывать зависимости между колонками, например что у конкретного
После сбора статистики PostgreSQL начинает правильно оценивать селективность комбинации условий, а не перемножать вероятности как будто они независимы:
🔥 Не меняешь код и индексы, но кардинально улучшаешь планы выполнения.
➡️ SQL Ready | #совет
Бывает, что запрос очевидный, индексы есть, но PostgreSQL всё равно выбирает Seq Scan, потому что считает условия независимыми и сильно ошибается в кардинальности:
CREATE STATISTICS s_orders (dependencies)
ON user_id, status
FROM orders;
Extended statistics позволяют оптимизатору учитывать зависимости между колонками, например что у конкретного
user_id почти всегда один и тот же status:ANALYZE orders;
После сбора статистики PostgreSQL начинает правильно оценивать селективность комбинации условий, а не перемножать вероятности как будто они независимы:
EXPLAIN ANALYZE SELECT ...
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍9🔥8
This media is not supported in your browser
VIEW IN TELEGRAM
Например, индексы ускоряют поиск данных, а шардинг позволяет распределять нагрузку между несколькими серверами.
На картинке — основные стратегии масштабирования, которые используются в реальных системах.
Сохрани, чтобы держать под рукой!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥9❤8🤝1
LIMIT без ORDER BY — почему результат нестабилен!
Есть таблица:
И вот такой запрос:
Интуитивно хочется думать, что это первые 10 или последние 10. На деле — это просто неопределённые 10 строк в неопределённом порядке.
Без
Типичный кейс — хочу последние заказы:
Уже лучше: теперь это 10 самых новых по
Но тут есть тонкость — если несколько строк имеют одинаковый
Поэтому обычно добавляют второй критерий:
Если id уникальный — результат становится детерминированным для текущего среза данных. Где это особенно критично: пагинация, API, отчёты, кэш.
Без стабильного порядка начинаются фантомные баги: строки то появляются, то исчезают, то дублируются.
Добавились новые записи — и страницы уже не совпадают с тем, что было секунду назад.
Поэтому в проде чаще используют keyset pagination:
Фактически: дай следующие записи после этой точки.
Работает быстрее и ведёт себя предсказуемо при изменениях данных (Важно: синтаксис с (
И ещё частая ошибка:
Кажется, что это последняя запись, а по факту — самая старая (по умолчанию используется ASC в большинстве СУБД).
🔥 Вся суть:
➡️ SQL Ready | #практика
Есть таблица:
orders(id, customer_id, amount, created_at)
И вот такой запрос:
SELECT * FROM orders LIMIT 10;
Интуитивно хочется думать, что это первые 10 или последние 10. На деле — это просто неопределённые 10 строк в неопределённом порядке.
Без
ORDER BY база не обязана возвращать данные в каком-то фиксированном порядке. Сегодня это один набор строк, завтра — другой. Особенно если поменялся план выполнения или появился индекс.Типичный кейс — хочу последние заказы:
SELECT *
FROM orders
ORDER BY created_at DESC
LIMIT 10;
Уже лучше: теперь это 10 самых новых по
created_at.Но тут есть тонкость — если несколько строк имеют одинаковый
created_at, порядок между ними не детерминирован. Иногда это всплывает в самых неожиданных местах (например, в пагинации).Поэтому обычно добавляют второй критерий:
SELECT *
FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 10;
Если id уникальный — результат становится детерминированным для текущего среза данных. Где это особенно критично: пагинация, API, отчёты, кэш.
Без стабильного порядка начинаются фантомные баги: строки то появляются, то исчезают, то дублируются.
OFFSET и его ограничения:SELECT *
FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 10 OFFSET 20;
Добавились новые записи — и страницы уже не совпадают с тем, что было секунду назад.
Поэтому в проде чаще используют keyset pagination:
SELECT *
FROM orders
WHERE (created_at, id) < ('2026-01-10', 1050)
ORDER BY created_at DESC, id DESC
LIMIT 10;
Фактически: дай следующие записи после этой точки.
Работает быстрее и ведёт себя предсказуемо при изменениях данных (Важно: синтаксис с (
col1, col2) поддерживается не во всех СУБД; универсальный вариант — через OR.)И ещё частая ошибка:
SELECT *
FROM orders
ORDER BY created_at
LIMIT 1;
Кажется, что это последняя запись, а по факту — самая старая (по умолчанию используется ASC в большинстве СУБД).
LIMIT отвечает только за количество. Порядок — это всегда ORDER BY. Без составного индекса (created_at, id) такие запросы на больших таблицах начинают ощутимо тормозить.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍9🤝8❤2
This media is not supported in your browser
VIEW IN TELEGRAM
Этот репозиторий разбирает типичные ошибки и плохие практики в SQL, с которыми сталкиваются разработчики в реальных проектах. Здесь показано, как не стоит писать запросы, и даются более правильные и эффективные альтернативы. Отлично подходит для оптимизации запросов и подготовки к собеседованиям.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🤝9🔥8❤2
Например, B-Tree используется в индексах для ускорения поиска, а Hash Table — в механизмах join’ов и кэширования.
На картинке — ключевые структуры данных и типичные сценарии их использования в реальных системах и базах данных.
Сохрани, чтобы держать под рукой!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍9🔥7🤝1
Полуинтервалы вместо BETWEEN для корректных и быстрых диапазонов!
Такой код пропустит всё, что позже '2025-01-31 00:00:00', и это одна из самых частых, незаметных ошибок в аналитике:
Полуинтервал
🔥 Этот паттерн используют в биллинге, аналитике, логах и любых системах, где важна точность временных границ.
➡️ SQL Ready | #совет
BETWEEN кажется удобным, но он включает обе границы, из-за чего легко ловятся баги на датах и времени, особенно с timestamp:WHERE created_at BETWEEN '2025-01-01' AND '2025-01-31'
Такой код пропустит всё, что позже '2025-01-31 00:00:00', и это одна из самых частых, незаметных ошибок в аналитике:
WHERE created_at >= '2025-01-01'
AND created_at < '2025-02-01'
Полуинтервал
[start, end) работает предсказуемо для любых типов времени, не ломается на миллисекундах и хорошо ложится на индексы:WHERE ts >= now()
AND ts < now() + interval '1 day'
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16❤10👍9