Alias ​​di riferimento (calcolato in SELECT) nella clausola WHERE


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

Il valore calcolato "BalanceDue" impostato come variabile nell'elenco delle colonne selezionate non può essere utilizzato nella clausola WHERE.

C'è un modo che può? In questa domanda correlata ( Utilizzo di una variabile in MySQL Select Statment in una clausola Where ), sembra che la risposta sarebbe, in realtà, no, dovresti semplicemente scrivere il calcolo ( ed eseguire quel calcolo nella query) due volte, nessuno di che è soddisfacente.

Risposte:


237

Non è possibile fare riferimento a un alias se non in ORDER BY perché SELECT è la seconda ultima clausola valutata. Due soluzioni alternative:

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

O semplicemente ripetere l'espressione:

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

Preferisco quest'ultima. Se l'espressione è estremamente complessa (o costosa da calcolare), dovresti probabilmente prendere in considerazione una colonna calcolata (e forse persistente), soprattutto se molte query fanno riferimento a questa stessa espressione.

PS le tue paure sembrano infondate. Almeno in questo semplice esempio, SQL Server è abbastanza intelligente da eseguire il calcolo una sola volta, anche se lo hai fatto riferimento due volte. Vai avanti e confronta i piani; vedrai che sono identici. Se hai un caso più complesso in cui vedi l'espressione valutata più volte, pubblica la query più complessa e i piani.

Ecco 5 query di esempio che generano esattamente lo stesso piano di esecuzione:

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;

Piano risultante per tutte e cinque le query:

inserisci qui la descrizione dell'immagine


11
Wow. SQL Server è abbastanza intelligente da eseguire il calcolo una sola volta
alternatefaraz

5
Wow questa è una risposta di altissima qualità!
Siddhartha,

Avevo bisogno di alcuni condizionali extra in una dichiarazione MERGE, e questo era l'unico modo per farlo funzionare. Grazie!
Eric Burdo,

1
@EricBurdo Se stai utilizzando MERGE, assicurati di aver preso in considerazione tutto questo: MERGEcon cautela .
Aaron Bertrand,

11

Puoi farlo usando cross apply

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

4

In realtà è possibile definire efficacemente una variabile che può essere utilizzata in entrambe le clausole SELECT, WHERE e altre.

Un cross join non consente necessariamente un'adeguata associazione alle colonne della tabella di riferimento, come fa OUTER APPLY, e tratta i valori null in modo più trasparente.

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

Complimenti a Syed Mehroz Alam .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.