Como minhas habilidades de ajuste de desempenho nunca parecem suficientes, sempre me pergunto se há mais otimização que posso executar em algumas consultas. A situação a que esta pergunta se refere é uma função MAX de janela aninhada em uma subconsulta.
Os dados que estou pesquisando são uma série de transações em vários grupos de conjuntos maiores. Eu tenho quatro campos de importância, o ID exclusivo de uma transação, o ID do grupo de um lote de transações e as datas associadas à respectiva transação ou grupo exclusivo de transações. Na maioria das vezes, a Data do grupo corresponde à Data máxima máxima de transação para um lote, mas há momentos em que ajustes manuais passam pelo nosso sistema e ocorre uma operação de data única após a captura da data da transação do grupo. Esta edição manual não ajusta a data do grupo por design.
O que identifico nesta consulta são os registros em que a Data Única cai após a Data do Grupo. O exemplo de consulta a seguir cria um equivalente aproximado do meu cenário e a instrução SELECT retorna os registros que estou procurando, no entanto, estou abordando esta solução da maneira mais eficiente? Isso demora um pouco para ser executado durante o carregamento da minha tabela de fatos, pois meu registro conta o número nos 9 dígitos superiores, mas principalmente meu desdém por subconsultas me faz pensar se há uma abordagem melhor aqui. Não estou tão preocupado com nenhum índice quanto tenho certeza de que eles já estão no lugar; o que estou procurando é uma abordagem de consulta alternativa que atinja a mesma coisa, mas com ainda mais eficiência. Qualquer feedback é bem-vindo.
CREATE TABLE #Example
(
UniqueID INT IDENTITY(1,1)
, GroupID INT
, GroupDate DATETIME
, UniqueDate DATETIME
)
CREATE CLUSTERED INDEX [CX_1] ON [#Example]
(
[UniqueID] ASC
)
SET NOCOUNT ON
--Populate some test data
DECLARE @i INT = 0, @j INT = 5, @UniqueDate DATETIME, @GroupDate DATETIME
WHILE @i < 10000
BEGIN
IF((@i + @j)%173 = 0)
BEGIN
SET @UniqueDate = GETDATE()+@i+5
END
ELSE
BEGIN
SET @UniqueDate = GETDATE()+@i
END
SET @GroupDate = GETDATE()+(@j-1)
INSERT INTO #Example (GroupID, GroupDate, UniqueDate)
VALUES (@j, @GroupDate, @UniqueDate)
SET @i = @i + 1
IF (@i % 5 = 0)
BEGIN
SET @j = @j+5
END
END
SET NOCOUNT OFF
CREATE NONCLUSTERED INDEX [IX_2_4_3] ON [#Example]
(
[GroupID] ASC,
[UniqueDate] ASC,
[GroupDate] ASC
)
INCLUDE ([UniqueID])
-- Identify any UniqueDates that are greater than the GroupDate within their GroupID
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
FROM (
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
, MAX(UniqueDate) OVER (PARTITION BY GroupID) AS maxUniqueDate
FROM #Example
) calc_maxUD
WHERE maxUniqueDate > GroupDate
AND maxUniqueDate = UniqueDate
DROP TABLE #Example
dbfiddle aqui
fonte
Respostas:
Estou assumindo que não há índice, pois você não forneceu nenhum.
Logo de cara, o seguinte índice eliminará um operador de classificação em seu plano, o que de outra forma consumiria muita memória:
A subconsulta não é um problema de desempenho neste caso. Se alguma coisa, eu procuraria maneiras de eliminar a função da janela (MAX ... OVER) para evitar a construção Nested Loop e Table Spool.
Com o mesmo índice, a consulta a seguir pode parecer menos eficiente à primeira vista e passa de duas a três varreduras na tabela base, mas elimina um grande número de leituras internamente porque não possui operadores de spool. Suponho que ele ainda terá um desempenho melhor, principalmente se você tiver núcleos de CPU e desempenho de IO suficientes no servidor:
(Observação: adicionei uma
MERGE JOIN
dica de consulta, mas isso provavelmente deve acontecer automaticamente se suas estatísticas estiverem em ordem. A melhor prática é deixar sugestões como essas, se possível.)fonte
Quando e se você puder atualizar do SQL Server 2012 para o SQL Server 2016, poderá aproveitar o desempenho muito aprimorado (especialmente para agregados de janelas sem moldura) fornecido pelo novo operador Agregado de Janela em modo de lote.
Quase todos os grandes cenários de processamento de dados funcionam melhor com o armazenamento columnstore do que com o rowstore. Mesmo sem alterar o columnstore para suas tabelas base, você ainda pode obter os benefícios da nova execução do modo operador e em lote de 2016, criando um índice filtrado columnstore não clusterizado vazio em uma das tabelas base ou ingressando de forma redundante em uma associação organizada por columnstore mesa.
Usando a segunda opção, a consulta se torna:
db <> violino
Observe que a única alteração na consulta original é criar uma tabela temporária vazia e adicionar a junção esquerda. O plano de execução é:
Para obter mais informações e opções, consulte a excelente série de Itzik Ben-Gan, O que você precisa saber sobre o Operador agregado da janela Modo de lote no SQL Server 2016 (em três partes).
fonte
Eu só vou jogar o velho Cross Apply por aí:
Com alguns tipos de índices, funciona muito bem.
O tempo e o io das estatísticas são assim (sua consulta é o primeiro resultado)
Os planos de consulta estão aqui (novamente, o seu é o primeiro):
https://www.brentozar.com/pastetheplan/?id=BJYJvqAal
Por que eu prefiro esta versão? Eu evito os carretéis. Se eles começarem a derramar em disco, vai ficar feio.
Mas você pode querer tentar isso também.
Se este for um DW grande, você pode preferir a Hash Join e a linha filtrada na junção, em vez de no final da
TOP 1
consulta como um operador Filter.O plano está aqui: https://www.brentozar.com/pastetheplan/?id=BkUF55ATx
Estatísticas de tempo e io aqui:
Espero que isto ajude!
Uma edição, com base na idéia de @ ypercube, e um novo índice.
Aqui estão as estatísticas de tempo e io:
Aqui está o plano:
https://www.brentozar.com/pastetheplan/?id=SJv8foR6g
fonte
Eu daria uma olhada
top with ties
Se
GroupDate
é o mesmo paraGroupId
então:Senão: usando
top with ties
em uma expressão de tabela comumdbfiddle: http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=c058994c2f5f3d99b212f06e1dae9fd3
Consulta original
vs
top with ties
em uma expressão de tabela comumfonte
Então, eu fiz algumas análises sobre as várias abordagens postadas até agora e, no meu ambiente, parece que a abordagem de Daniel vence de forma consistente nos tempos de execução. Surpreendentemente (para mim), a terceira abordagem CROSS APPLY de sp_BlitzErik não ficou muito atrás. Aqui estão as saídas, se alguém estiver interessado, mas obrigado por uma tonelada por todas as abordagens alternativas. Aprendi mais pesquisando as respostas sobre essa pergunta do que há muito tempo!
fonte
top with ties
fivelas com tantas linhas. dbfiddle.uk/...