SQL Server có đọc tất cả hàm COALESCE ngay cả khi đối số đầu tiên không phải là NULL không?


98

Tôi đang sử dụng hàm T-SQL COALESCEtrong đó đối số đầu tiên sẽ không có giá trị trong khoảng 95% số lần nó được chạy. Nếu đối số thứ nhất là NULL, đối số thứ hai là một quá trình dài:

SELECT COALESCE(c.FirstName
                ,(SELECT TOP 1 b.FirstName
                  FROM TableA a 
                  JOIN TableB b ON .....)
                )

Ví dụ, c.FirstName = 'John'nếu SQL Server vẫn chạy truy vấn phụ?

Tôi biết với IIF()hàm VB.NET , nếu đối số thứ hai là True, mã vẫn đọc đối số thứ ba (mặc dù nó sẽ không được sử dụng).

Câu trả lời:


95

Không . Đây là một thử nghiệm đơn giản:

SELECT COALESCE(1, (SELECT 1/0)) -- runs fine
SELECT COALESCE(NULL, (SELECT 1/0)) -- throws error

Nếu điều kiện thứ hai được ước tính, một ngoại lệ được đưa ra để chia cho 0.

Theo Tài liệu MSDN, điều này có liên quan đến cách COALESCEngười phiên dịch xem - đó chỉ là một cách dễ dàng để viết một CASEtuyên bố.

CASE được biết đến là một trong những chức năng duy nhất trong SQL Server (hầu hết) đáng tin cậy là ngắn mạch.

Có một số trường hợp ngoại lệ khi so sánh với các biến vô hướng và tập hợp như được thể hiện bởi Aaron Bertrand trong một câu trả lời khác ở đây (và điều này sẽ áp dụng cả cho CASECOALESCE):

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

sẽ tạo ra một phân chia bởi lỗi không.

Điều này nên được coi là một lỗi, và như một quy tắc COALESCEsẽ phân tích từ trái sang phải.


6
@JNK vui lòng xem câu trả lời của tôi để xem một trường hợp rất đơn giản trong đó điều này không đúng (mối quan tâm của tôi là thậm chí còn có nhiều kịch bản chưa được phát hiện - làm cho khó có thể đồng ý rằng CASEluôn đánh giá từ trái sang phải và luôn ngắn mạch ).
Aaron Bertrand

4
Hành vi thú vị khác @QueryKiwi chỉ cho tôi: SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 1 END), 1);- lặp lại nhiều lần. NULLĐôi khi bạn sẽ nhận được . Hãy thử lại với ISNULL- bạn sẽ không bao giờ có được NULL...
Aaron Bertrand


@Martin vâng tôi tin như vậy. Nhưng không phải hành vi mà hầu hết người dùng sẽ thấy trực quan trừ khi họ đã nghe nói về (hoặc bị cắn bởi) vấn đề đó.
Aaron Bertrand

73

Làm thế nào về điều này - như được báo cáo với tôi bởi Itzik Ben-Gan, người đã được Jaime Lafargue nói về nó ?

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

Kết quả:

Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.

Tất nhiên có những cách giải quyết tầm thường, nhưng vấn đề vẫn là CASEkhông phải lúc nào cũng đảm bảo đánh giá từ trái sang phải / ngắn mạch. Tôi đã báo cáo lỗi ở đây và nó đã bị đóng là "theo thiết kế." Paul White sau đó đã nộp mục Kết nối này và nó đã bị đóng là Đã sửa. Không phải vì nó đã được sửa mỗi lần, mà bởi vì họ đã cập nhật Sách trực tuyến với một mô tả chính xác hơn về kịch bản mà các tổng hợp có thể thay đổi thứ tự đánh giá của một CASEbiểu thức. Gần đây tôi đã viết blog nhiều hơn về điều này ở đây .

EDIT chỉ là một phụ lục, trong khi tôi đồng ý rằng đây là những trường hợp cạnh, hầu hết thời gian bạn có thể dựa vào đánh giá từ trái sang phải và ngắn mạch, và đây là những lỗi mâu thuẫn với tài liệu và cuối cùng có thể sẽ được sửa chữa ( điều này không chắc chắn - hãy xem cuộc trò chuyện tiếp theo trên bài đăng trên blog của Bart Duncan để biết lý do tại sao), tôi phải không đồng ý khi mọi người nói rằng điều gì đó luôn luôn đúng ngay cả khi có một trường hợp duy nhất từ ​​chối nó. Nếu Itzik và những người khác có thể tìm thấy những con bọ đơn độc như thế này, thì ít nhất nó cũng có khả năng có những con bọ khác. Và vì chúng tôi không biết phần còn lại của truy vấn của OP, chúng tôi không thể nói chắc chắn rằng anh ấy sẽ dựa vào sự ngắn mạch này nhưng cuối cùng lại bị nó cắn. Vì vậy, với tôi, câu trả lời an toàn hơn là:

Mặc dù bạn thường có thể dựa vào CASEđể đánh giá từ trái sang phải và ngắn mạch, như được mô tả trong tài liệu, không chính xác để nói rằng bạn luôn có thể làm như vậy. Có hai trường hợp được chứng minh trên trang này là không đúng sự thật và không có lỗi nào được sửa trong bất kỳ phiên bản SQL Server công khai nào.

EDIT ở đây là một trường hợp khác (tôi cần dừng thực hiện điều đó) khi một CASEbiểu thức không đánh giá theo thứ tự bạn mong đợi, mặc dù không có tổng hợp nào được tham gia.


2
Và có vẻ như có một vấn đề khác CASE đã được khắc phục một cách lặng lẽ
Martin Smith

IMO điều này không chứng minh rằng đánh giá của biểu thức CASE không được đảm bảo vì các giá trị tổng hợp được tính toán trước khi chọn (để chúng có thể được sử dụng bên trong có).
Salman A

1
@SalmanA Tôi không chắc điều gì khác có thể xảy ra ngoại trừ chứng minh chính xác thứ tự đánh giá trong biểu thức CASE không được đảm bảo. Chúng tôi đang nhận được một ngoại lệ vì tổng hợp được tính toán trước, mặc dù đó là trong mệnh đề ELSE - nếu bạn đi theo tài liệu - sẽ không bao giờ đạt được.
Aaron Bertrand

Tổng hợp @AaronBertrand được tính trước câu lệnh CASE (và họ nên IMO). Các tài liệu sửa đổi chỉ ra chính xác điều này, rằng lỗi xảy ra trước khi CASE được đánh giá.
Salman A

@SalmanA Nó vẫn chứng minh cho nhà phát triển thông thường rằng biểu thức CASE không đánh giá theo thứ tự được viết - cơ học cơ bản là không liên quan nếu tất cả những gì bạn đang cố gắng hiểu là tại sao một lỗi đến từ nhánh CASE không nên xảy ra ' t đã đạt được. Bạn có tranh luận chống lại tất cả các ví dụ khác trên trang này không?
Aaron Bertrand

37

Quan điểm của tôi về điều này là tài liệu cho thấy rõ ràng một cách hợp lý rằng ý định là CASE nên ngắn mạch. Như Aaron đề cập, đã có một vài trường hợp (ha!) Trong đó điều này đã được chứng minh là không phải lúc nào cũng đúng.

Cho đến nay, tất cả những lỗi này đã được thừa nhận là lỗi và đã được sửa - mặc dù không nhất thiết phải là phiên bản SQL Server mà bạn có thể mua và sửa lỗi ngay hôm nay (lỗi không thể gập lại chưa được cập nhật vào AFAIK Cập nhật tích lũy). Lỗi tiềm năng mới nhất - được báo cáo ban đầu bởi Itzik Ben-Gan - vẫn chưa được điều tra (Aaron hoặc tôi sẽ thêm nó vào Connect ngay).

Liên quan đến câu hỏi ban đầu, có các vấn đề khác với CASE (và do đó COALESCE) trong đó các hàm tác dụng phụ hoặc truy vấn phụ được sử dụng. Xem xét:

SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 999 END), 999);
SELECT ISNULL((SELECT CASE WHEN RAND() <= 0.5 THEN 999 END), 999);

Biểu mẫu COALESCE thường trả về NULL, chi tiết hơn tại https://connect.microsoft.com/SQLServer/feedback/details/546437/coalesce-subquery-1-may-return-null

Các vấn đề đã được chứng minh với các biến đổi tối ưu hóa và theo dõi biểu thức chung có nghĩa là không thể đảm bảo rằng CASE sẽ ngắn mạch trong mọi trường hợp. Tôi có thể hình dung các trường hợp thậm chí không thể dự đoán hành vi bằng cách kiểm tra đầu ra của chương trình chương trình công cộng, mặc dù hôm nay tôi không có ý kiến ​​phản bác.

Tóm lại, tôi nghĩ bạn có thể tin tưởng một cách hợp lý rằng CASE nói chung sẽ bị đoản mạch (đặc biệt nếu một người có kỹ năng hợp lý kiểm tra kế hoạch thực hiện và kế hoạch thực hiện đó được 'thi hành' với hướng dẫn hoặc gợi ý về kế hoạch) nhưng nếu bạn cần một sự đảm bảo tuyệt đối, bạn phải viết SQL hoàn toàn không bao gồm biểu thức.

Không phải là một tình trạng cực kỳ thỏa đáng, tôi đoán vậy.


18

Tôi đã gặp một trường hợp khác trong đó CASE/ COALESCEkhông ngắn mạch. TVF sau đây sẽ vi phạm PK nếu được thông qua 1dưới dạng tham số.

CREATE FUNCTION F (@P INT)
RETURNS @T TABLE (
  C INT PRIMARY KEY)
AS
  BEGIN
      INSERT INTO @T
      VALUES      (1),
                  (@P)

      RETURN
  END

Nếu được gọi như sau

DECLARE @Number INT = 1

SELECT COALESCE(@Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = @Number), 
                         (SELECT TOP (1)  C
                          FROM   F(@Number))) 

Hoặc như

DECLARE @Number INT = 1

SELECT CASE
         WHEN @Number = 1 THEN @Number
         ELSE (SELECT TOP (1) C
               FROM   F(@Number))
       END 

Cả hai đều cho kết quả

Vi phạm các ràng buộc CHÍNH CHÍNH 'PK__F__3BD019A800551192'. Không thể chèn khóa trùng lặp vào đối tượng 'dbo. @ T'. Giá trị khóa trùng lặp là (1).

cho thấy rằng SELECT(hoặc ít nhất là dân số biến bảng) vẫn được thực hiện và gây ra lỗi ngay cả khi nhánh đó của câu lệnh không bao giờ đạt được. Kế hoạch cho COALESCEphiên bản dưới đây.

Kế hoạch

Viết lại truy vấn này xuất hiện để tránh vấn đề

SELECT COALESCE(Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = Number), 
                         (SELECT TOP (1)  C
                          FROM   F(Number))) 
FROM (VALUES(1)) V(Number)   

Mà đưa ra kế hoạch

Kế hoạch 2


8

Một vi dụ khac

CREATE TABLE T1 (C INT PRIMARY KEY)

CREATE TABLE T2 (C INT PRIMARY KEY)

INSERT INTO T1 
OUTPUT inserted.* INTO T2
VALUES (1),(2),(3);

Truy vấn

SET STATISTICS IO ON;

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (LOOP JOIN)

Cho thấy không có đọc chống lại T2tất cả.

Việc tìm kiếm T2nằm dưới một vị ngữ đi qua và toán tử không bao giờ được thực thi. Nhưng

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (MERGE JOIN)

cho thấy rằng T2được đọc. Mặc dù không có giá trị từ T2bao giờ thực sự cần thiết.

Tất nhiên điều này không thực sự đáng ngạc nhiên nhưng tôi nghĩ rằng đáng để thêm vào kho ví dụ truy cập nếu chỉ vì nó đặt ra vấn đề ngắn mạch thậm chí có nghĩa là gì trong một ngôn ngữ khai báo dựa trên tập hợp.


7

Tôi chỉ muốn đề cập đến một chiến lược mà bạn có thể chưa xem xét. Nó có thể không phải là một trận đấu ở đây, nhưng đôi khi nó có ích. Xem nếu sửa đổi này cung cấp cho bạn bất kỳ hiệu suất tốt hơn:

SELECT COALESCE(c.FirstName
            ,(SELECT TOP 1 b.FirstName
              FROM TableA a 
              JOIN TableB b ON .....
              WHERE C.FirstName IS NULL) -- this is the changed part
            )

Một cách khác để làm điều đó có thể là điều này (về cơ bản tương đương, nhưng cho phép bạn truy cập nhiều cột hơn từ truy vấn khác nếu cần thiết):

SELECT COALESCE(c.FirstName, x.FirstName)
FROM
   TableC c
   OUTER APPLY (
      SELECT TOP 1 b.FirstName
      FROM
         TableA a 
         JOIN TableB b ON ...
      WHERE
         c.FirstName IS NULL -- the important part
   ) x

Về cơ bản, đây là một kỹ thuật của các bảng tham gia "cứng" nhưng bao gồm cả điều kiện khi nào có bất kỳ hàng nào được THAM GIA. Theo kinh nghiệm của tôi, điều này thực sự đã giúp các kế hoạch thực hiện đôi khi.


3

Không, nó sẽ không. Nó sẽ chỉ chạy khi c.FirstNameđược NULL.

Tuy nhiên, bạn nên tự mình thử. Thí nghiệm. Bạn nói truy vấn của bạn là dài. Điểm chuẩn. Rút ra kết luận của riêng bạn về điều này.

Câu trả lời @Aaron trên truy vấn phụ đang chạy hoàn chỉnh hơn.

Tuy nhiên, tôi vẫn nghĩ bạn nên làm lại truy vấn của mình và sử dụng LEFT JOIN. Hầu hết thời gian, các truy vấn phụ có thể được loại bỏ bằng cách làm lại truy vấn của bạn để sử dụng LEFT JOINs.

Vấn đề với việc sử dụng các truy vấn phụ là câu lệnh tổng thể của bạn sẽ chạy chậm hơn vì truy vấn phụ được chạy cho mỗi hàng trong tập kết quả của truy vấn chính.


@Adrian nó vẫn chưa đúng. Nhìn vào kế hoạch thực hiện và bạn sẽ thấy rằng các truy vấn con thường được chuyển đổi khá thông minh thành THAM GIA. Đó chỉ là một lỗi thử nghiệm suy nghĩ khi cho rằng toàn bộ truy vấn con phải được chạy đi chạy lại cho mỗi hàng, mặc dù điều này có thể xảy ra một cách hiệu quả nếu chọn một vòng lặp lồng nhau với quét.
ErikE

3

Tiêu chuẩn thực tế nói rằng tất cả các mệnh đề WHEN (cũng như mệnh đề ELSE) phải được phân tích cú pháp để xác định toàn bộ kiểu dữ liệu của biểu thức. Tôi thực sự phải lấy ra một số ghi chú cũ của mình để xác định cách xử lý lỗi. Nhưng chỉ cần thực hiện, 1/0 sử dụng số nguyên, vì vậy tôi sẽ cho rằng trong khi đó là một lỗi. Đó là một lỗi với kiểu dữ liệu số nguyên. Khi bạn chỉ có null trong danh sách kết hợp, việc xác định loại dữ liệu sẽ khó hơn một chút và đó là một vấn đề khác.

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.