Estou otimizando uma consulta, onde tenho que juntar duas tabelas, digamos Product
e TransactionHistory
uma tabela, e retornar várias colunas de ambas as tabelas para a última TransactionDate
da TransactionHistory
tabela para cada Produto na Product
tabela.
TransactionHistory
table tem cerca de 13 milhões de linhas e a Product
tabela tem quase 2 milhões de linhas.
Parece muito fácil, não é? mas para diferentes cenários, a consulta diferente tem um desempenho diferente.
Não estou usando meus nomes de tabela reais aqui, para mostrar a sintaxe de consulta diferente que tentei, estou usando as tabelas BigProduct e BigSalesHistory de Adam Machanic.
A consulta foi originalmente escrita como ....
SELECT p.ProductID
, p.Name
, h.Quantity
, h.TransactionDate
FROM [dbo].[bigProduct] p
INNER JOIN [dbo].[bigTransactionHistory] h ON p.ProductID = h.ProductID
WHERE h.TransactionDate = ( SELECT MAX(TransactionDate)
FROM [dbo].[bigTransactionHistory]
WHERE ProductID = h.ProductID )
AND p.ProductID > 1317
AND p.ProductID < 1416
GO
O que resultou em cerca de 30 contagens de varredura e quase 300.000 leituras lógicas.
Eu sendo um grande fã de funções de janelas, escrevi as seguintes consultas usando ROW_NUMBER()
, ambas saíram pela culatra:
SELECT p.ProductID
, p.Name
, c.*
FROM [dbo].[bigProduct] p
INNER JOIN ( SELECT h.ProductID
, h.Quantity
, h.TransactionDate
, ROW_NUMBER() OVER (PARTITION BY h.ProductID
ORDER BY h.TransactionDate DESC) rn
FROM [dbo].[bigTransactionHistory] h
) c
ON p.ProductID = c.ProductID AND rn = 1
WHERE p.ProductID > 1317
AND p.ProductID < 1416
GO
E
SELECT p.ProductID
, p.Name
, c.*
FROM [dbo].[bigProduct] p
CROSS APPLY ( SELECT h.ProductID
, h.Quantity
, h.TransactionDate
, ROW_NUMBER() OVER (PARTITION BY h.ProductID
ORDER BY h.TransactionDate DESC) rn
FROM [dbo].[bigTransactionHistory] h
WHERE p.ProductID = ProductID
) c
WHERE p.ProductID > 1317
AND p.ProductID < 1416
AND rn = 1
GO
A contagem de varreduras foi para 100s e as leituras lógicas saltaram para 700.000s.
Ficando bastante desapontado com o desempenho da função de janela, reescrevi a consulta usandoCROSS APPLY
SELECT p.ProductID
, p.Name
, c.*
FROM [dbo].[bigProduct] p
CROSS APPLY ( SELECT TOP 1 h.ProductID
, h.Quantity
, h.TransactionDate
FROM [dbo].[bigTransactionHistory] h
WHERE p.ProductID = h.ProductID
ORDER BY h.TransactionDate
) c
WHERE p.ProductID > 1317
AND p.ProductID < 1416
GO
Agora, essa consulta parece ter o melhor desempenho de todas as mencionadas acima, mas assim que o intervalo da consulta externa para ProductID
aumenta, essa consulta dá terrivelmente errado e começa a ter um desempenho pior do que os dois acima.
Além disso, embora tenha reduzido as leituras lógicas para 27.000, mas o tempo de CPU e o tempo decorrido aumentaram 8 vezes.
Existe uma maneira mais eficiente de escrever uma consulta como essa, tendo em mente que o intervalo de consulta externo para ProductID varia muito?.
Qualquer sugestão ou indicações na direção certa são muito apreciadas. Obrigada.
Você precisa pensar em como gostaria de resolver isso se tivesse um sistema baseado em papel.
Como - você deseja usar cada ProductID um por um e encontrar o mais novo? Então seu loop aninhado que chama um Seek usando TOP 1 em um índice em (ProductID, Transaction Date DESC) será o melhor.
Deseja pesquisar todas as suas transações e agrupá-las por ProductID e obter o máximo? Então essa abordagem pode ser a melhor.
Mas eu sugiro a você que o que você faria na vida real é fazer a segunda abordagem em um subconjunto de seus dados (como o mês mais recente) e, em seguida, fazer uma pesquisa produto por produto naqueles isso não funcionou. Tente uma junção externa em uma subconsulta que agrupe por ProductID e filtre por TransactionDate para "bastante recente". Em seguida, faça um Outer Apply para fazer um Top 1, mas inclua um Predicate lá como
where m.ProductID IS NULL
, para que ele faça apenas aqueles Seeks para produtos que foram perdidos na primeira rodada. Você deve ver um filtro com um predicado de expressão de inicialização e observar que a busca de índice é executada muito menos vezes.Mostro esse tipo de coisa em http://blogs.lobsterpot.com.au/2014/07/08/ssis-lookup-transformation-in-t-sql/ - procure o plano que mostro, no exemplo com dois Bits de aplicação externa.