Giá trị NULL bên trong mệnh đề NOT IN


244

Vấn đề này xuất hiện khi tôi nhận được các bản ghi khác nhau cho những gì tôi nghĩ là các truy vấn giống hệt nhau bằng cách sử dụng một not in whereràng buộc và khác left join. Bảng trong not inràng buộc có một giá trị null (dữ liệu xấu) khiến truy vấn đó trả về số lượng 0 bản ghi. Tôi có thể hiểu tại sao nhưng tôi có thể sử dụng một số trợ giúp để nắm bắt hoàn toàn khái niệm này.

Nói một cách đơn giản, tại sao truy vấn A trả về kết quả nhưng B thì không?

A: select 'true' where 3 in (1, 2, 3, null)
B: select 'true' where 3 not in (1, 2, null)

Đây là trên SQL Server 2005. Tôi cũng thấy rằng việc gọi set ansi_nulls offB khiến kết quả trả về.

Câu trả lời:


283

Truy vấn A giống như:

select 'true' where 3 = 1 or 3 = 2 or 3 = 3 or 3 = null

3 = 3là đúng, bạn nhận được một kết quả.

Truy vấn B giống như:

select 'true' where 3 <> 1 and 3 <> 2 and 3 <> null

Khi nào ansi_nullsbật, 3 <> nulllà UNKNOWN, vì vậy vị từ ước tính thành UNKNOWN và bạn không nhận được bất kỳ hàng nào.

Khi ansi_nullstắt, 3 <> nulllà đúng, vì vậy vị từ ước tính là đúng và bạn nhận được một hàng.


11
Có ai từng chỉ ra rằng việc chuyển đổi NOT INsang một loạt các <> andthay đổi hành vi ngữ nghĩa không có trong bộ này sang thứ khác không?
Ian Boyd

8
@Ian - Có vẻ như "A KHÔNG IN ('X', 'Y')" thực sự là bí danh cho A <> 'X' VÀ A <> 'Y' trong SQL. (Tôi thấy rằng bạn đã tự mình khám phá điều này trong stackoverflow.com/questions/3924694/ , nhưng muốn đảm bảo rằng sự phản đối của bạn đã được giải quyết trong câu hỏi này.)
Ryan Olson

Tôi đoán điều này giải thích tại sao SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);mang lại một hàng thay vì kết quả trống mà tôi mong đợi.
b Liệu

2
Đây là một hành vi rất kém của máy chủ SQL, bởi vì nếu nó mong đợi so sánh NULL bằng cách sử dụng "IS NULL", thì nó nên mở rộng mệnh đề IN cho cùng hành vi đó và không ngu ngốc áp dụng ngữ nghĩa sai cho chính nó.
OzrenTkalcecKrznaric

@binki, Bạn truy vấn thực thi nếu chạy ở đây rextester.com/l/sql_server_online_compiler nhưng không hoạt động nếu chạy ở đây sqlcference.com/cgi-bin/interpreter.cgi .
Istiaque Ahmed

52

Bất cứ khi nào bạn sử dụng NULL, bạn thực sự đang xử lý logic Ba giá trị.

Truy vấn đầu tiên của bạn trả về kết quả khi mệnh đề WHERE ước tính:

    3 = 1 or 3 = 2 or 3 = 3 or 3 = null
which is:
    FALSE or FALSE or TRUE or UNKNOWN
which evaluates to 
    TRUE

Cái thứ hai:

    3 <> 1 and 3 <> 2 and 3 <> null
which evaluates to:
    TRUE and TRUE and UNKNOWN
which evaluates to:
    UNKNOWN

UNKNOWN không giống như FALSE, bạn có thể dễ dàng kiểm tra nó bằng cách gọi:

select 'true' where 3 <> null
select 'true' where not (3 <> null)

Cả hai truy vấn sẽ không cho bạn kết quả

Nếu UNKNOWN giống với FALSE thì giả sử rằng truy vấn đầu tiên sẽ cung cấp cho bạn FALSE, lần thứ hai sẽ phải đánh giá thành TRUE vì nó sẽ giống như KHÔNG (FALSE).
Đó không phải là tình huống.

Có một bài viết rất hay về chủ đề này trên SqlServerCentral .

Toàn bộ vấn đề về NULL và Logic ba giá trị ban đầu có thể hơi khó hiểu nhưng điều cần thiết là phải hiểu để viết các truy vấn chính xác trong TSQL

Một bài viết khác tôi muốn giới thiệu là Hàm tổng hợp SQL và NULL .


33

NOT IN trả về 0 bản ghi khi so sánh với giá trị không xác định

NULLlà một ẩn số, một NOT INtruy vấn có chứa một NULLhoặc NULLs trong danh sách các giá trị có thể sẽ luôn trả về 0các bản ghi vì không có cách nào để chắc chắn rằng NULLgiá trị đó không phải là giá trị đang được kiểm tra.


3
Đây là câu trả lời một cách ngắn gọn. Tôi thấy điều này dễ hiểu hơn ngay cả khi không có ví dụ nào.
Govind Rai

18

So sánh với null là không xác định, trừ khi bạn sử dụng IS NULL.

Vì vậy, khi so sánh 3 với NULL (truy vấn A), nó trả về không xác định.

Tức là CHỌN 'đúng' trong đó 3 trong (1,2, null) và CHỌN 'đúng' trong đó 3 không ở (1,2, null)

sẽ tạo ra kết quả tương tự, vì KHÔNG (HIỂU) vẫn chưa được xác định, nhưng không ĐÚNG


Điểm tuyệt vời. chọn 1 trong đó null trong (null) không trả về hàng (ansi).
crokusek

9

Tiêu đề của câu hỏi này tại thời điểm viết là

Các giá trị SQL KHÔNG IN và ràng buộc NULL

Từ văn bản của câu hỏi, có vẻ như vấn đề đã xảy ra trong SELECTtruy vấn DML của SQL , thay vì SQL DDL CONSTRAINT.

Tuy nhiên, đặc biệt được đưa ra từ ngữ của tiêu đề, tôi muốn chỉ ra rằng một số tuyên bố được đưa ra ở đây là những tuyên bố có khả năng gây hiểu lầm, những tuyên bố dọc theo (diễn giải)

Khi vị từ ước tính thành UNKNOWN, bạn không nhận được bất kỳ hàng nào.

Mặc dù đây là trường hợp của SQL DML, nhưng khi xem xét các ràng buộc thì hiệu ứng lại khác.

Hãy xem xét bảng rất đơn giản này với hai ràng buộc được lấy trực tiếp từ các vị từ trong câu hỏi (và được giải quyết trong một câu trả lời xuất sắc của @Brannon):

DECLARE @T TABLE 
(
 true CHAR(4) DEFAULT 'true' NOT NULL, 
 CHECK ( 3 IN (1, 2, 3, NULL )), 
 CHECK ( 3 NOT IN (1, 2, NULL ))
);

INSERT INTO @T VALUES ('true');

SELECT COUNT(*) AS tally FROM @T;

Theo câu trả lời của @ Brannon, ràng buộc đầu tiên (sử dụng IN) ước tính thành TRUE và ràng buộc thứ hai (sử dụng NOT IN) ước tính thành UNKNOWN. Tuy nhiên , chèn thành công! Do đó, trong trường hợp này không đúng khi nói, "bạn không nhận được bất kỳ hàng nào" vì thực sự chúng tôi đã có một hàng được chèn vào.

Hiệu ứng trên thực sự là hiệu quả chính xác liên quan đến Tiêu chuẩn SQL-92. So sánh và đối chiếu phần sau từ thông số SQL-92

Điều khoản 7.6

Kết quả của một bảng gồm các hàng T mà kết quả của điều kiện tìm kiếm là đúng.

4.10 Ràng buộc toàn vẹn

Ràng buộc kiểm tra bảng được thỏa mãn khi và chỉ khi điều kiện tìm kiếm được chỉ định không sai đối với bất kỳ hàng nào của bảng.

Nói cách khác:

Trong SQL DML, các hàng được xóa khỏi kết quả khi WHEREđánh giá thành UNKNOWN vì nó không thỏa mãn điều kiện "là đúng".

Trong SQL DDL (hạn chế ví dụ), hàng không bị xoá khỏi kết quả khi họ đánh giá để UNKNOWN vì nó không thoả mãn điều kiện "là không sai".

Mặc dù các hiệu ứng trong SQL DML và SQL DDL tương ứng có vẻ mâu thuẫn, nhưng có lý do thực tế để mang lại cho UNKNOWN kết quả 'lợi ích của sự nghi ngờ' bằng cách cho phép chúng thỏa mãn một ràng buộc (chính xác hơn là cho phép chúng không thỏa mãn ràng buộc) : không có hành vi này, mọi ràng buộc sẽ phải xử lý rõ ràng null và điều đó sẽ không thỏa mãn từ góc độ thiết kế ngôn ngữ (chưa kể, một nỗi đau đúng cho các lập trình viên!)

ps nếu bạn thấy khó khăn khi tuân theo logic như "không biết không thỏa mãn một ràng buộc" như tôi viết nó, thì hãy xem xét bạn có thể phân phối với tất cả điều này chỉ bằng cách tránh các cột rỗng trong SQL DDL và bất cứ điều gì trong SQL DML tạo ra null (ví dụ: nối ngoài)!


Tôi thực sự không nghĩ rằng còn gì để nói về chủ đề này. Hấp dẫn.
Jamie Ide

2
@Jamie Ide: Thật ra, tôi có một câu trả lời khác về chủ đề này: vì NOT IN (subquery)liên quan đến null có thể mang lại kết quả bất ngờ, nên tránh IN (subquery)hoàn toàn và luôn luôn sử dụng NOT EXISTS (subquery)(như tôi đã từng làm!) Vì dường như nó luôn xử lý nulls một cách chính xác. Tuy nhiên, có những trường hợp NOT IN (subquery)cho kết quả như mong đợi trong khi NOT EXISTS (subquery)lại cho kết quả bất ngờ! Tôi có thể đi xung quanh để viết nó lên nếu tôi có thể tìm thấy ghi chú của mình về chủ đề này (cần ghi chú vì nó không trực quan!) Kết luận là như vậy, mặc dù: tránh null!
onedaywhen

@encedaywhen Tôi bối rối bởi sự khẳng định của bạn rằng NULL sẽ cần phải được đặc biệt để có hành vi nhất quán (nhất quán nội bộ, không phù hợp với thông số kỹ thuật). Sẽ không đủ để thay đổi 4.10 để đọc "Ràng buộc kiểm tra bảng được thỏa mãn khi và chỉ khi điều kiện tìm kiếm được chỉ định là đúng"?
DylanYoung

@DylanYoung: Không, spec là worded cách mà vì một lý do rất quan trọng: đau khổ SQL từ ba lý giá trị, nơi mà những giá trị này TRUE, FALSEUNKNOWN. Tôi cho rằng 4.10 có thể đã đọc, "Ràng buộc kiểm tra bảng được thỏa mãn khi và chỉ khi điều kiện tìm kiếm được chỉ định là TRUE hoặc UNKNOWN cho mỗi hàng của bảng" - lưu ý thay đổi của tôi đến cuối câu - mà bạn đã bỏ qua - - từ "cho bất kỳ" đến "cho tất cả '. Tôi cảm thấy cần phải viết hoa các giá trị logic bởi vì ý nghĩa của' đúng 'và' sai 'trong ngôn ngữ tự nhiên chắc chắn phải đề cập đến logic hai giá trị cổ điển.
onedaywhen

1
Xem xét: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- ý định ở đây là bphải bằng ahoặc không có giá trị. Nếu một ràng buộc phải khiến TRUE được thỏa mãn thì chúng ta cần thay đổi ràng buộc để xử lý rõ ràng null, ví dụ CHECK( a = b OR b IS NULL ). Do đó, mọi ràng buộc sẽ cần phải có ...OR IS NULLlogic do người dùng thêm vào cho mỗi cột không liên quan: phức tạp hơn, nhiều lỗi hơn khi họ quên làm như vậy, v.v. Vì vậy, tôi nghĩ rằng ủy ban tiêu chuẩn SQL chỉ cố gắng thực dụng.
ngày

7

Trong A, 3 được kiểm tra sự bình đẳng đối với từng thành viên của tập hợp, năng suất (FALSE, FALSE, TRUE, UNKNOWN). Vì một trong các phần tử là TRUE, điều kiện là TRUE. (Cũng có thể một số ngắn mạch xảy ra ở đây, vì vậy nó thực sự dừng lại ngay khi chạm vào TRUE đầu tiên và không bao giờ đánh giá 3 = NULL.)

Trong B, tôi nghĩ rằng nó đang đánh giá điều kiện là KHÔNG (3 trong (1,2, null)). Kiểm tra 3 về sự bằng nhau so với năng suất đã đặt (FALSE, FALSE, UNKNOWN), được tổng hợp thành UNKNOWN. KHÔNG (UNKNOWN) mang lại UNKNOWN. Vì vậy, nhìn chung sự thật của tình trạng này vẫn chưa được biết, về cơ bản cuối cùng được coi là SAI.


7

Có thể kết luận từ các câu trả lời ở đây là NOT IN (subquery)không xử lý null một cách chính xác và nên tránh có lợi cho NOT EXISTS. Tuy nhiên, kết luận như vậy có thể là sớm. Trong kịch bản sau đây, được ghi có vào Chris Date (Thiết kế và lập trình cơ sở dữ liệu, Tập 2 số 9, tháng 9 năm 1989), đó là NOT INxử lý null chính xác và trả về kết quả chính xác, thay vìNOT EXISTS .

Xem xét một bảng spđể đại diện cho các nhà cung cấp ( sno) được biết là cung cấp các bộ phận ( pno) về số lượng ( qty). Bảng hiện đang giữ các giá trị sau:

      VALUES ('S1', 'P1', NULL), 
             ('S2', 'P1', 200),
             ('S3', 'P1', 1000)

Lưu ý rằng số lượng là không có nghĩa là có thể ghi lại thực tế nhà cung cấp được biết là cung cấp các bộ phận ngay cả khi không biết số lượng.

Nhiệm vụ là tìm các nhà cung cấp được biết là số bộ phận cung cấp 'P1' nhưng không phải với số lượng 1000.

Cách sử dụng sau đây NOT INđể xác định chính xác chỉ nhà cung cấp 'S2':

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND 1000 NOT IN (
                        SELECT spy.qty
                          FROM sp spy
                         WHERE spy.sno = spx.sno
                               AND spy.pno = 'P1'
                       );

Tuy nhiên, truy vấn bên dưới sử dụng cùng một cấu trúc chung nhưng với NOT EXISTSnhưng không chính xác bao gồm nhà cung cấp 'S1' trong kết quả (nghĩa là số lượng là null):

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND NOT EXISTS (
                       SELECT *
                         FROM sp spy
                        WHERE spy.sno = spx.sno
                              AND spy.pno = 'P1'
                              AND spy.qty = 1000
                      );

Vì vậy, NOT EXISTSkhông phải là viên đạn bạc nó có thể đã xuất hiện!

Tất nhiên, nguồn gốc của vấn đề là sự hiện diện của null, do đó, giải pháp 'thực tế' là loại bỏ những null đó.

Điều này có thể đạt được (trong số các thiết kế có thể khác) bằng cách sử dụng hai bảng:

  • sp nhà cung cấp được biết đến để cung cấp các bộ phận
  • spq nhà cung cấp được biết đến để cung cấp các bộ phận với số lượng được biết đến

lưu ý có lẽ nên có một ràng buộc khóa ngoài nơi spqtham chiếu sp.

Kết quả sau đó có thể thu được bằng cách sử dụng toán tử quan hệ 'trừ' (là EXCEPTtừ khóa trong SQL chuẩn), vd

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1' ), 
                       ( 'S2', 'P1' ),
                       ( 'S3', 'P1' ) )
              AS T ( sno, pno )
     ),
     spq AS 
     ( SELECT * 
         FROM ( VALUES ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT sno
  FROM spq
 WHERE pno = 'P1'
EXCEPT 
SELECT sno
  FROM spq
 WHERE pno = 'P1'
       AND qty = 1000;

1
Chúa ơi. Cảm ơn bạn đã thực sự viết nó lên .... điều này đã khiến tôi phát điên ..
Govind Rai

6

Null biểu thị và không có dữ liệu, đó là không rõ, không phải là giá trị dữ liệu. Mọi người từ nền tảng lập trình rất dễ nhầm lẫn điều này bởi vì trong các ngôn ngữ loại C khi sử dụng con trỏ null thực sự không có gì.

Do đó trong trường hợp đầu tiên 3 thực sự nằm trong tập hợp (1,2,3, null) nên trả về true

Trong lần thứ hai, tuy nhiên bạn có thể giảm nó xuống

chọn 'true' trong đó 3 không trong (null)

Vì vậy, không có gì được trả về vì trình phân tích cú pháp không biết gì về tập hợp mà bạn đang so sánh - đó không phải là tập rỗng mà là tập không xác định. Sử dụng (1, 2, null) không giúp ích gì vì bộ (1,2) rõ ràng là sai, nhưng sau đó, bạn đang và điều đó chống lại điều chưa biết, điều chưa biết.


6

NẾU bạn muốn lọc mà KHÔNG IN cho một truy vấn phụ, chỉ cần kiểm tra NULL không phải là null

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )

Tôi gặp vấn đề với truy vấn tham gia bên ngoài mà không trả về bất kỳ bản ghi nào trong các tình huống đặc biệt, Vì vậy, đã kiểm tra giải pháp này cho cả kịch bản bản ghi Null và tồn tại và nó hoạt động với tôi, Nếu một vấn đề khác xảy ra tôi sẽ đề cập ở đây, Cảm ơn rất nhiều.
QMaster

1

cái này dành cho Boy:

select party_code 
from abc as a
where party_code not in (select party_code 
                         from xyz 
                         where party_code = a.party_code);

cái này hoạt động bất kể cài đặt ansi


đối với câu hỏi ban đầu: B: chọn 'true' trong đó 3 không trong (1, 2, null) một cách để loại bỏ null phải được thực hiện, ví dụ: chọn 'true' trong đó 3 không trong (1, 2, isnull (null, 0) ) logic tổng thể là, nếu NULL là nguyên nhân, thì hãy tìm cách loại bỏ các giá trị NULL tại một số bước trong truy vấn.

chọn party_code từ abc làm nơi party_code không ở (chọn party_code từ xyz trong đó party_code không null) nhưng chúc may mắn nếu bạn quên trường cho phép null, thường là như vậy

1

SQL sử dụng logic ba giá trị cho các giá trị thật. Các INtruy vấn ra kết quả dự kiến:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1)
-- returns first row

Nhưng thêm một NOTkhông đảo ngược kết quả:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT col IN (NULL, 1)
-- returns zero rows

Điều này là do truy vấn trên tương đương với các điều sau đây:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT (col = NULL OR col = 1)

Đây là cách mệnh đề where được đánh giá:

| col | col = NULL (1) | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN (2)           | UNKNOWN (3)                 |

Thông báo rằng:

  1. Sự so sánh liên quan đến NULLsản lượngUNKNOWN
  2. Các ORbiểu hiện ở đâu không ai trong số các toán hạng là TRUEvà ít nhất một toán hạng là UNKNOWNsản lượng UNKNOWN( ref )
  3. Các NOTcủa UNKNOWNsản lượng UNKNOWN( ref )

Bạn có thể mở rộng ví dụ trên thành nhiều hơn hai giá trị (ví dụ NULL, 1 và 2) nhưng kết quả sẽ giống nhau: nếu một trong các giá trị NULLđó thì không có hàng nào khớp.


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.