J'essaie d'améliorer les performances de la requête suivante:
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
Actuellement, avec mes données de test, cela prend environ une minute. J'ai une quantité limitée d'entrée dans les modifications de la procédure stockée dans laquelle réside cette requête, mais je peux probablement les amener à modifier cette seule requête. Ou ajoutez un index. J'ai essayé d'ajouter l'index suivant:
CREATE CLUSTERED INDEX ix_test ON #TempTable(AgentID, RuleId, GroupId, Passed)
Et cela a en fait doublé le temps nécessaire à la requête. J'obtiens le même effet avec un index NON CLUSTERED.
J'ai essayé de le réécrire comme suit sans effet.
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
Ensuite, j'ai essayé d'utiliser une fonction de fenêtrage comme celle-ci.
UPDATE [#TempTable]
SET Received = COUNT(DISTINCT (CASE WHEN Passed=1 THEN GroupId ELSE NULL END))
OVER (PARTITION BY AgentId, RuleId)
FROM [#TempTable]
À ce stade, j'ai commencé à obtenir l'erreur
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near 'distinct'.
J'ai donc deux questions. Tout d'abord, ne pouvez-vous pas faire un COUNT DISTINCT avec la clause OVER ou l'ai-je simplement écrit de manière incorrecte? Et deuxièmement, quelqu'un peut-il suggérer une amélioration que je n'ai pas encore essayée? Pour info, il s'agit d'une instance SQL Server 2008 R2 Enterprise.
EDIT: Voici un lien vers le plan d'exécution d'origine. Je dois également noter que mon gros problème est que cette requête est exécutée 30 à 50 fois.
https://OneDrive.live.com/redir?resid=4C359AF42063BD98%21772
EDIT2: Voici la boucle complète dans laquelle se trouve l'instruction comme demandé dans les commentaires. Je vérifie régulièrement avec la personne qui travaille avec cela le but de la boucle.
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
Cette construction n'est pas actuellement prise en charge dans SQL Server. Il pourrait (et devrait, à mon avis) être implémenté dans une future version.
En appliquant l'une des solutions de contournement répertoriées dans le élément de rétroaction signalant cette lacune, votre requête pourrait être réécrite comme suit:
WITH UpdateSet AS
(
SELECT
AgentID,
RuleID,
Received,
Calc = SUM(CASE WHEN rn = 1 THEN 1 ELSE 0 END) OVER (
PARTITION BY AgentID, RuleID)
FROM
(
SELECT
AgentID,
RuleID,
Received,
rn = ROW_NUMBER() OVER (
PARTITION BY AgentID, RuleID, GroupID
ORDER BY GroupID)
FROM #TempTable
WHERE Passed = 1
) AS X
)
UPDATE UpdateSet
SET Received = Calc;
Le plan d'exécution qui en résulte est le suivant:
Cela a l'avantage d'éviter une bobine de table désireuse pour Protection Halloween (en raison de l'auto-jointure), mais cela introduit un tri (pour la fenêtre) et une construction de bobine de table paresseuse souvent inefficace pour calculer et appliquez le résultat SUM OVER (PARTITION BY)
à toutes les lignes de la fenêtre. Ce qu'il fait en pratique est un exercice que vous seul pouvez effectuer.
L'approche globale est difficile à faire bien performer. L'application récursive de mises à jour (en particulier celles basées sur une auto-jointure) à une grande structure peut être bonne pour le débogage mais c'est une recette pour de mauvaises performances. Les analyses répétées de grande taille, les déversements de mémoire et les problèmes d'Halloween ne sont que quelques-uns des problèmes. L'indexation et (plus) de tables temporaires peuvent aider, mais une analyse très minutieuse est nécessaire, surtout si l'index est mis à jour par d'autres instructions dans le processus (le maintien des index affecte les choix du plan de requête et ajoute des E/S).
En fin de compte, la résolution du problème sous-jacent rendrait le travail de conseil intéressant, mais c'est trop pour ce site. J'espère cependant que cette réponse répond à la question de surface.
Interprétation alternative de la requête d'origine (entraîne la mise à jour de plusieurs lignes):
WITH UpdateSet AS
(
SELECT
AgentID,
RuleID,
Received,
Calc = SUM(CASE WHEN Passed = 1 AND rn = 1 THEN 1 ELSE 0 END) OVER (
PARTITION BY AgentID, RuleID)
FROM
(
SELECT
AgentID,
RuleID,
Received,
Passed,
rn = ROW_NUMBER() OVER (
PARTITION BY AgentID, RuleID, Passed, GroupID
ORDER BY GroupID)
FROM #TempTable
) AS X
)
UPDATE UpdateSet
SET Received = Calc
WHERE Calc > 0;
Remarque: l'élimination du tri (par exemple en fournissant un index) pourrait réintroduire le besoin d'une bobine désireuse ou autre chose pour fournir la protection d'Halloween nécessaire. Le tri est un opérateur de blocage, il fournit donc une séparation de phase complète.
Nécromancement:
Il est relativement simple d'émuler un nombre distinct sur une partition avec DENSE_RANK:
;WITH baseTable AS
(
SELECT 'RM1' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM1' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR3' AS ADR
UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM3' AS RM, 'ADR2' AS ADR
)
,CTE AS
(
SELECT RM, ADR, DENSE_RANK() OVER(PARTITION BY RM ORDER BY ADR) AS dr
FROM baseTable
)
SELECT
RM
,ADR
,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY ADR) AS cnt1
,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM) AS cnt2
-- Geht nicht / Doesn't work
--,COUNT(DISTINCT CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY CTE.ADR) AS cntDist
,MAX(CTE.dr) OVER (PARTITION BY CTE.RM ORDER BY CTE.RM) AS cntDistEmu
FROM CTE