Da dove provengono questa Scansione Costante e Join esterno sinistro in un banale piano di query SELECT?


21

Ho questa tabella:

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

Questa query:

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

viene eseguito con un piano di query costituito da una singola ricerca indice - come previsto:

SELECT <---- Clustered Index Seek

Questa query fa lo stesso:

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

ma viene eseguito con un piano in cui il risultato di Index Seek è lasciato esterno unito con il risultato di una scansione costante e quindi immesso nel calcolo scalare:

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

Cos'è quella magia in più? Che cosa fa quella scansione costante seguita dall'unione esterna sinistra?

Risposte:


29

La semantica delle due affermazioni è diversa:

  • Il primo non imposta il valore della variabile se non viene trovata alcuna riga.
  • Il secondo imposta sempre la variabile, incluso su null se non viene trovata alcuna riga.

La scansione costante produce una riga vuota (senza colonne!) Che comporterà l'aggiornamento della variabile nel caso in cui nulla corrisponda alla tabella di base. Il join sinistro garantisce che la riga vuota sopravviva al join. L'assegnazione delle variabili può essere considerata come avvenuta nel nodo principale del piano di esecuzione.

utilizzando 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;

Risultato 1

utilizzando 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;

Risultato 2

Piani di esecuzione

SELEZIONA assegnazioneNessuna riga arriva al nodo principale, quindi non si verifica alcuna assegnazione.

Assegnazione SETUna riga arriva sempre al nodo principale, quindi si verifica l'assegnazione variabile.


La Scansione Costante extra e la giunzione esterna sinistra dei loop nidificati non sono nulla di cui preoccuparsi. Il join, in particolare, è economico poiché garantisce di incontrare una riga sul suo input esterno e al massimo una riga (nel tuo esempio) sull'input interno.

Esistono altri modi per garantire che una riga venga generata dalla sottoquery per garantire che si verifichi un'assegnazione variabile. Uno è usare un aggregato scalare ridondante (nessuna clausola 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;

Risultato 3

Piano di esecuzione aggregato scalare

Si noti che l'aggregato scalare produce una riga anche se non riceve input.

Documentazione:

Se l'istruzione SELECT non restituisce righe, la variabile mantiene il suo valore attuale. Se expression è una sottoquery scalare che non restituisce alcun valore, la variabile è impostata su NULL.

Per l'assegnazione delle variabili, si consiglia di utilizzare SET @local_variable anziché SELECT @local_variable.

Ulteriori letture:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.