Warum erhält CROSS APPLY * nicht * einen ungültigen Spaltenfehler in dieser Abfrage?

8

Ich schreibe Code, um einige DMVs abzufragen. Einige der Spalten können je nach SQL-Version in der DMV vorhanden sein oder nicht. Ich habe online einen interessanten Vorschlag gefunden, wie man bestimmte Überprüfungen mit überspringt CROSS APPLY.

Die folgende Abfrage ist ein Beispiel für Code zum Lesen einer DMV für eine möglicherweise fehlende Spalte. Der Code erstellt einen Standardwert für die Spalte und CROSS APPLYextrahiert die tatsächliche Spalte, falls vorhanden, aus der DMV.

Die Spalte, die der Code zu extrahieren versucht, BogusColumn, existiert nicht. Ich würde erwarten, dass die folgende Abfrage einen Fehler über einen ungültigen Spaltennamen generiert ... aber dies ist nicht der Fall. Es gibt fehlerfrei NULL zurück.

Warum führt die unten stehende CROSS APPLY-Klausel NICHT zu einem Fehler "Ungültiger Spaltenname"?

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;

Wenn ich die Abfrage CROSS APPLYseparat ausführe :

select BogusColumn from sys.dm_exec_sessions;

Ich erhalte einen erwarteten Fehler bezüglich eines ungültigen Spaltennamens:

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

Wenn ich den DMV-Spaltennamen in BogusColumn2 ändere, um ihn eindeutig zu machen, wird der erwartete Spaltennamenfehler angezeigt:

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

Ich habe dieses Verhalten in Versionen von SQL 2012 bis SQL 2017 getestet und das Verhalten ist in allen Versionen konsistent.

Paul Williams
quelle
5
Obwohl dieses Verhalten vorhersehbar ist, ist es auch ein extrem grody Hack. Wer auch immer darauf gekommen ist, verdient sowohl ein Lob als auch einen Schlag auf das Handgelenk für die Einführung einer solchen Wartungsfalle. Es ist fast akzeptabel, Versionsunterschiede in den Systemansichten abzudecken, und fast nichts anderes.
Jeroen Mostert
3
Ich stimme @JeroenMostert zu. Um Fehler zu vermeiden, die durch überraschende Änderungen der Spaltenauflösung verursacht werden, verwenden Sie IMMER Tabellenaliasnamen aus der Spalte. Ich habe gesehen, dass die Produktion zurückgegangen ist, weil jemand einer Tabelle eine neue Spalte hinzugefügt hat, die einen ähnlichen Effekt verursacht.
Piotr
1
Geniale Frage! Und ein großes Lob an @Piotr für die Erwähnung des Spalten-Aliasing. Ich benutze APPLY oft, oft verschachtelt und ohne Aliase können die Dinge schnell ziemlich verwirrend werden.
Alan Burstein
Ich bin damit einverstanden, dass dies sowohl klug als auch ein hässlicher Hack ist. Ich möchte dies nicht im Produktionscode verwenden, aber ich möchte es verwenden, um die Versionsprobleme in den DMVs zu vermeiden. Abfragen vom Typ DBA zur Analyse der Serveraktivität sind mit dieser Methode viel einfacher als mit der gesamten Versionsprüfung, die ich sonst durchführen müsste.IF @MajorVersion >= @SQL2016 AND @MinorVersion >= @SQL2016SP1 BEGIN /* write and execute dynamic SQL, etc. */ END
Paul Williams

Antworten:

7

BogusColumn wird in der 1. Abfrage als gültige Spalte definiert.

Wenn wir cross apply anwenden, wird die Spaltenauflösung wie folgt verwendet:
1. In der 2. Abfrage (dmv) wird nach der Spalte 'BogusColumn' gesucht.
2. Wenn die Spalte in der dmv vorhanden ist, wird sie in die dmv
3 aufgelöst Wenn die Spalte in der dmv nicht vorhanden ist, sucht sie in der äußeren Abfrage (der obersten) nach dieser Spalte und verwendet den dort angegebenen Wert.

Mit anderen Worten, wenn in der Ansicht keine falsche Spalte definiert ist, funktioniert die endgültige Abfrage wie folgt:

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

Wenn es definiert ist, wird die Abfrage wie folgt aufgelöst:

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
quelle