Por que CROSS APPLY * not * obtém um erro de coluna inválido nesta consulta?

8

Estou escrevendo algum código para consultar alguns DMVs. Algumas das colunas podem ou não existir na DMV, dependendo da versão do SQL. Encontrei uma sugestão interessante online de como ignorar a verificação específica usando CROSS APPLY.

A consulta abaixo é um exemplo de código para ler uma DMV para uma coluna potencialmente ausente. O código cria um valor padrão para a coluna e usa CROSS APPLYpara extrair a coluna real, se existir, da DMV.

A coluna que o código tenta extrair, BogusColumn, não existe. Eu esperaria que a consulta abaixo gere um erro sobre um nome de coluna inválido ... mas isso não ocorre. Retorna NULL sem erro.

Por que a cláusula CROSS APPLY abaixo NÃO resulta em um erro "nome inválido da coluna"?

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;

Se eu executar a consulta no CROSS APPLYseparadamente:

select BogusColumn from sys.dm_exec_sessions;

Recebo um erro esperado sobre um nome de coluna inválido:

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

Se eu alterar o nome da coluna DMV para BogusColumn2 para torná-lo exclusivo, recebo o erro esperado do nome da coluna:

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

Eu testei esse comportamento nas versões do SQL 2012 ao SQL 2017, e o comportamento é consistente em todas as versões.

Paul Williams
fonte
5
Embora esse comportamento seja previsível, também é um truque extremamente complicado. Quem inventou merece tanto elogios e um tapa no pulso por introduzir uma armadilha de manutenção como essa. É apenas sobre aceitável para cobrir diferenças de versão nas exibições do sistema, e apenas sobre para mais nada.
Jeroen Mostert
3
Eu concordo com @JeroenMostert. Para evitar erros causados ​​por alterações surpreendentes na resolução da coluna, use SEMPRE os aliases da tabela a partir da coluna. Vi a produção cair porque alguém adicionou uma nova coluna a uma tabela, causando efeito semelhante.
Piotr #
1
Pergunta brilhante! E parabéns ao @Piotr pela menção do alias da coluna. Eu uso muito o APPLY, geralmente aninhado e sem apelidos, as coisas podem ficar confusas rapidamente.
Alan Burstein
Concordo que isso é ao mesmo tempo inteligente e feio. Eu não gostaria de usar isso no código de produção, mas quero usá-lo para evitar os problemas de versão nas DMVs. As consultas do tipo DBA para analisar a atividade do servidor são muito mais simples com esse método, em vez de toda a verificação de versão que eu teria que fazer. IF @MajorVersion >= @SQL2016 AND @MinorVersion >= @SQL2016SP1 BEGIN /* write and execute dynamic SQL, etc. */ END
Paul Williams

Respostas:

7

BogusColumn é definido como uma coluna válida na 1ª consulta.

Quando aplicamos a aplicação cruzada, ela está usando a resolução da coluna da seguinte forma:
1. Procura a coluna 'BogusColumn' na 2ª consulta (dmv)
2. Se a coluna existir no dmv, ela será resolvida para o dmv
3 Se a coluna não existir no dmv, ela procurará essa coluna na consulta externa (a superior) e usará o valor fornecido lá.

Em outras palavras, quando a coluna falsa não estiver definida na exibição, a 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;

Se estiver definido, a consulta será resolvida para:

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
fonte