Por que essa consulta, sem uma cláusula FROM, não gera erros?

9

Portanto, temos uma consulta com uma subconsulta que possui um erro de digitação. Está faltando a cláusula FROM. Mas quando você executá-lo, ele não erro! Por quê!?


SELECT

    1
   ,r.id
   ,'0D4133BE-C1B5-4141-AFAD-B171A2CCCE56'
   ,GETDATE()
   ,1
   ,'Y'
   ,'N'
   ,oldItem.can_view
   ,oldItem.can_update

FROM Role r

JOIN RoleObject oldReport
    ON r.customer_id = oldReport.customer_id

JOIN RoleItem oldItem
    ON oldReport.id = oldItem.role_object_id
        AND r.id = oldItem.role_id

WHERE r.id NOT IN (SELECT
        role_id
    WHERE role_object_id = '0D4133BE-C1B5-4141-AFAD-B171A2CCCE56')

AND oldReport.id = '169BA22F-1614-4EBA-AF45-18E333C54C6C'
Wjdavis5
fonte

Respostas:

21

Esta declaração é legal (em outras palavras, não FROMé necessária):

SELECT x = 1;
SELECT x = 1 WHERE 1 = 1; -- also try WHERE 1 = 0;

O truque é quando você introduz um nome de coluna que claramente não pode existir. Portanto, estes falham:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

Mensagem 207, Nível 16, Estado 1
Nome da coluna inválido 'name'.
Mensagem 207, Nível 16, Estado 1
Nome da coluna inválido 'id'.

Mas quando a coluna inválida é introduzida em algo como uma subconsulta, o que o SQL Server faz quando não consegue encontrar essa coluna no escopo interno da subconsulta, é direcionado para um escopo externo e torna a subconsulta correlacionada a esse escopo externo. Isso retornará todas as linhas, por exemplo:

SELECT * FROM sys.columns WHERE name IN (SELECT name WHERE 1 = 1);

Porque é essencialmente dizendo:

SELECT * FROM sys.columns WHERE name IN (SELECT sys.columns.name WHERE 1 = 1); /*
              ^^^^^^^^^^^                       -----------
                   |                                 |
                   -----------------------------------    */

Você nem precisa de uma WHEREcláusula na subconsulta:

SELECT * FROM sys.columns WHERE name IN (SELECT name);

Você pode ver que realmente está olhando para a tabela com escopo externo, porque isso:

SELECT * FROM sys.columns WHERE name IN (SELECT name WHERE name > N'x');

Retorna muito menos linhas (11 no meu sistema).

Isso envolve a adesão ao padrão sobre o escopo. Você pode ver coisas semelhantes quando tiver duas tabelas #temp:

CREATE TABLE #foo(foo int);
CREATE TABLE #bar(bar int);

SELECT foo FROM #foo WHERE foo IN (SELECT foo FROM #bar);

Obviamente, isso deve erro, direito, uma vez que não é fooem #bar? Não. O que acontece é que o SQL Server diz: "Ah, eu não encontrei um fooaqui, você deve ter pensado no outro".

Além disso, em geral, eu evitaria NOT IN. NOT EXISTStem o potencial de ser mais eficiente em alguns cenários, mas o mais importante é que seu comportamento não muda quando é possível que a coluna de destino possa ser NULL. Veja este post para mais informações .

Aaron Bertrand
fonte
Fiz uma pergunta no Stack Overflow, na qual a resposta é essencialmente a mesma (embora a sua seja mais completa). Por que fazer referência a uma coluna (como operando à esquerda) que não faz parte da tabela está sendo consultada, não é um erro no operador EXISTS?
Marc.2377
2

Reproduzi isso em 2016 com um exemplo simplificado:

declare @t1 table (c1 int, c2 int, c3 int)
insert into @t1 values (1,2,3), (2,3,4), (3,4,5)

select * from @t1
where
    c1 not in 
    (select c2 where c3 = 3)

Parece que c2 e c3 são avaliados para cada linha.

paulbarbin
fonte
1

No SQL Server, a sintaxe SELECT não requer a seção FROM. Se você omitir FROM, a instrução select usará a tabela "fictícia" que possui uma linha e nenhuma coluna. assim

select 'x' as c where ...

retornará uma linha se a expressão for verdadeira e nenhuma linha quando for falsa.

Piotr
fonte
Mas isso não funciona se você apenas diz select ce cnão existe em algum objeto externo. Concordo que FROMnão é necessária, mas a mecânica de jogo aqui quando você nomear explicitamente uma coluna que faz existir em um escopo externo são definitivamente diferente do que uma tabela fictícia, e se você não fornecer uma constante para uma coluna que não faz existe, você recebe um erro de tempo de execução, portanto, não há nenhuma tabela fictícia lá. A tabela fictícia pode entrar em jogo em outros cenários, mas não quando a referência está em uma subconsulta / tabela derivada.
Aaron Bertrand
No exemplo, é uma sub-seleção correlacionada, role_id e role_object_id pertence a uma das tabelas na seleção externa.
Piotr
Certo, mas dizer SELECT 'x' AS cé um cenário completamente diferente dos OP, que acabamos de dizer SELECT c. Em uma subconsulta / tabela derivada.
Aaron Bertrand