¿Por qué esta consulta, que falta una cláusula FROM, no produce un error?

9

Entonces tenemos una consulta con una subconsulta que tiene un error tipográfico. Falta la cláusula FROM. Pero cuando lo ejecutas, ¡no falla! ¿¡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
fuente

Respuestas:

21

Esta declaración es legal (en otras palabras, no FROMse requiere):

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

El truco es cuando introduce un nombre de columna que claramente no puede existir. Entonces estos fallan:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

Mensaje 207, Nivel 16, Estado 1
Nombre de columna no válido 'nombre'.
Mensaje 207, Nivel 16, Estado 1
Nombre de columna no válido 'id'.

Pero cuando la columna no válida se introduce en algo así como una subconsulta, lo que hace SQL Server cuando no puede encontrar esa columna en el alcance interno de la subconsulta, es transversal a un alcance externo y hace que la subconsulta esté correlacionada con ese alcance externo. Esto devolverá todas las filas, por ejemplo:

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

Porque esencialmente dice:

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

Ni siquiera necesita una WHEREcláusula en la subconsulta:

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

Puede ver que realmente está mirando la tabla de alcance exterior, porque esto:

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

Devuelve muchas menos filas (11 en mi sistema).

Esto implica el cumplimiento del estándar sobre el alcance. Puede ver cosas similares cuando tiene dos tablas #temp:

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

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

Obviamente, esto debería error, lo correcto, ya que no es fooen #bar? No Lo que sucede es que SQL Server dice: "Oh, no encontré un fooaquí, debes haber querido decir el otro".

Además, en general, lo evitaría NOT IN. NOT EXISTStiene el potencial de ser más eficiente en algunos escenarios, pero lo más importante es que su comportamiento no cambia cuando es posible que la columna objetivo lo sea NULL. Vea esta publicación para más información .

Aaron Bertrand
fuente
Hice una pregunta sobre Stack Overflow a la que la respuesta es esencialmente la misma que esta (aunque la suya es más exhaustiva). ¿Por qué hacer referencia a una columna (como un operando de la izquierda) que no es parte de la tabla que se está consultando no es un error en el operador EXISTS?
Marc.2377
2

Reproduje esto en 2016 con un ejemplo 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 y c3 se evalúan para cada fila.

paulbarbin
fuente
1

En SQL Server, la sintaxis SELECT no requiere la sección FROM. Si omite FROM, la instrucción select usará la tabla "ficticia" que tiene una fila y ninguna columna. Entonces

select 'x' as c where ...

devolverá una fila si la expresión es verdadera y no habrá filas cuando sea falsa.

Piotr
fuente
Pero eso no funciona si solo dice select cy cno existe en algún objeto externo. Estoy de acuerdo en que FROMno es necesaria, pero la mecánica de juego aquí cuando nombre explícitamente una columna que hace existir en un ámbito exterior son definitivamente diferente a una tabla ficticia, y si usted no proporciona una constante para una columna que no lo hace existe, obtiene un error de tiempo de ejecución, por lo que tampoco hay una tabla ficticia allí. La tabla ficticia puede entrar en juego en otros escenarios, pero no cuando la referencia está en una tabla de subconsulta / derivada.
Aaron Bertrand
En su ejemplo, es una sub-selección correlacionada, role_id y role_object_id pertenece a una de las tablas en la selección externa.
Piotr
Correcto, pero decir SELECT 'x' AS ces un escenario completamente diferente al de los OP, que acaba de decir SELECT c. En una subconsulta / tabla derivada.
Aaron Bertrand