web-dev-qa-db-fra.com

Jointure interne avec table dérivée à l'aide d'une sous-requête

Environnement: SQL 2008 R2

J'ai créé une table dérivée à l'aide de la sous-requête et rejoint la table principale. J'aime juste savoir si la sous-requête n'est exécutée qu'une seule fois ou si elle sera exécutée pour chaque ligne du jeu de résultats. Envisagez l'exemple suivant (noms de table fictifs pour référence uniquement)

SELECT E.EID,DT.Salary FROM Employees E
INNER JOIN
(
    SELECT EID, (SR.Rate * AD.DaysAttended) Salary
    FROM SalaryRate SR
    INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID

Donc, la sous-requête utilisée pour Inner Join ne sera exécutée qu'une ou plusieurs fois ??

Si je réécris la requête ci-dessus en utilisant OUTER APPLY, je sais avec certitude que la sous-requête sera exécutée pour chaque ligne. Voir ci-dessous.

SELECT E.EID,DT.Salary FROM Employees E
OUTER APPLY
(
    SELECT (SR.Rate * AD.DaysAttended) Salary
    FROM SalaryRate SR
    INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
    WHERE SR.EID=E.EID
) DT --Derived Table for outer apply

Il suffit donc de s'assurer qu'Inner Join n'exécutera la sous-requête qu'une seule fois.

11
love kumar

La première chose à noter est que vos requêtes ne sont pas comparables, OUTER APPLY doit être remplacé par CROSS APPLY, ou INNER JOIN avec LEFT JOIN.

Cependant, lorsqu'ils sont rendus comparables, vous pouvez voir que les plans de requête pour les deux requêtes sont identiques. Je viens de simuler un exemple de DDL:

CREATE TABLE #Employees (EID INT NOT NULL);
INSERT #Employees VALUES (0);
CREATE TABLE #SalaryRate (EID INT NOT NULL, Rate MONEY NOT NULL);
CREATE TABLE #AttendanceDetails (EID INT NOT NULL, DaysAttended INT NOT NULL);

Exécution de ce qui suit:

SELECT E.EID,DT.Salary FROM #Employees E
OUTER APPLY
(
    SELECT (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
    WHERE SR.EID=E.EID
) DT; --Derived Table for outer apply

SELECT E.EID,DT.Salary FROM #Employees E
LEFT JOIN
(
    SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;

Donne le plan suivant:

enter image description here

Et passer à INNER/CROSS:

SELECT E.EID,DT.Salary FROM #Employees E
CROSS APPLY
(
    SELECT (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
    WHERE SR.EID=E.EID
) DT; --Derived Table for outer apply


SELECT E.EID,DT.Salary FROM #Employees E
INNER JOIN
(
    SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;

Donne le plan suivant:

enter image description here

Ce sont les plans où il n'y a pas de données dans les tables externes et une seule ligne d'employés, donc pas vraiment réalistes. Dans le cas de l'application externe, SQL Server est en mesure de déterminer qu'il n'y a qu'une seule ligne dans les employés, il serait donc avantageux de simplement faire une jointure de boucle imbriquée (c'est-à-dire une recherche ligne par ligne) aux tables externes. Après avoir mis 1 000 lignes dans les employés, l'utilisation de LEFT JOIN/OUTER APPLY donne le plan suivant:

enter image description here

Vous pouvez voir ici que la jointure est désormais une jointure par correspondance de hachage, ce qui signifie (dans ses termes les plus simples) que SQL Server a déterminé que le meilleur plan consiste à exécuter d'abord la requête externe, à hacher les résultats, puis à rechercher des employés. Cela ne signifie cependant pas que la sous-requête dans son ensemble est exécutée et les résultats stockés, pour des raisons de simplicité, vous pouvez envisager cela, mais les prédicats de la requête externe peuvent toujours être utilisés, par exemple, si la sous-requête a été exécutée et stockée en interne , la requête suivante présenterait d'énormes frais généraux:

SELECT E.EID,DT.Salary FROM #Employees E
LEFT JOIN
(
    SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID
WHERE E.EID = 1;

Quel serait l'intérêt de récupérer tous les tarifs des employés, de stocker les résultats, pour ne rechercher réellement qu'un seul employé? L'examen du plan d'exécution montre que le EID = 1 le prédicat est passé à l'analyse de la table le #AttendanceDetails:

enter image description here

La réponse aux points suivants est donc:

  • Si je réécris la requête ci-dessus en utilisant OUTER APPLY, je sais avec certitude que la sous-requête sera exécutée pour chaque ligne.
  • Inner Join n'exécutera la sous-requête qu'une seule fois.

Cela dépend. En utilisant APPLY SQL Server tentera de réécrire la requête en tant que JOIN si possible, car cela produira le plan optimal, donc en utilisant OUTER APPLY ne garantit pas que la requête sera exécutée une fois pour chaque ligne. De même, en utilisant LEFT JOIN ne garantit pas que la requête n'est exécutée qu'une seule fois.

SQL est un langage déclaratif, en ce que vous lui dites ce que vous voulez qu'il fasse, pas comment le faire, vous ne devez donc pas vous fier à des commandes spécifiques pour susciter un comportement spécifique, mais si vous rencontrez des problèmes de performances, vérifiez le plan d'exécution , et IO statistiques pour savoir comment il le fait et identifier comment vous pouvez améliorer votre requête.

De plus, SQL Server ne matérialise pas les sous-requêtes, généralement la définition est développée dans la requête principale, donc même si vous avez écrit:

SELECT E.EID,DT.Salary FROM #Employees E
INNER JOIN
(
    SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;

Ce qui est réellement exécuté ressemble plus à:

SELECT  e.EID, sr.Rate * ad.DaysAttended AS Salary
FROM    #Employees e
        INNER JOIN #SalaryRate sr
            on e.EID = sr.EID
        INNER JOIN #AttendanceDetails ad
            ON ad.EID = sr.EID;
12
GarethD

Avec INNER JOIN, votre sous-requête ne sera exécutée qu'une seule fois et ses enregistrements peuvent être stockés en interne dans la table de travail tempdb sur des opérations complexes, puis JOIN avec la 1ère table.

Avec la clause APPLY, la sous-requête sera exécutée pour chaque ligne de la 1ère table.

modifier: utiliser CTE

;with SalaryRateCTE as 
(
    SELECT EID, (SR.Rate * AD.DaysAttended) AS Salary
    FROM SalaryRate SR
    INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
)
SELECT E.EID, DT.Salary 
FROM Employees E
INNER JOIN SalaryRateCTE DT --Derived Table for inner join
ON DT.EID = E.EID
2
Manoj Pandey

La sous-requête ne sera évaluée qu'une seule fois. Pour éviter toute confusion, nous pourrions simplement considérer la sous-requête comme une table/vue unique, car les requêtes internes et externes ne sont pas co-liées.

1
anonxen