ย้อนกลับนิพจน์บูลีนซึ่งสามารถส่งคืน UNKNOWN


11

ตัวอย่าง

ฉันมีโต๊ะ

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

และนิพจน์ T-SQL Boolean ซึ่งสามารถประเมินค่าเป็น TRUE, FALSE หรือ (เนื่องจากตรรกะที่ประกอบไปด้วยของ SQL) UNKNOWN:

SELECT * FROM myTable WHERE myField = 'someValue'

-- yields record 1

หากฉันต้องการได้รับบันทึกอื่น ๆ ทั้งหมดฉันไม่สามารถปฏิเสธการแสดงออกได้

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

-- yields only record 3

ฉันรู้ว่าทำไมสิ่งนี้จึงเกิดขึ้น (ตรรกะที่สาม) และฉันรู้วิธีแก้ปัญหาเฉพาะนี้

ฉันรู้ว่าฉันสามารถใช้myField = 'someValue' AND NOT myField IS NULLและฉันได้รับการแสดงออก "ย้อนกลับ" ซึ่งไม่เคยยอมแพ้ UNKNOWN:

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

-- yields records 2 and 3, hooray!

กรณีทั่วไป

ทีนี้มาพูดถึงกรณีทั่วไปกัน สมมุติว่าmyField = 'someValue'ฉันมีนิพจน์ที่ซับซ้อนที่เกี่ยวข้องกับฟิลด์และเงื่อนไขจำนวนมากบางทีเคียวรีย่อย:

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

มีวิธีทั่วไปในการ "คว่ำ" การถดถอยนี้หรือไม่? คะแนนโบนัสหากใช้กับนิพจน์ย่อย:

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

ฉันต้องการสนับสนุน SQL Server 2008-2014 แต่ถ้ามีโซลูชันที่หรูหราที่ต้องใช้รุ่นที่ใหม่กว่าปี 2008 ฉันก็อยากจะได้ยินเช่นกัน

คำตอบ:


15

คุณสามารถใส่เงื่อนไขในนิพจน์ CASE ที่ส่งคืนผลลัพธ์ไบนารีตัวอย่างเช่น 1 หรือ 0:

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

การลบการแสดงออกจะให้แถวอื่น ๆ ทั้งหมดจากแหล่งข้อมูลเดียวกันรวมถึงแถวที่someColumnเป็นโมฆะ:

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
;

ตั้งแต่ SQL Server 2012 คุณยังมีฟังก์ชัน IIFซึ่งเป็นเพียง wrapper รอบ ๆ ไบนารี CASE เช่นด้านบน ดังนั้นการแสดงออกกรณีนี้:

CASE WHEN someColumn = someValue THEN 1 ELSE 0 END

จะมีลักษณะเช่นนี้หากเขียนใหม่โดยใช้ IIF:

IIF(someColumn = someValue, 1, 0)

และคุณสามารถใช้มันได้ในลักษณะเดียวกับ CASE นิพจน์ จะไม่มีความแตกต่างในประสิทธิภาพการทำงานเฉพาะรหัสจะกระชับขึ้นเล็กน้อยอาจทำความสะอาดด้วยวิธีนี้เช่นกัน


นั่นเป็นความคิดที่ดี! ใช้ CASE เพื่อ "แปลง" นิพจน์บูลีนเป็นนิพจน์ที่สามารถทำงานได้จากนั้นใช้การเปรียบเทียบเพื่อ "แปลง" นิพจน์บูลีนกลับไปเป็นนิพจน์บูลีน
Heinzi

10

ความคิดแรกที่เกิดขึ้นกับฉัน:

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;

ผลตอบแทน:

ผลลัพธ์

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

ผลตอบแทน:

ผลเมื่อตะกี้

นี้ต้องอาศัยวิธีการที่EXISTSเสมอกลับจริงหรือเท็จไม่เคยรู้จัก จำเป็นต้องSELECT 1 WHEREมีสิ่งที่น่าเสียดาย แต่อาจเป็นไปได้สำหรับความต้องการของคุณตัวอย่างเช่น:

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

ดูEXISTS (Transact-SQL)


ตัวอย่างที่ทำงานที่ซับซ้อนมากขึ้นเล็กน้อยแสดงวิธีการอย่างใดอย่างหนึ่งEXISTSหรือCASE/IIFวิธีการอาจจะนำไปใช้กับภาคกลับบุคคล:

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);

รหัส:

-- 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

หากคุณไม่รังเกียจที่จะเขียนนิพจน์ย่อยไว้ล่วงหน้าคุณสามารถใช้COALESCE:

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

คุณต้องทำให้แน่ใจว่า'notSomeValue'แตกต่างจาก'someValue'; โดยเฉพาะอย่างยิ่งมันจะเป็นค่าที่ผิดกฎหมายโดยสิ้นเชิงสำหรับคอลัมน์ (เป็นไปไม่ได้NULLแน่นอน) นี่เป็นเรื่องง่ายที่จะปฏิเสธแม้ว่าคุณจะมีรายชื่อยาว ๆ :

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

ในความคิดของฉันสะอาดกว่าง่ายกว่าและชัดเจนกว่าCASEหรือ IIFข้อเสียเปรียบหลักคือการมีค่าที่สองที่คุณรู้ว่าไม่เท่ากัน แต่นี่เป็นปัญหาจริงๆถ้าคุณไม่ทราบมูลค่าที่แท้จริงล่วงหน้า ในกรณีดังกล่าวคุณสามารถทำตามที่Hanno Binderแนะนำและใช้COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'(ซึ่ง'someValue'จะมีการกำหนดพารามิเตอร์)

COALESCE มีการจัดทำเอกสารให้พร้อมใช้งานจาก SQL Server 2005 เป็นต้นไป

โปรดทราบว่าการสับสนกับข้อความค้นหาของคุณเช่นนี้ (การใช้วิธีการใด ๆ ที่แนะนำที่นี่) อาจทำให้ฐานข้อมูลของคุณยากต่อการปรับให้เหมาะสมที่สุด สำหรับชุดข้อมูลขนาดใหญ่IS NULLเวอร์ชันนั้นน่าจะง่ายที่สุดในการปรับให้เหมาะสม


1
COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'ควรทำงานกับ "someValue" และข้อมูลใด ๆ ในตาราง
JimmyB

2

มีตัวดำเนินการEXCEPT ที่ติดตั้งไว้ในตัวซึ่งจะลบผลลัพธ์ของข้อความค้นหาที่สองจากแบบสอบถามแรกได้อย่างมีประสิทธิภาพ

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

หวังว่ามันจะเป็นโต๊ะเล็ก :-)
Lennart

-4

มีถ่านหินหรือไม่

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

4
ใช่มี COALESCE แต่ไม่สามารถใช้งานได้: (a) COALESCE จะไม่ยอมรับนิพจน์บูลีน (ทั้ง ISNULL, ตามวิธี) และ (b) ค่าความจริง FALSE ไม่สามารถใช้งานได้โดยตรงใน SQL เป็น ตัวอักษร ลองใช้แล้วคุณจะได้รับข้อผิดพลาดทางไวยากรณ์
Heinzi

@Heinzi - ฉันลองแล้วมันใช้งานได้นั่นเป็นสาเหตุที่ฉันโพสต์ไว้ อาจจะใช้ไม่ได้กับ T-SQL แต่มันใช้ได้กับ Postgres และ MySQL
Malvolio

2
@Malvolio: คำถามที่มีการติดแท็กsql-serverแต่ไม่ได้หรือmysql postgresql
Andriy M

@ Malvolio นั่นเป็นเพราะ Postgres มีBOOLEANประเภทและ MySQL มีประเภท (faked) BOOLEANซึ่งสามารถเป็นพารามิเตอร์ของCOALESCE()ฟังก์ชั่น หากคำถามถูกติดแท็กด้วยsql-agnosticหรือsql-standardคำตอบจะเป็นไร
ypercubeᵀᴹ

@ ypercubeᵀᴹ - เอ๊ะฉันจะบอกอะไรคุณได้บ้าง รับฐานข้อมูลที่ดีกว่า
Malvolio
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.