В SQL Server 2005+
:
SELECT oo.*
FROM (
SELECT DISTINCT ProductId
FROM Orders
) od
CROSS APPLY
(
SELECT TOP 1 ProductID, Date, CustomerID
FROM Orders oi
WHERE oi.ProductID = od.ProductID
ORDER BY
Date DESC
) oo
Номинально план запроса содержит Nested Loops
.
Однако внешний цикл будет использовать Index Scan
с Stream Aggregate
, а внутренний цикл будет содержать Index Seek
для ProductID
с Top
.
На самом деле вторая операция практически бесплатна, поскольку страница индекса, используемая во внутреннем цикле, скорее всего, будет находиться в кэше, поскольку она только что использовалась для внешнего цикла.
Вот результат теста для 1,000,000
строк (с 100
DISTINCT
ProductID
s):
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 1 ms.
(строк обработано: 100)
Table 'Orders'. Scan count 103, logical reads 6020, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, 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 = 234 ms, elapsed time = 125 ms.
, хотя это результат простого SELECT DISTINCT
запроса:
SELECT od.*
FROM (
SELECT DISTINCT ProductId
FROM Orders
) od
И статистика:
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 1 ms.
(строк обработано: 100)
Table 'Orders'. Scan count 3, logical reads 5648, 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 = 250 ms, elapsed time = 125 ms.
Как мы видим, производительность такая же, и CROSS APPLY
занимает всего 400
дополнительно logical reads
(что, скорее всего, никогда не будет physical
).
Не понимаю, как можно улучшить этот запрос.
Также преимущество этого запроса в том, что он хорошо распараллеливается. Вы можете заметить, что CPU
время вдвое больше elapsed time
: это связано с распараллеливанием на моем старом Core Duo
.
A 4-core
CPU
выполнит этот запрос за половину этого времени.
Решения, использующие оконные функции, не распараллеливаются:
SELECT od.*
FROM (
SELECT ProductId, Date, CustomerID, ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY Date DESC) AS rn
FROM Orders
) od
WHERE rn = 1
, а вот статистика:
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 1 ms.
(строк обработано: 100)
Table 'Orders'. Scan count 1, logical reads 5123, 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 = 406 ms, elapsed time = 415 ms.