Đảo ngược một biểu thức Boolean có thể trả về UNKNOWN


11

Thí dụ

Tôi có một cái bàn

ID  myField
------------
 1  someValue
 2  NULL
 3  someOtherValue

và một biểu thức Boolean T-SQL có thể đánh giá thành TRUE, FALSE hoặc (do logic tạm thời của SQL) UNKNOWN:

SELECT * FROM myTable WHERE myField = 'someValue'

-- yields record 1

Nếu tôi muốn nhận tất cả các bản ghi khác , tôi không thể đơn giản phủ nhận biểu thức

SELECT * FROM myTable WHERE NOT (myField = 'someValue')

-- yields only record 3

Tôi biết tại sao điều này xảy ra (logic ternary) và tôi biết cách giải quyết vấn đề cụ thể này.

Tôi biết tôi chỉ có thể sử dụng myField = 'someValue' AND NOT myField IS NULLvà tôi nhận được một biểu thức "không thể đảo ngược" mà không bao giờ mang lại UNKNOWN:

SELECT * FROM myTable WHERE NOT (myField = 'someValue' AND myField IS NOT NULL)

-- yields records 2 and 3, hooray!

Trường hợp chung

Bây giờ, hãy nói về trường hợp chung. Giả sử thay vì myField = 'someValue'tôi có một số biểu thức phức tạp liên quan đến nhiều trường và điều kiện, có thể là các truy vấn con:

SELECT * FROM myTable WHERE ...some complex Boolean expression...

Có một cách chung để "đảo ngược" sự hết hạn này? Điểm thưởng nếu nó hoạt động cho các biểu hiện phụ:

SELECT * FROM myTable 
 WHERE ...some expression which stays... 
   AND ...some expression which I might want to invert...

Tôi cần hỗ trợ SQL Server 2008-2014, nhưng nếu có một giải pháp tao nhã yêu cầu phiên bản mới hơn 2008, tôi cũng muốn nghe về nó.

Câu trả lời:


15

Bạn có thể kèm theo điều kiện trong biểu thức CASE trả về kết quả nhị phân, ví dụ 1 hoặc 0:

SELECT
  ...
FROM
  ...
WHERE
  CASE WHEN someColumn = someValue THEN 1 ELSE 0 END = 1
;

Việc phủ định biểu thức sẽ cung cấp cho bạn tất cả các hàng khác từ cùng một nguồn dữ liệu, bao gồm cả các hàng trong đó someColumn là null:

SELECT
  ...
FROM
  ...
WHERE
  NOT CASE WHEN someColumn = someValue THEN 1 ELSE 0 END = 1
  -- or: CASE WHEN someColumn = someValue THEN 1 ELSE 0 END <> 1
;

Kể từ SQL Server 2012, bạn cũng có chức năng IIF , đây chỉ là một trình bao bọc xung quanh một CASE nhị phân như trên. Vì vậy, biểu thức CASE này:

CASE WHEN someColumn = someValue THEN 1 ELSE 0 END

sẽ trông như thế này nếu được viết lại bằng IIF:

IIF(someColumn = someValue, 1, 0)

Và bạn có thể sử dụng nó chính xác giống như biểu thức CASE. Sẽ không có sự khác biệt về hiệu suất, chỉ có mã sẽ ngắn gọn hơn một chút, có thể cũng sạch hơn theo cách đó.


Đó là một ý tưởng tốt! Sử dụng CASE để "chuyển đổi" biểu thức Boolean thành biểu thức mà người ta có thể làm việc với, sau đó sử dụng phép so sánh để "chuyển đổi" nó trở lại thành biểu thức Boolean.
Heinzi

10

Ý nghĩ đầu tiên xảy ra với tôi:

DECLARE @T AS table (c1 integer NULL);

INSERT @T (c1)
VALUES (1), (NULL), (2);

-- Original expression c1 = 1
SELECT T.c1
FROM @T AS T
WHERE c1 = 1;

Trả về:

kết quả

-- Negated
SELECT T.c1
FROM @T AS T
WHERE NOT EXISTS (SELECT 1 WHERE c1 = 1);

Trả về:

Kết quả phủ định

Điều này phụ thuộc vào cách EXISTSluôn trả về đúng hay sai , không bao giờ được biết . Không SELECT 1 WHEREmay là cần thiết, nhưng nó có thể khả thi cho yêu cầu của bạn, ví dụ:

sql = "
    SELECT * 
    FROM someTable 
    WHERE " + someExpression + 
    " AND NOT EXISTS (SELECT 1 WHERE " + 
    someOtherExpression + ")";
result = executeAndShow(sql);

Xem EXISTS (Transact-SQL)


Một ví dụ cách hơi phức tạp hơn thể hiện như thế nào trong hai EXISTShoặc CASE/IIFphương pháp có thể được áp dụng để đảo ngược vị từ cá nhân:

DECLARE @T AS table 
(
    c1 integer NULL,
    c2 integer NULL,
    c3 integer NULL
);

INSERT @T 
    (c1, c2, c3)
VALUES 
    (1, NULL, 2),
    (2, 2, 3),
    (NULL, 1, 4);

Mã số:

-- Original
SELECT 
    T.c1,
    T.c2,
    T.c3
FROM @T AS T
WHERE
    1 = 1
    -- Predicate #1
    AND T.c1 = 2
    -- Predicate #2
    AND T.c2 =
    (
        SELECT MAX(T2.c2)
        FROM @T AS T2
        WHERE T2.c2 IS NOT NULL
    )
    -- Predicate #3
    AND T.c3 IN (3, 4)
    ;

-- Invert predicates #1 and #2
SELECT 
    T.c1,
    T.c2,
    T.c3
FROM @T AS T
WHERE
    1 = 1
    AND NOT EXISTS (SELECT 1 WHERE 1 = 1
        -- Predicate #1
        AND T.c1 = 2)
    AND NOT EXISTS (SELECT 1 WHERE 1 = 1
        -- Predicate #2
            AND T.c2 =
            (
                SELECT MAX(T2.c2)
                FROM @T AS T2
                WHERE T2.c2 IS NOT NULL
            ))
    -- Predicate #3
    AND T.c3 IN (3, 4)
    ;

3

Nếu bạn không nhớ viết lại các biểu thức phụ lên phía trước, bạn có thể sử dụng COALESCE:

SELECT *
FROM myTable
WHERE NOT (COALESCE(myField, 'notSomeValue') = 'someValue')

Bạn phải chắc chắn rằng 'notSomeValue'nó khác biệt với 'someValue'; tốt nhất, nó sẽ là một số giá trị hoàn toàn bất hợp pháp cho cột. (Tất nhiên, nó cũng không thể NULL.) Điều này rất dễ phủ nhận, ngay cả khi bạn có một danh sách dài:

SELECT *
FROM myTable
WHERE NOT (
    COALESCE(myField, 'notSomeValue') = 'someValue' AND
    COALESCE(myField2, 'notSomeValue') = 'someValue2' AND
    COALESCE(myField3, 'notSomeValue') = 'someValue3' AND
    COALESCE(myField4, 'notSomeValue') = 'someValue4'
)

Theo tôi, sạch hơn, đơn giản hơn và rõ ràng hơn CASEhoặc IIF. Nhược điểm chính là có giá trị thứ hai mà bạn biết là không bằng nhau, nhưng đây thực sự chỉ là vấn đề nếu bạn không biết giá trị thực tế ở phía trước. Trong trường hợp đó, bạn có thể làm như Hanno Binder gợi ý và sử dụng COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'(nơi 'someValue'thực sự sẽ được tham số hóa).

COALESCE được ghi lại từ SQL Server 2005 trở đi.

Xin lưu ý rằng việc gây rối với truy vấn của bạn như thế này (sử dụng bất kỳ phương pháp nào được đề xuất ở đây) có thể gây khó khăn hơn cho cơ sở dữ liệu để tối ưu hóa truy vấn của bạn. Đối với các bộ dữ liệu lớn, IS NULLphiên bản có khả năng dễ dàng tối ưu hóa hơn.


1
COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'nên hoạt động cho bất kỳ "someValue" nào và bất kỳ dữ liệu nào trong bảng.
JimmyB

2

Có toán tử tập hợp EXCEPT tích hợp, loại bỏ kết quả của truy vấn thứ hai khỏi truy vấn thứ nhất một cách hiệu quả.

select * from table
except
select * from table
where <really complex predicates>

Hãy hy vọng đó là một chiếc bàn nhỏ :-)
Lennart

-4

COALESCE có sẵn không?

SELECT * FROM myTable WHERE NOT COALESCE(myField = 'someValue', FALSE)

4
Có, COALESCE có sẵn, nhưng không, điều này sẽ không hoạt động: (a) COALESCE sẽ không chấp nhận biểu thức Boolean (bằng cách này, ISNULL cũng không) và (b) giá trị thật FALSE không có sẵn trực tiếp trong SQL như một nghĩa đen. Hãy thử nó và bạn sẽ nhận được một lỗi cú pháp.
Heinzi

@Heinzi - Tôi đã thử nó, nó hoạt động, đó là lý do tại sao tôi đăng nó. Có thể nó không hoạt động trên T-SQL, nhưng nó ổn trên Postgres và MySQL.
Malvolio

2
@Malvolio: Câu hỏi được gắn thẻ sql-server, mặc dù, không mysqlhoặc postgresql.
Andriy M

@Malvolio đó là vì Postgres có một BOOLEANloại và MySQL có loại (giả) BOOLEANcó thể là tham số của COALESCE()hàm. Nếu câu hỏi đã được gắn thẻ sql-agnostichoặc sql-standard, câu trả lời sẽ ổn.
ypercubeᵀᴹ 22/03/2016

@ ypercubeᵀᴹ - eh, tôi có thể nói gì với bạn? Nhận một cơ sở dữ liệu tốt hơn.
Malvolio
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.