Bí danh tham chiếu (được tính bằng CHỌN) trong mệnh đề WHERE


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

Giá trị được tính toán 'Số dư' được đặt làm biến trong danh sách các cột được chọn có thể được sử dụng trong mệnh đề WHERE.

Có cách nào mà nó có thể? Trong câu hỏi liên quan này ( Sử dụng một biến trong MySQL Chọn Statment trong mệnh đề Where ), có vẻ như câu trả lời sẽ thực sự là không, bạn chỉ cần viết ra phép tính ( thực hiện phép tính đó trong truy vấn) hai lần, không đó là thỏa đáng.

Câu trả lời:


237

Bạn không thể tham chiếu một bí danh ngoại trừ trong ORDER BY vì SELECT là mệnh đề cuối cùng thứ hai được đánh giá. Hai cách giải quyết:

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

Hoặc chỉ cần lặp lại biểu thức:

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

Tôi thích cái sau. Nếu biểu thức cực kỳ phức tạp (hoặc tốn kém để tính toán), có lẽ bạn nên xem xét một cột được tính toán (và có lẽ vẫn tồn tại), đặc biệt nếu rất nhiều truy vấn đề cập đến cùng biểu thức này.

PS nỗi sợ hãi của bạn dường như vô căn cứ. Trong ví dụ đơn giản này, ít nhất, SQL Server đủ thông minh để chỉ thực hiện phép tính một lần, mặc dù bạn đã tham chiếu nó hai lần. Đi trước và so sánh các kế hoạch; bạn sẽ thấy chúng giống hệt nhau. Nếu bạn có một trường hợp phức tạp hơn khi bạn thấy biểu thức được đánh giá nhiều lần, vui lòng gửi truy vấn phức tạp hơn và các kế hoạch.

Dưới đây là 5 truy vấn mẫu mà tất cả đều mang lại cùng một kế hoạch thực hiện:

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;

Kế hoạch kết quả cho tất cả năm truy vấn:

nhập mô tả hình ảnh ở đây


11
Ồ SQL Server đủ thông minh để chỉ thực hiện phép tính một lần
Alternatefaraz

5
Wow đây là một câu trả lời chất lượng rất cao!
Siddhartha

Tôi cần một số điều kiện bổ sung trong một tuyên bố MERGE và đây là cách duy nhất tôi có thể làm cho nó hoạt động. Cảm ơn!
Eric Burdo

1
@EricBurdo Nếu bạn đang sử dụng MERGE, vui lòng đảm bảo rằng bạn đã tính đến tất cả những điều này: MERGEcẩn thận .
Aaron Bertrand

11

Bạn có thể làm điều này bằng cách sử dụng cross apply

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

4

Thật sự có thể định nghĩa một cách hiệu quả một biến có thể được sử dụng trong cả mệnh đề CHỌN, WHERE và các mệnh đề khác.

Một phép nối chéo không nhất thiết cho phép liên kết thích hợp với các cột trong bảng được tham chiếu, tuy nhiên OUTER ỨNG DỤNG thì có - và xử lý null một cách minh bạch hơn.

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 đến Syed Mehroz Alam .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.