Estou tentando melhorar o desempenho da seguinte consulta:
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupId)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
) r ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
Atualmente, com meus dados de teste, leva cerca de um minuto. Eu tenho uma quantidade limitada de entrada em alterações no procedimento armazenado geral em que essa consulta reside, mas provavelmente posso fazer com que eles modifiquem essa consulta. Ou adicione um índice. Tentei adicionar o seguinte índice:
CREATE CLUSTERED INDEX ix_test ON #TempTable(AgentID, RuleId, GroupId, Passed)
E na verdade dobrou a quantidade de tempo que a consulta leva. Eu recebo o mesmo efeito com um índice NON-CLUSTERED.
Eu tentei reescrever da seguinte forma sem nenhum efeito.
WITH r AS (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupId)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
)
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN r
ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
Em seguida, tentei usar uma função de janela como esta.
UPDATE [#TempTable]
SET Received = COUNT(DISTINCT (CASE WHEN Passed=1 THEN GroupId ELSE NULL END))
OVER (PARTITION BY AgentId, RuleId)
FROM [#TempTable]
Neste ponto eu comecei a receber o erro
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near 'distinct'.
Então eu tenho duas perguntas. Primeiro você não pode fazer um COUNT DISTINCT com a cláusula OVER ou eu apenas escrevi incorretamente? E segundo, alguém pode sugerir uma melhoria que eu ainda não tenha tentado? Para sua informação, esta é uma instância do SQL Server 2008 R2 Enterprise.
EDIT: Aqui está um link para o plano de execução original. Também devo observar que meu grande problema é que essa consulta está sendo executada 30 a 50 vezes.
https://onedrive.live.com/redir?resid=4C359AF42063BD98%21772
EDIT2: Aqui está o loop completo em que a instrução está conforme solicitado nos comentários. Estou verificando com a pessoa que trabalha com isso regularmente quanto ao propósito do loop.
DECLARE @Counting INT
SELECT @Counting = 1
-- BEGIN: Cascading Rule check --
WHILE @Counting <= 30
BEGIN
UPDATE w1
SET Passed = 1
FROM [#TempTable] w1,
[#TempTable] w3
WHERE w3.AgentID = w1.AgentID AND
w3.RuleID = w1.CascadeRuleID AND
w3.RulePassed = 1 AND
w1.Passed = 0 AND
w1.NotFlag = 0
UPDATE w1
SET Passed = 1
FROM [#TempTable] w1,
[#TempTable] w3
WHERE w3.AgentID = w1.AgentID AND
w3.RuleID = w1.CascadeRuleID AND
w3.RulePassed = 0 AND
w1.Passed = 0 AND
w1.NotFlag = 1
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupID)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
) r ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
UPDATE [#TempTable]
SET RulePassed = 1
WHERE TotalNeeded = Received
SELECT @Counting = @Counting + 1
END
Essa construção não tem suporte no momento no SQL Server. Poderia (e deveria, na minha opinião) ser implementado em uma versão futura.
Aplicando uma das soluções alternativas listadas no item de feedback relatando essa deficiência, sua consulta pode ser reescrita como:
O plano de execução resultante é:
Isso tem a vantagem de evitar um Eager Table Spool para proteção de Halloween (devido à auto-junção), mas introduz uma ordenação (para a janela) e uma construção Lazy Table Spool muitas vezes ineficiente para calcular e aplicar o
SUM OVER (PARTITION BY)
resultado a todas as linhas na janela. Como ele funciona na prática é um exercício que só você pode realizar.A abordagem geral é difícil de fazer funcionar bem. Aplicar atualizações (especialmente aquelas baseadas em uma auto-junção) recursivamente a uma estrutura grande pode ser boa para depuração, mas é uma receita para um desempenho ruim. Varreduras grandes repetidas, vazamentos de memória e problemas de Halloween são apenas alguns dos problemas. A indexação e (mais) tabelas temporárias podem ajudar, mas é necessária uma análise muito cuidadosa, especialmente se o índice for atualizado por outras instruções no processo (a manutenção de índices afeta as escolhas do plano de consulta e adiciona E/S).
Em última análise, resolver o problema subjacente resultaria em um trabalho de consultoria interessante, mas é demais para este site. Espero que esta resposta aborde a questão superficial.
Interpretação alternativa da consulta original (resulta na atualização de mais linhas):
Nota: eliminar a classificação (por exemplo, fornecendo um índice) pode reintroduzir a necessidade de um Eager Spool ou qualquer outra coisa para fornecer a proteção de Halloween necessária. Sort é um operador de bloqueio, portanto, fornece separação de fase completa.
Necromante:
É relativamente simples emular uma contagem distinta sobre a partição com DENSE_RANK:
Edit:
Advertência: Obviamente, você precisa adicionar WHERE ADR IS NOT NULL se houver valores nulos.