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
This media is not supported in your browser
VIEW IN TELEGRAM
Репозиторий с материалами для изучения SQL с самого начала: здесь разбираются основы работы с базами данных, синтаксис запросов, JOIN-ы, фильтрация, агрегации и структура таблиц. Формат обучения построен вокруг практики, примеры запросов, задания и объяснения помогают не просто читать теорию, а сразу писать код.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
❤21🔥10🤝10
This media is not supported in your browser
VIEW IN TELEGRAM
Здесь собраны конспекты, шпаргалки и учебные PDF по всем ключевым темам: от базовых запросов до сложных конструкций и оптимизации. Отдельно выделяется наличие большого количества материалов для подготовки к собеседованиям, сотни вопросов и разборов.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤7🔥7🤝2
UPDATE ... FROM в PostgreSQL: как обновлять данные из другой таблицы и не поймать скрытые дубли!
Задача, которая встречается постоянно: обновить таблицу по данным из другой. Например, проставить клиентам дату последнего заказа.
Есть таблицы:
Интуитивный вариант:
Запрос выполнится, но если у клиента несколько заказов — возникает вопрос: какая именно дата попадёт в
Запрос корректен синтаксически, но логически небезопасен. Если
Поэтому сначала нужно свести соответствие к одной строке на клиента. Через агрегат:
Теперь для каждого клиента ровно одна строка — обновление становится детерминированным.
Вариант через
Здесь тоже выбирается одна строка на клиента за счёт сортировки. Если возможны одинаковые
Коррелированный подзапрос:
Плюс: гарантированно одно значение. Нюанс: обновятся все строки в
Пример ловушки:
С виду безопасно, по факту — та же проблема: соответствие не уникально.
Практическая проверка: замените
Если дубли есть — такой
Помогает и для
🔥 Если
➡️ 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 возвращает несколько строк на одну обновляемую запись, результат не гарантируется. Сначала фиксируем одну строку на ключ — потом обновляем.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤9👍9
Например,
WHERE фильтрует строки до агрегации, а HAVING — уже после GROUP BY.На картинке — основные конструкции: разница между
WHERE и HAVING, оконные функции против обычной агрегации, а также основы партицирования для больших таблиц.Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥9🤝8❤1
Индексы и ORDER BY DESC без лишней сортировки!
Многие думают, что для
В PostgreSQL обычный B-tree индекс:
уже может использоваться для
Если нужен смешанный порядок (например (
🔥 В таких случаях PostgreSQL сможет читать данные сразу в нужном порядке.
➡️ 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);
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16👍11❤10
This media is not supported in your browser
VIEW IN TELEGRAM
Это AI-ассистент для обучения, который позволяет загружать документы, видео или аудио и автоматически превращать их в структурированные материалы: краткие конспекты, ответы, карточки и тесты. Также можно задавать вопросы прямо по загруженному источнику и получать точные ответы на основе его содержания.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🔥8🤝8
Уникальность с NULL без триггеров и костылей!
Обычный
Если колонка опциональная, но по смыслу значение всё равно должно быть уникальным, стандартный
Эта форма говорит PostgreSQL считать
То есть второй
Особенно полезно для
🔥
➡️ 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 закрывает один из источников грязных данных и заменяет триггеры.Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤8🔥8
Каждый уровень играет важную роль: от физической передачи сигналов до приложений, с которыми мы взаимодействуем каждый день. Понимание этой модели помогает лучше разбираться в сетевых ошибках, маршрутизации и защите данных.
На картинке — 7 уровней OSI, что делает каждый из них и примеры протоколов.
Сохрани, чтобы не забыть!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥7🤝7
Проверка качества и целостности данных!
В больших продакшн-базах важно не только находить ошибки, но и структурировать их: проверять NULL, дубликаты, некорректные форматы и аномальные значения.
Сначала выявляем строки с пустыми ключевыми полями:
Проверяем дубликаты по уникальному полю и сразу классифицируем их:
Ищем аномалии в числовых полях (например, сумма заказа < 0):
🔥 Это позволяет отслеживать качество данных, предотвращать ошибки аналитики и готовить отчёты для команды разработки.
➡️ SQL Ready | #практика
В больших продакшн-базах важно не только находить ошибки, но и структурировать их: проверять NULL, дубликаты, некорректные форматы и аномальные значения.
Сначала выявляем строки с пустыми ключевыми полями:
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;
🔥 Это позволяет отслеживать качество данных, предотвращать ошибки аналитики и готовить отчёты для команды разработки.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍9🤝9
This media is not supported in your browser
VIEW IN TELEGRAM
Если нужно быстро освежить синтаксис или понять суть команд — это то, что нужно. Все основные конструкции, примеры и видеоуроки — коротко и по делу. Отлично подойдёт как шпаргалка и мини‑курс.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥8🤝8❤1
FILTER позволяет задать условие прямо для SUM, COUNT, AVG — без вложенных подзапросов и лишнего шума. Код получается чище, короче и проще читается.Что важно знать:
• FILTER работает внутри агрегата — условие применяется только к нему.
• Отлично подходит для отчётных таблиц с множеством условий.
• Заменяет CASE WHEN в 90% ситуаций, где раньше казалось без него никак.
Поэтому, это инструмент, с которым SQL-запросы становятся короче и понятнее.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17👍10❤9🤝2
This media is not supported in your browser
VIEW IN TELEGRAM
Универсальная шпаргалка по SQL, где собраны все основные конструкции языка. Всё структурировано по разделам, поэтому можно быстро найти нужный синтаксис. Формат максимально практичный: команда, пример, объяснение, что позволяет не просто смотреть, а сразу понимать, как что применяется в запросах.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
🤝11👍8🔥8❤2