D'où viennent ce balayage constant et cette jointure externe gauche dans un plan de requête SELECT trivial?


21

J'ai ce tableau:

CREATE TABLE [dbo].[Accounts] (
    [AccountId] UNIQUEIDENTIFIER UNIQUE NOT NULL DEFAULT NEWID(),
    -- WHATEVER other columns
);
GO
CREATE UNIQUE CLUSTERED INDEX [AccountsIndex]
    ON [dbo].[Accounts]([AccountId] ASC);
GO

Cette requête:

DECLARE @result UNIQUEIDENTIFIER
SELECT @result = AccountId FROM Accounts WHERE AccountId='guid-here'

s'exécute avec un plan de requête composé d'une seule recherche d'index - comme prévu:

SELECT <---- Clustered Index Seek

Cette requête fait de même:

DECLARE @result UNIQUEIDENTIFIER
SET @result = (SELECT AccountId FROM Accounts WHERE AccountId='guid-here')

mais il est exécuté avec un plan où le résultat de la recherche d'index est joint à gauche avec le résultat d'un scan constant, puis introduit dans Compute Scalar:

SELECT <--- Compute Scalar <--- Left Outer Join <--- Constant Scan
                                      ^
                                      |------Clustered Index Seek

Quelle est cette magie supplémentaire? Que fait ce balayage constant suivi de la jointure externe gauche?

Réponses:


29

La sémantique des deux déclarations est différente:

  • Le premier ne définit pas la valeur de la variable si aucune ligne n'est trouvée.
  • Le second définit toujours la variable, y compris sur null si aucune ligne n'est trouvée.

Le scan constant produit une ligne vide (sans colonnes!) Qui entraînera la mise à jour de la variable au cas où rien ne correspondrait à la table de base. La jointure gauche garantit que la ligne vide survit à la jointure. L'affectation variable peut être considérée comme se produisant au nœud racine du plan d'exécution.

En utilisant SELECT @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result does not change
SELECT @result = AccountId 
FROM Accounts 
WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'};

SELECT @result;

Résultat 1

En utilisant SET @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
(
    SELECT AccountId 
    FROM Accounts 
    WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'}
);

SELECT @result;

Résultat 2

Plans d'exécution

SELECT affectationAucune ligne n'arrive au nœud racine, donc aucune affectation ne se produit.

Affectation SETUne ligne arrive toujours au nœud racine, donc l'affectation des variables se produit.


Le balayage constant supplémentaire et la jointure externe gauche des boucles imbriquées ne sont pas à craindre. La jointure en particulier est bon marché car elle est garantie de rencontrer une ligne sur son entrée externe et au plus une ligne (dans votre exemple) sur l'entrée interne.

Il existe d'autres façons de garantir qu'une ligne est générée à partir de la sous-requête pour garantir qu'une affectation de variable se produit. L'une consiste à utiliser un agrégat scalaire redondant (pas de clause group by):

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
    (
        SELECT MAX(AccountId)
        FROM Accounts 
        WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'} 
    );
SELECT @result;

Résultat 3

Plan d'exécution agrégé scalaire

Notez que l'agrégat scalaire produit une ligne même s'il ne reçoit aucune entrée.

Documentation:

Si l'instruction SELECT ne renvoie aucune ligne, la variable conserve sa valeur actuelle. Si expression est une sous-requête scalaire qui ne renvoie aucune valeur, la variable est définie sur NULL.

Pour affecter des variables, nous vous recommandons d'utiliser SET @local_variable au lieu de SELECT @local_variable.

Lectures complémentaires:

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.