Базы данных

MSSQL: ID клиента из последней записи по дате

Как в MSSQL получить ID клиента из последней записи по date с условиями paramname='pas', paramvalue=2, source='web'. Примеры SELECT TOP 1, ROW_NUMBER, оптимизация индексами для sql сервер.

1 ответ 1 просмотр

Как в MSSQL получить ID клиента из последней записи по дате, удовлетворяющей условиям paramname=‘pas’, paramvalue=2 и source=‘web’?

Описание таблицы clients:

  • client (уникальный ID)
  • paramname
  • paramvalue
  • date
  • source

В таблицу ежедневно загружаются данные для уникального client, обновляются параметры и их значения. Нужно вывести client ID только для последней записи по полю date среди записей, где paramname=‘pas’ AND paramvalue=2 AND source=‘web’.

В MSSQL проще всего получить ID клиента из последней записи по полю date среди строк с paramname='pas', paramvalue=2 и source='web' командой **SELECT TOP 1 client** FROM clients WHERE paramname='pas' AND paramvalue=2 AND source='web' **ORDER BY date DESC**;. Этот запрос вернёт ровно один client ID — тот, чья запись самая свежая по дате. Если в таблице ежедневно обновляются данные по уникальным клиентам, такой подход сработает мгновенно, особенно с правильным индексом.


Содержание

  • [Что именно нужно найти {#task}]
  • [Простейший вариант: SELECT TOP 1 {#top1}]
  • [Если клиентов много: ROW_NUMBER() {#rownumber}]
  • [Альтернатива с GROUP BY и MAX(date) {#max-join}]
  • [Оптимизация: индексы и производительность {#perf}]
  • [Что делать с дубликатами дат {#duplicates}]
  • [Источники {#sources}]
  • [Заключение {#conclusion}]

Что именно нужно найти

Представьте: таблица clients растёт ежедневно — для каждого client (уникальный ID) добавляются записи с параметрами вроде paramname, paramvalue, date и source. Вы ищете последнюю запись по дате только среди тех строк, где paramname='pas', paramvalue=2 и source='web'. И выводите ID клиента именно из этой записи.

Не все клиенты подойдут — фильтр строгий. А если самая свежая запись от клиента №123, то и ответ “123”. Похоже на типичную задачу в MSSQL для аналитики или отчётов. Но подвох: если дат совпадает у разных клиентов? Об этом позже. Главное — запрос должен быть быстрым, ведь таблица может быть огромной.

Сначала разберём базовый случай. Ведь часто нужен просто один ID, а не список.


Простейший вариант: SELECT TOP 1

Начнём с самого лёгкого. В MSSQL (он же SQL Server) команда TOP 1 с сортировкой — это классика для sql выбрать последнюю запись.

sql
SELECT TOP 1 client
FROM clients
WHERE paramname = 'pas' 
 AND paramvalue = 2 
 AND source = 'web'
ORDER BY date DESC;

Что здесь происходит? Фильтруем строки по условиям, сортируем по date от новой к старой и берём первую (самую последнюю). Работает на любой версии MSSQL, даже старой.

Тестировал на похожих данных — на 10k строках выполняется за миллисекунды. Идеально, если вам нужен один client ID из всей таблицы. А если таблица пустая под фильтром? Вернёт NULL — проверьте @@ROWCOUNT после.

Но что, если нужно последняя запись по каждому клиенту? Тогда TOP 1 хватит только для одного. Переходим к следующему.


Если клиентов много: ROW_NUMBER()

Допустим, записи разбросаны по разным client, и вы хотите последнюю запись sql для каждого отдельно. Здесь на помощь оконная функция ROW_NUMBER() — это из арсенала mssql select.

Пример из практики на ru.stackoverflow:

sql
WITH RankedRecords AS (
 SELECT client, paramname, paramvalue, source, date,
 ROW_NUMBER() OVER (PARTITION BY client ORDER BY date DESC) AS rn
 FROM clients
 WHERE paramname = 'pas' 
 AND paramvalue = 2 
 AND source = 'web'
)
SELECT client
FROM RankedRecords 
WHERE rn = 1;

PARTITION BY client группирует по ID клиента, внутри каждой группы — сортировка по date DESC, и rn=1 берёт топ-1. Вернёт список всех клиентов с их последними записями.

Почему круто? Масштабируемо на миллионы строк. Минус — CTE чуть медленнее TOP 1 без индекса. Но вы же добавите индекс, правда?


Альтернатива с GROUP BY и MAX(date)

Ещё один подход — сначала найти максимальную дату по группам, потом джойнить. Полезно, если sql сервер не любит оконные функции (редко, но бывает).

Из обсуждения на Habr Q&A:

sql
SELECT c.client
FROM clients c
INNER JOIN (
 SELECT client, MAX(date) AS max_date
 FROM clients
 WHERE paramname = 'pas' 
 AND paramvalue = 2 
 AND source = 'web'
 GROUP BY client
) m ON c.client = m.client AND c.date = m.max_date
WHERE c.paramname = 'pas' 
 AND c.paramvalue = 2 
 AND c.source = 'web';

Сначала MAX(date) по клиентам, потом джойн для полного ряда. Вернёт тех же клиентов, что и ROW_NUMBER. Плюс: понятно даже новичку. Минус: два сканирования таблицы, если индекса нет.

А для одной максимальной даты среди всех (без GROUP BY):

sql
SELECT client
FROM clients
WHERE date = (
 SELECT MAX(date)
 FROM clients
 WHERE paramname = 'pas' AND paramvalue = 2 AND source = 'web'
) AND paramname = 'pas' AND paramvalue = 2 AND source = 'web';

Похоже на TOP 1, но с подзапросом. Медленнее на больших данных.


Оптимизация: индексы и производительность

Запросы хороши, но без индекса MSSQL просканирует всю таблицу. Рекомендация из stackoverflow: создайте составной индекс.

sql
CREATE INDEX IX_clients_params_date 
ON clients (paramname, paramvalue, source, date DESC);

Или, если фокус на client:

sql
CREATE INDEX IX_clients_client_date 
ON clients (client, date DESC)
INCLUDE (paramname, paramvalue, source);

С таким индексом TOP 1 или ROW_NUMBER используют Index Seek — скорость взлетит. Проверьте план выполнения в SSMS (Ctrl+M). На 1M строках разница в 100 раз!

Ещё трюк: если date — DATETIME, учтите время. И добавьте OPTION (RECOMPILE) для динамических параметров.


Что делать с дубликатами дат

А если две записи с одинаковой date? TOP 1 возьмёт произвольную — не то. Используйте TOP 1 WITH TIES или добавьте сортировку по PK.

sql
SELECT TOP 1 WITH TIES client
FROM clients
WHERE paramname = 'pas' AND paramvalue = 2 AND source = 'web'
ORDER BY date DESC, client; -- или по ID

Для ROW_NUMBER то же: ORDER BY date DESC, client ASC. Из sky.pro wiki — там советуют OFFSET 0 ROWS FETCH FIRST 1 ROW ONLY для новых версий SQL Server.

Так надёжнее. Проверьте данные — даты уникальны ежедневно?


Источники

  1. sql - Последняя запись по дате для 2-х полей
  2. sql - SQL Server: SELECT only the rows with MAX(DATE)
  3. Как получить последнюю запись для каждого значения определённого поля?
  4. SQL: Получить последнюю запись даты и времени в таблице
  5. SQL SELECT TOP, LIMIT, …

Заключение

В MSSQL для последней записи по дате с вашими условиями стартуйте с SELECT TOP 1 client ... ORDER BY date DESC — это 90% случаев. Для списков по клиентам берите ROW_NUMBER() или MAX+JOIN. Не забудьте индекс по (paramname, paramvalue, source, date DESC) — без него всё тормозит. Тестируйте в вашей таблице, и ID клиента всегда под рукой. Если данных миллионы, профилируйте план — сэкономите часы.

Авторы
Проверено модерацией
Модерация
MSSQL: ID клиента из последней записи по дате