Eu tenho esta tabela:
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
Esta consulta:
DECLARE @result UNIQUEIDENTIFIER
SELECT @result = AccountId FROM Accounts WHERE AccountId='guid-here'
executa com um plano de consulta que consiste em uma única busca de índice - como esperado:
SELECT <---- Clustered Index Seek
Esta consulta faz o mesmo:
DECLARE @result UNIQUEIDENTIFIER
SET @result = (SELECT AccountId FROM Accounts WHERE AccountId='guid-here')
mas é executado com um plano em que o resultado do Index Seek é Left Outer Joined com o resultado de alguma Constant Scan e, em seguida, alimentado no Compute Scalar:
SELECT <--- Compute Scalar <--- Left Outer Join <--- Constant Scan
^
|------Clustered Index Seek
O que é essa magia extra? O que faz a Varredura Constante seguida pela Junção Externa Esquerda?
A semântica das duas declarações é diferente:
O Constant Scan produz uma linha vazia (sem colunas!) que resultará na atualização da variável caso nada corresponda à tabela base. A junção à esquerda garante que a linha vazia sobreviva à junção. A atribuição de variável pode ser considerada como ocorrendo no nó raiz do plano de execução.
Usando
SELECT @result
Usando
SET @result
Planos de execução
O Constant Scan extra e o Nested Loops Left Outer Join não são motivo de preocupação. A junção, em particular, é barata, pois é garantido que encontrará uma linha em sua entrada externa e no máximo uma linha (no seu exemplo) na entrada interna.
Existem outras maneiras de garantir que uma linha seja gerada a partir da subconsulta para garantir que ocorra uma atribuição de variável. Uma delas é usar um agregado escalar redundante (sem cláusula group by):
Observe que o agregado escalar produz uma linha mesmo que não receba nenhuma entrada.
Documentação:
Leitura adicional: