TGViewer
SQL Portal | Базы Данных SQL Portal | Базы Данных @sqlportal · 13.7K subscribers
Post #1816 1.01K
Любимый паттерн для “последняя запись на группу” в Postgres — DISTINCT ON.
Да, он Postgres-specific. Нет, он не из SQL Standard. Зато для этой задачи он читается почти идеально.

Есть таблица заказов:
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id int,
status text,
created_at timestamptz
);


Старый классический вариант:
SELECT o.*
FROM orders o
INNER JOIN (
SELECT customer_id, MAX(created_at) AS latest
FROM orders
GROUP BY customer_id
) latest ON o.customer_id = latest.customer_id
AND o.created_at = latest.latest;


Работает, но есть нюансы.
Таблица читается два раза: сначала ищем MAX(created_at) по каждому customer_id, потом джойнимся обратно за полной строкой.

И ещё хуже: если у одного клиента два заказа с одинаковым created_at, запрос может вернуть две строки. Нужен tie-breaker.

В Postgres проще так:
SELECT DISTINCT ON (customer_id)
customer_id, status, created_at
FROM orders
ORDER BY customer_id, created_at DESC;


DISTINCT ON (customer_id)
оставляет первую строку для каждого клиента после сортировки.
То есть ORDER BY customer_id, created_at DESC сначала кладёт свежий заказ клиента наверх, а DISTINCT ON забирает его.

Чтобы это реально летало, добавляем индекс:
CREATE INDEX ON orders (customer_id, created_at DESC);


Теперь Postgres может идти по уже отсортированному индексу и не делать отдельную сортировку.
На небольшом тесте с 100k строк и 20k customer_id получилось так:
Без индекса:
• MAX + self-join: 32.92ms
• DISTINCT ON: 34.75ms
• ROW_NUMBER(): 38.68ms
С индексом (customer_id, created_at DESC):
• DISTINCT ON: 15.99ms
• ROW_NUMBER(): 25.36ms
• MAX + self-join: 38.01ms

Главный вывод: для “одна последняя строка на группу” в Postgres я первым беру DISTINCT ON.
Если нужно не одну строку, а top N строк на группу, тогда уже ROW_NUMBER():
SELECT customer_id, status, created_at
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY created_at DESC
) AS rn
FROM orders
) ranked
WHERE rn = 1;


DISTINCT ON
— для простого “последний на группу”.
Window functions — когда нужна гибкость.

👉 @SQLPortal
  • ❤ 2
  • 👍 1
More from @sqlportal
  1. Oct 9, 2026Горизонтальные или вертикальные столбцы: что выбрать? Оба варианта помогают сравнивать кат…
  2. Oct 9, 2026Оконные функции SQL: аналитика без потери отдельных строк В отличие от GROUP BY, оконные ф…
  3. Oct 8, 2026DBcooper — лёгкий клиент для работы с базами данных Open-source-приложение на Tauri, Rust…
  4. Oct 8, 20265 групп реляционных СУБД, которые полезно знать • С открытым исходным кодом: PostgreSQL, M…
  5. Oct 7, 2026Разбираем SQL-запросы с помощью Python Какие таблицы и колонки использует запрос? Библиоте…
  6. Oct 7, 2026Линтер и автоформаттер для SQL Бесплатный инструмент с открытым исходным кодом, который пр…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →