Alias ​​referensi (dihitung dalam SELECT) dalam klausa WHERE

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

Nilai terhitung 'BalanceDue' yang ditetapkan sebagai variabel dalam daftar kolom yang dipilih tidak dapat digunakan dalam klausa WHERE.

Apakah ada cara yang bisa dilakukan? Dalam pertanyaan terkait ini ( Menggunakan variabel dalam MySQL Select Statment di a Where Clause ), sepertinya jawabannya adalah, sebenarnya, tidak, Anda hanya akan menuliskan perhitungan ( dan melakukan perhitungan dalam kueri) dua kali, tidak ada yang memuaskan.

Nicholas Petersen
sumber

Jawaban:

237

Anda tidak dapat mereferensikan alias kecuali dalam ORDER BY karena SELECT adalah klausa terakhir kedua yang dievaluasi. Dua solusi:

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

Atau ulangi saja ungkapan:

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

Saya lebih suka yang terakhir. Jika ekspresi sangat rumit (atau mahal untuk dihitung), Anda mungkin harus mempertimbangkan kolom yang dihitung (dan mungkin bertahan) sebagai gantinya, terutama jika banyak pertanyaan merujuk ke ekspresi yang sama ini.

PS Ketakutan Anda tampaknya tidak berdasar. Setidaknya dalam contoh sederhana ini, SQL Server cukup pintar untuk hanya melakukan perhitungan satu kali, meskipun Anda telah merujuknya dua kali. Silakan membandingkan rencana; Anda akan melihat mereka identik. Jika Anda memiliki kasus yang lebih kompleks di mana Anda melihat ekspresi dievaluasi beberapa kali, silakan kirim kueri yang lebih kompleks dan paket.

Berikut adalah 5 contoh kueri yang semuanya menghasilkan rencana eksekusi yang sama persis:

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;

Rencana hasil untuk semua lima pertanyaan:

masukkan deskripsi gambar di sini

Aaron Bertrand
sumber
11
Wow. SQL Server cukup pintar untuk hanya melakukan perhitungan sekali
alternatefaraz
5
Wow ini jawaban berkualitas sangat tinggi!
Siddhartha
Saya membutuhkan beberapa persyaratan tambahan dalam pernyataan MERGE, dan ini adalah satu-satunya cara saya dapat membuatnya bekerja. Terima kasih!
Eric Burdo
1
@EricBurdo Jika Anda menggunakan MERGE, pastikan Anda telah memperhitungkan semua ini: MERGEdengan hati-hati .
Aaron Bertrand
11

Anda dapat melakukan ini menggunakan cross apply

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

Sebenarnya mungkin untuk secara efektif mendefinisikan variabel yang dapat digunakan dalam klausa SELECT, WHERE dan lainnya.

Penggabungan silang tidak selalu memungkinkan pengikatan yang sesuai dengan kolom tabel yang direferensikan, namun OUTER BERLAKU melakukannya - dan memperlakukan null secara lebih transparan.

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 to Syed Mehroz Alam .

Peter Aylett
sumber