Alias ​​odniesienia (obliczany w SELECT) w klauzuli WHERE

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

Obliczona wartość BalanceDueustawiona jako zmienna na liście wybranych kolumn nie może być używana w WHEREklauzuli.

Czy jest na to sposób? W tym pokrewnym pytaniu ( przy użyciu zmiennej w instrukcji MySQL Select w klauzuli Where ) wydaje się, że odpowiedź brzmiałaby w rzeczywistości nie, po prostu wypiszesz obliczenie ( i wykonasz je w zapytaniu) dwa razy, żadna z co jest zadowalające.

Nicholas Petersen
źródło

Odpowiedzi:

248

Nie możesz odwoływać się do aliasu, z wyjątkiem ORDER BY, ponieważ SELECT jest przedostatnią klauzulą, która jest oceniana. Dwa obejścia:

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

Lub po prostu powtórz wyrażenie:

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

Wolę to drugie. Jeśli wyrażenie jest niezwykle złożone (lub kosztowne w obliczeniu), prawdopodobnie powinieneś rozważyć zamiast tego kolumnę obliczaną (i być może utrwaloną), zwłaszcza jeśli wiele zapytań odnosi się do tego samego wyrażenia.

PS Twoje obawy wydają się bezpodstawne. Przynajmniej w tym prostym przykładzie SQL Server jest na tyle inteligentny, że wykonuje obliczenia tylko raz, nawet jeśli odwołałeś się do niego dwukrotnie. Śmiało i porównaj plany; zobaczysz, że są identyczne. Jeśli masz bardziej złożony przypadek, w którym wyrażenie zostało ocenione wielokrotnie, opublikuj bardziej złożone zapytanie i plany.

Oto 5 przykładowych zapytań, które dają dokładnie ten sam plan wykonania:

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;

Wynikowy plan dla wszystkich pięciu zapytań:

wprowadź opis obrazu tutaj

Aaron Bertrand
źródło
13
Łał. SQL Server jest na tyle sprytny, że wykonuje obliczenia tylko raz
alternatefaraz
7
Wow, to bardzo wysokiej jakości odpowiedź!
Siddhartha
Potrzebowałem dodatkowych warunków w oświadczeniu MERGE i był to jedyny sposób, aby to zadziałało. Dzięki!
Eric Burdo,
1
@EricBurdo Jeśli używasz MERGE, upewnij się, że wziąłeś to wszystko pod uwagę: MERGEz ostrożnością .
Aaron Bertrand,
@AaronBertrand - Czy pierwsze zapytanie jest podzapytaniem czy tabelą pochodną? Nie znam różnicy między nimi, nawet po szybkim googlowaniu.
MasterJoe
11

Możesz to zrobić za pomocą cross apply

SELECT c.BalanceDue AS BalanceDue
FROM Invoices
cross apply (select (InvoiceTotal - PaymentTotal - CreditTotal) as BalanceDue) as c
WHERE  c.BalanceDue  > 0;
Manoj
źródło
4

W rzeczywistości możliwe jest efektywne zdefiniowanie zmiennej, która może być używana zarówno w klauzulach SELECT, WHERE, jak i innych.

Sprzężenie krzyżowe niekoniecznie pozwala na odpowiednie powiązanie z kolumnami tabeli, do których się odwołuje, jednak OUTER APPLY tak - i traktuje wartości null w bardziej przejrzysty sposób.

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

Kudos dla Syed Mehroz Alam .

Peter Aylett
źródło