Atualização 2014-12-18
Com a resposta esmagadora à pergunta principal sendo "Não", as respostas mais interessantes se concentraram na parte 2, como resolver o quebra-cabeça de desempenho com um ORDER BY
. Embora eu já tenha marcado uma resposta, não ficaria surpreso se houvesse uma solução com desempenho ainda melhor.
Original
Essa dúvida surgiu porque a única solução extremamente rápida que consegui encontrar para um determinado problema só funciona sem uma ORDER BY
cláusula. Abaixo está o T-SQL completo necessário para produzir o problema, junto com minha solução proposta (estou usando o SQL Server 2008 R2, se isso for importante).
--Create Orders table
IF OBJECT_ID('tempdb..#Orders') IS NOT NULL DROP TABLE #Orders
CREATE TABLE #Orders
(
OrderID INT NOT NULL IDENTITY(1,1)
, CustID INT NOT NULL
, StoreID INT NOT NULL
, Amount FLOAT NOT NULL
)
CREATE CLUSTERED INDEX IX ON #Orders (StoreID, Amount DESC, CustID)
--Add 1 million rows w/ 100K Customers each of whom had 10 orders
;WITH
Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows
Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows
Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows
Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows
Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows
Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows
FinalCte AS (SELECT ROW_NUMBER() OVER (ORDER BY C) AS Number FROM Cte5)
INSERT INTO #Orders (CustID, StoreID, Amount)
SELECT CustID = Number / 10
, StoreID = Number % 4
, Amount = 1000 * RAND(Number)
FROM FinalCte
WHERE Number <= 1000000
SET STATISTICS IO ON
SET STATISTICS TIME ON
--For StoreID = 1, find the top 500 customers ordered by their most expensive purchase (Amount)
--Solution A: Without ORDER BY
DECLARE @Top INT = 500
SELECT DISTINCT TOP (@Top) CustID
FROM #Orders WITH(FORCESEEK)
WHERE StoreID = 1
OPTION(OPTIMIZE FOR (@Top = 1), FAST 1);
--9 logical reads, CPU Time = 0 ms, elapsed time = 1 ms
GO
--Solution B: With ORDER BY
DECLARE @Top INT = 500
SELECT TOP (@Top) CustID
FROM #Orders
WHERE StoreID = 1
GROUP BY CustID
ORDER BY MAX(Amount) DESC
OPTION(MAXDOP 1)
--745 logical reads, CPU Time = 141 ms, elapsed time = 145 ms
--Uses Sort operator
GO
Aqui estão os planos de execução para a Solução A e B, respectivamente:
A solução A fornece o desempenho de que preciso, mas não consegui fazê-la funcionar com o mesmo desempenho ao adicionar qualquer tipo de cláusula ORDER BY (por exemplo, consulte a Solução B). E certamente parece que a Solução A teria que entregar seus resultados em ordem, já que 1) a tabela tem apenas um índice nela, 2) uma busca é forçada, eliminando assim a possibilidade de usar uma varredura de ordem de alocação baseada em páginas IAM .
Então minhas perguntas são:
Estou certo de que garantirá o pedido neste caso sem uma cláusula de pedido?
Caso contrário, existe outro método para forçar um plano que seja tão rápido quanto a Solução A, preferencialmente um que evite classificações? Observe que ele teria que resolver exatamente o mesmo problema (para
StoreID = 1
, encontre os 500 principais clientes solicitados pelo valor de compra mais caro). Também teria que usar a#Orders
tabela, mas diferentes esquemas de indexação seriam OK.