Можно ли изменить этот Sql-оператор, чтобы НЕ использовать RANK / PARTITION? - PullRequest
3 голосов
/ 07 февраля 2010

У меня есть следующий SQL-оператор, который прекрасно работает. Я надеялся увидеть, как это можно изменить, чтобы не использовать RANK / PARTITION ... если возможно.

SELECT LogEntryId, FileId, CreatedOn, EventTypeId
FROM (SELECT a.LogEntryId, a.FileId, a.CreatedOn,  a.EventTypeId, 
        RANK() OVER (PARTITION BY ClientName ORDER BY a.CreatedOn DESC) AS MostRecentEventRank
    FROM LogEntries a
    WHERE (a.EventTypeId = 2 or a.EventTypeId = 4)) SubQuery
WHERE MostRecentEventRank = 1

Что он пытается сделать?

  1. Соберите все записи в таблице, сгруппированные по имени клиента и затем упорядоченные по последним созданным.
  2. Фильтруйте это только по типам событий # 2 (соединение) или # 4 (разъединение).
  3. Теперь для каждого имени клиента извлекаем самую последнюю запись.

Это фактически захватывает самое последнее событие (для соединения или разъединения) для каждого уникального пользователя в таблице.

Мне нравится RANK / PARTITION, но я надеялся увидеть, можно ли обойтись без его использования.

Ответы [ 4 ]

4 голосов
/ 07 февраля 2010

Еще один вариант: выберите клиентов, затем используйте CROSS APPLY (.. TOP (1) ... ORDER BY ...), чтобы получить соответствующую запись.

SELECT c.ClientName,r.LogEntryId, r.FileId, r.CreatedOn,  r.EventTypeId
FROM (
 SELECT DISTINCT ClientName
 FROM LogEntries
 WHERE EventTypeId IN (2,4)) as c
CROSS APPLY (
   SELECT TOP (1) a.LogEntryId, a.FileId, a.CreatedOn,  a.EventTypeId
   FROM LogEntries as a
   WHERE a.ClientName = c.ClientName
   AND a.EventTypeId IN (2,4)
   ORDER BY a.CreatedOn DESC) as r;

Обновление

Говорить о производительности по запросу T-SQL, не зная схемы, бессмысленно. Этот запрос идеально подходит для правильно спроектированной схемы для своих нужд. Поскольку доступ осуществляется через ClientName и CreatedOn, то даже упрощенная схема должна учитывать это:

CREATE TABLE LogEntries (
   LogEntryId int identity(1,1),
   FileID int,
   CreatedOn datetime,
   EventTypeID int,
   ClientName varchar(30)
);

create clustered index cdxLogEntries on LogEntries (
    ClientName, CreatedOn DESC);
go

И давайте загрузим таблицу с некоторыми 2,4M строками:

declare @i int;
set @i = 0;

while @i < 1000
begin
    insert into LogEntries (FileId, CreatedOn, EventTypeId, ClientName)
    select cast(rand()*100 as int),
        dateadd(minute, -rand()*10000, getdate()),
        cast(rand() * 5 as int),
        'Client' + cast(@i as varchar(10))
        from master..spt_values;
    set @i = @i+1;
end

Сколько времени и IO мы получаем с set statistics io on; set statistics time on; в нагретом кеше?

(410 row(s) affected)
Table 'LogEntries'. Scan count 411, logical reads 14354, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 1219 ms,  elapsed time = 1932 ms.

1,9 с, чтобы получить данные от 2,4 М записей на моем ноутбуке (которому 4 года и 1 ГБ ОЗУ). И еще есть много возможностей для улучшения дизайна схемы. Разделение ClientName на нормализованную таблицу с доверенным внешним ключом от LogEntries позволит значительно сократить время. Правильные отфильтрованные индексы на EntryTypeId IN (2,4) также будут способствовать. Мы даже не начали исследовать возможности параллелизма.

Это SQL, производительность получается на чертежной доске вашей схемы, а не в текстовом редакторе вашего запроса.

2 голосов
/ 07 февраля 2010

Сканирование одной таблицы, отсутствие оконной функции, одиночная группировка, нет проблем с дублирующимися датами, одинаковая производительность с оконными функциями или даже они превосходят их при действительно больших запросах. (Обновление: я не знаю, как он работает по сравнению с методом TOP 1 WITH TIES / CROSS APPLY. Так как он использует сканирование, он может быть медленнее в некоторых ситуациях.)

SELECT
   LogEntryID = Convert(int, Substring(Packed, 9, 4)),
   FileID = Convert(int, Substring(Packed, 13, 4)),
   CreatedOn = Convert(datetime, Substring(Packed, 1, 8)),
   EventTypeID = Convert(int, Substring(Packed, 17, 4))
FROM
   (
      SELECT
         Packed = Max(
            Convert(binary(8), CreatedOn)
            + Convert(binary(4), LogEntryID)
            + Convert(binary(4), FileID)
            + Convert(binary(4), EventTypeID)
         )
      FROM LogEntries
      WHERE EventTypeID IN (2,4)
      GROUP BY ClientName
   ) X

Если кто-то хотел бы увидеть это в действии, вот сценарий создания:

USE tempdb
CREATE TABLE LogEntries (
   LogEntryID int not null identity(1,1),
   FileID int,
   CreatedOn datetime,
   EventTypeID int,
   ClientName varchar(30)
)

INSERT LogEntries VALUES (1, GetDate()-20, 2, 'bob')
INSERT LogEntries VALUES (1, GetDate()-19, 3, 'bob')
INSERT LogEntries VALUES (1, GetDate()-18, 4, 'bob')
INSERT LogEntries VALUES (1, GetDate()-17, 3, 'bob')
INSERT LogEntries VALUES (1, GetDate()-19.5, 2, 'anna')
INSERT LogEntries VALUES (1, GetDate()-18.5, 3, 'anna')
INSERT LogEntries VALUES (1, GetDate()-17.5, 4, 'anna')
INSERT LogEntries VALUES (1, GetDate()-16.5, 3, 'anna')

Обратите внимание, что этот метод использует внутреннее представление байтов данных типов данных, имеющих тот же порядок, что и значения типа. Упакованные типы данных, такие как float или decimal, НЕ будут работать: для этого потребуется сначала преобразовать их во что-нибудь подходящее, например, int, bigint или символьные.

Кроме того, новые типы данных Date и Time в SQL 2008 имеют разные представления, которые не будут правильно упаковываться для использования с этим методом. Я еще не исследовал тип данных Time, но для типа данных Date:

DECLARE @d date
SET @d ='99990101'
SELECT Convert(binary(3), @d) -- 0x6EB837

Фактическое значение - 0x37B86E, поэтому оно хранится в обратном байтовом порядке (нулевая дата - 0001-01-01).

1 голос
/ 07 февраля 2010

Вы можете использовать эксклюзив left join:

select     cur.*
from       LogEntries cur
left join  LogEntries next
on         next.ClientName = cur.ClientName
           and next.EventTypeId in (2,4)
           and next.CreatedOn > cur.CreatedOn               
where      next.ClientName is null
           and cur.EventTypeId in (2,4)

Соединяет таблицу самостоятельно, ищет более поздние строки в условии on. В предложении where вы указываете, что более поздняя строка не может существовать. Таким образом вы отфильтровываете все, кроме последней строки, для каждого клиента.

0 голосов
/ 07 февраля 2010

Вот, пожалуйста. Может быть быстрее ... не уверен. Также предполагается, что ClientName + CreatedOn уникален.

;WITH MostRecent AS
(
   SELECT ClientName, Max(CreatedOn) AS CreatedOn
   FROM LogEntries
   WHERE EventTypeID IN (2,4)
   GROUP BY ClientName
)
SELECT LogEntryId, FileId, CreatedOn, EventTypeId
FROM LogEntries L
INNER JOIN MostRecent R ON L.ClientName = R.ClientName AND L.CreatedOn = R.CreatedON

Обратите внимание, я не проверял, возможно, опечатки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...