Referenzalias (berechnet in SELECT) in der WHERE-Klausel

130
SELECT (InvoiceTotal - PaymentTotal - CreditTotal) AS BalanceDue
FROM Invoices
WHERE BalanceDue > 0 --error

Der berechnete Wert 'BalanceDue', der in der Liste der ausgewählten Spalten als Variable festgelegt ist, kann in der WHERE-Klausel nicht verwendet werden.

Gibt es einen Weg, den es kann? In dieser verwandten Frage ( Verwenden einer Variablen in MySQL Select Statment in einer Where-Klausel ) scheint die Antwort tatsächlich Nein zu sein. Sie würden die Berechnung nur zweimal ausschreiben ( und diese Berechnung in der Abfrage durchführen) das ist zufriedenstellend.

Nicholas Petersen
quelle

Antworten:

237

Sie können nur in ORDER BY auf einen Alias ​​verweisen, da SELECT die vorletzte Klausel ist, die ausgewertet wird. Zwei Problemumgehungen:

SELECT BalanceDue FROM (
  SELECT (InvoiceTotal - PaymentTotal - CreditTotal) AS BalanceDue
  FROM Invoices
) AS x
WHERE BalanceDue > 0;

Oder wiederholen Sie einfach den Ausdruck:

SELECT (InvoiceTotal - PaymentTotal - CreditTotal) AS BalanceDue
FROM Invoices
WHERE  (InvoiceTotal - PaymentTotal - CreditTotal)  > 0;

Ich bevorzuge letzteres. Wenn der Ausdruck äußerst komplex ist (oder teuer zu berechnen ist), sollten Sie stattdessen wahrscheinlich eine berechnete Spalte in Betracht ziehen (und möglicherweise beibehalten werden), insbesondere wenn sich viele Abfragen auf denselben Ausdruck beziehen.

PS Ihre Befürchtungen scheinen unbegründet. Zumindest in diesem einfachen Beispiel ist SQL Server intelligent genug, um die Berechnung nur einmal durchzuführen, obwohl Sie zweimal darauf verwiesen haben. Gehen Sie voran und vergleichen Sie die Pläne; Sie werden sehen, dass sie identisch sind. Wenn Sie einen komplexeren Fall haben, in dem der Ausdruck mehrmals ausgewertet wird, senden Sie bitte die komplexere Abfrage und die Pläne.

Hier sind 5 Beispielabfragen, die alle genau den gleichen Ausführungsplan ergeben:

SELECT LEN(name) + column_id AS x
FROM sys.all_columns
WHERE LEN(name) + column_id > 30;

SELECT x FROM (
SELECT LEN(name) + column_id AS x
FROM sys.all_columns
) AS x
WHERE x > 30;

SELECT LEN(name) + column_id AS x
FROM sys.all_columns
WHERE column_id + LEN(name) > 30;

SELECT name, column_id, x FROM (
SELECT name, column_id, LEN(name) + column_id AS x
FROM sys.all_columns
) AS x
WHERE x > 30;

SELECT name, column_id, x FROM (
SELECT name, column_id, LEN(name) + column_id AS x
FROM sys.all_columns
) AS x
WHERE LEN(name) + column_id > 30;

Resultierender Plan für alle fünf Abfragen:

Geben Sie hier die Bildbeschreibung ein

Aaron Bertrand
quelle
11
Beeindruckend. SQL Server ist intelligent genug, um die Berechnung nur einmal durchzuführen
alternativefaraz
5
Wow das ist eine sehr hochwertige Antwort!
Siddhartha
Ich brauchte einige zusätzliche Bedingungen in einer MERGE-Anweisung, und nur so konnte ich sie zum Laufen bringen. Vielen Dank!
Eric Burdo
1
@EricBurdo Wenn Sie verwenden MERGE, stellen Sie bitte sicher, dass Sie all dies berücksichtigt haben: MERGEmit Vorsicht .
Aaron Bertrand
10

Sie können dies mit tun cross apply

SELECT c.BalanceDue AS BalanceDue
FROM Invoices
cross apply (select (InvoiceTotal - PaymentTotal - CreditTotal) as BalanceDue) as c
WHERE  c.BalanceDue  > 0;
Manoj
quelle
4

Es ist tatsächlich möglich, eine Variable effektiv zu definieren, die sowohl in den SELECT-, WHERE- als auch in anderen Klauseln verwendet werden kann.

Ein Cross-Join ermöglicht nicht unbedingt eine angemessene Bindung an die Spalten der referenzierten Tabelle, OUTER APPLY jedoch - und behandelt Nullen transparenter.

SELECT
    vars.BalanceDue
FROM
    Entity e
OUTER APPLY (
    SELECT
        -- variables   
        BalanceDue = e.EntityTypeId,
        Variable2 = ...some..long..complex..expression..etc...
    ) vars
WHERE
    vars.BalanceDue > 0

Ein großes Lob an Syed Mehroz Alam .

Peter Aylett
quelle