¿Por qué CROSS APPLY * not * obtiene un error de columna no válido en esta consulta?

8

Estoy escribiendo un código para consultar algunos DMV. Algunas de las columnas pueden o no existir en el DMV dependiendo de la versión de SQL. Encontré una sugerencia interesante en línea sobre cómo omitir comprobaciones específicas usando CROSS APPLY.

La consulta a continuación es un ejemplo de código para leer un DMV para una columna potencialmente faltante. El código crea un valor predeterminado para la columna y lo utiliza CROSS APPLYpara extraer la columna real, si existe, del DMV.

La columna que el código intenta extraer, BogusColumn, no existe. Esperaría que la consulta a continuación genere un error sobre un nombre de columna no válido ... pero no lo hace. Devuelve NULL sin error.

¿Por qué la cláusula CROSS APPLY a continuación NO da como resultado un error de "nombre de columna no válido"?

declare @x int
select @x = b.BogusColumn
from
(
    select cast(null as int) as BogusColumn
) a
cross apply
(
    select BogusColumn from sys.dm_exec_sessions
) b;
select @x;

Si ejecuto la consulta por CROSS APPLYseparado:

select BogusColumn from sys.dm_exec_sessions;

Recibo un error esperado sobre un nombre de columna no válido:

Msg 207, Level 16, State 1, Line 9
Invalid column name 'BogusColumn'.

Si cambio el nombre de la columna DMV a BogusColumn2 para que sea único, obtengo el error de nombre de columna esperado:

select a.BogusColumn1, b.BogusColumn2
from
(
    select cast(null as int) as BogusColumn1
) a
cross apply
(
    select BogusColumn2 from sys.dm_exec_sessions
) b

He probado este comportamiento en las versiones de SQL 2012 hasta SQL 2017, y el comportamiento es consistente en todas las versiones.

Paul Williams
fuente
55
Aunque este comportamiento es predecible, también es un truco extremadamente inestable. Quien se le ocurra merece ambas felicitaciones y una palmada en la muñeca por introducir una trampa de mantenimiento como esta. Es justo sobre aceptable para cubrir las diferencias de versión en las vistas del sistema, y casi nada más.
Jeroen Mostert
3
Estoy de acuerdo con @JeroenMostert. Para evitar errores causados ​​por cambios sorprendentes en la resolución de la columna, SIEMPRE use alias de tabla desde la columna. Vi la producción baja porque alguien agregó una nueva columna a una tabla causando un efecto similar.
Piotr
1
Brillante pregunta! Y felicitaciones a @Piotr por la mención de alias de columna. Uso mucho APLICAR, a menudo anidado y sin alias, las cosas pueden volverse bastante confusas rápidamente.
Alan Burstein el
Estoy de acuerdo en que esto es a la vez un truco inteligente y feo. No quisiera usar esto en el código de producción, pero sí quiero usarlo para evitar los problemas de versiones en los DMV. Las consultas de tipo DBA para analizar la actividad del servidor son mucho más simples con este método en lugar de que todas las versiones verifiquen que de lo contrario tendría que hacer. IF @MajorVersion >= @SQL2016 AND @MinorVersion >= @SQL2016SP1 BEGIN /* write and execute dynamic SQL, etc. */ END
Paul Williams el

Respuestas:

7

BogusColumn se define como una columna válida en la primera consulta.

Cuando aplicamos la aplicación cruzada, se utiliza la resolución de columna de la siguiente manera:
1. Busca la columna 'BogusColumn' en la segunda consulta (dmv)
2. Si la columna existe en el dmv, se resolverá en el dmv
3 Si la columna no existe en el dmv, buscará esta columna en la consulta externa (la superior) y usará el valor proporcionado allí.

En otras palabras, cuando la columna falsa no está definida en la vista, la consulta final funcionará como:

select * from
(
    select cast(null as int) as BogusColumn
) a
cross apply
(
    select a.BogusColumn AS BogusColumn from sys.dm_exec_sessions
) b;

Si está definido, la consulta se resolverá en:

select * from
(
    select cast(null as int) as BogusColumn
) a
cross apply
(
    select s.BogusColumn AS BogusColumn from sys.dm_exec_sessions as s
) b;
Piotr
fuente