Присоединяйтесь к рангу Время на одноразовом ранге, чтобы получить разрыв:
with cte_ranked as (
select *, row_number() over (partition by UserId order by Time) as rn
from table)
select l.*, datediff(minute, r.Time, l.Time) as gap_length
from cte_ranked l join cte_ranked r on l.UserId = r.UserId and l.rn = r.rn-1
Затем вы можете использовать множество методов, чтобы определить максимальный разрыв, когда он начался и т. Д.
Обновление
Мой оригинальный ответ был написан с Mac без базы данных для тестирования. У меня было еще немного времени, чтобы поиграть с этой проблемой и на самом деле протестировать и измерить, как она работает с таблицей записей 1M. Моя тестовая таблица определена так:
create table access (id int identity(1,1)
, UserId int not null
, Time datetime not null);
create clustered index cdx_access on access(UserID, Time);
go
Для выбора записи для любой информации мой предпочтительный ответ до сих пор таков:
with cte_gap as (
select Id, UserId, a.Time, (a.Time - prev.Time) as gap
from access a
cross apply (
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc) as prev)
, cte_max_gap as (
select UserId, max(gap) as max_gap
from cte_gap
group by UserId)
select g.*
from cte_gap g
join cte_max_gap m on m.UserId = g.UserId and m.max_gap = g.gap
where g.UserId = 42;
Из 1М записи, ~ 47k разных пользователей, результат для этого возвращается в 1мс на моем тестовом маленьком экземпляре (теплый кеш), чтение 48 страниц.
Если фильтр UserId = 42 удаляется, максимальный разрыв и время, за которое он произошел для каждого пользователя (с дубликатами для нескольких максимальных разрывов), требуют 6379139 операций чтения, довольно тяжелых и занимающих 14 с на моей тестовой машине.
Время можно сократить вдвое, если требуется только идентификатор пользователя и максимальный разрыв (нет информации , когда произошел максимальный разрыв):
select UserId, max(a.Time-prev.Time) as gap
from access a
cross apply (
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc
) as prev
group by UserId
Для этого требуется только 3193448 операций чтения, только половина по сравнению с предыдущими, и завершение за 6 секунд на записях 1M. Разница возникает из-за того, что предыдущей версии нужно было оценить каждый пробел один раз, чтобы найти максимальный, а затем снова оценить их, чтобы найти те, которые равны максимальному. Обратите внимание, что для этих результатов производительности структура таблицы, которую я предложил с индексом (UserId, Time), составляет критических .
Что касается использования CTE и «разделов» (более известных как функции ранжирования): это все ANSI SQL-99 и поддерживается большинством поставщиков. Единственной конструкцией, специфичной для SQL Server, было использование функции datediff
, которая теперь удалена. У меня есть чувство, что некоторые читатели понимают «независимость» как «наименее распространенный знаменатель SQL, понимаемый также моим любимым поставщиком». Также обратите внимание, что использование общих табличных выражений и оператора перекрестного применения используются исключительно для улучшения читаемости запроса. Оба могут быть заменены производной таблицей с помощью простой механической замены. Вот тот же самый запрос , где CTE были заменены производными таблицами. Я позволю вам судить о его удобочитаемости по сравнению с CTE:
select g.*
from (
select Id, UserId, a.Time, (a.Time - (
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc
)) as gap
from access a) as g
join (
select UserId, max(gap) as max_gap
from (
select Id, UserId, a.Time, (a.Time - (
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc
)) as gap
from access a) as cte_gap
group by UserId) as m on m.UserId = g.UserId and m.max_gap = g.gap
where g.UserId = 42
Черт, я прыгал, в итоге получится более запутанным, лол. Это вполне читабельно, потому что у него было только два CTE. Тем не менее, при запросах с 5-6 производными таблицами форма CTE более удобна для чтения.
Для полноты, вот то же преобразование, примененное к моему упрощенному запросу (только максимальные промежутки, без времени окончания промежутка и идентификатора доступа):
select UserId, max(gap)
from (
select UserId, a.Time-(
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc) as gap
from access a) as gaps
group by UserId