Во многих системах есть правило: у пользователя, товара или сущности должна быть только одна активная запись. Если проверять это в коде, при конкурентных запросах правило легко нарушается.
Сегодня в гайде:
• Почему логика обновить + вставить ломается;
• Как зафиксировать правило на уровне базы;
• Как условный уникальный индекс предотвращает дубликаты.
Подход, который убирает класс ошибок с конкурентным доступом и делает данные консистентными независимо от нагрузки.
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
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