Інвертуйте булевий вираз, який може повернути НЕЗНАЧЕНО


11

Приклад

У мене стіл

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

і булевий вираз T-SQL, який може оцінювати ІСТИЧНИЙ, ЛАЖНИЙ або (за рахунок потрійної логіки SQL) НЕВІДОМО:

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і я отримую "зворотний" вираз, який ніколи не дає НЕВІДОМОГО:

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 , яка є лише обгорткою навколо двійкового CASE, як вище. Отже, цей вираз CASE:

CASE WHEN someColumn = someValue THEN 1 ELSE 0 END

буде виглядати так, якщо переписати за допомогою IIF:

IIF(someColumn = someValue, 1, 0)

І ви можете використовувати його точно так само, як і вираз CASE. Не буде різниці в продуктивності, тільки код буде трохи більш стислим, можливо, чистішим і таким чином.


Це гарна ідея! Використовуйте CASE, щоб "перетворити" булевий вираз у вираз, з яким можна працювати, а потім використовуйте порівняння, щоб "перетворити" його назад у булевий вираз.
Хайнзі

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, по - моєму. Основним недоліком є ​​те, що друге значення, яке ви знаєте, не дорівнює, але це справді проблема лише тоді, коли ви не знаєте фактичного значення наперед. У цьому випадку ви можете зробити так, як пропонує Ханно Біндер і використовувати COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'(де 'someValue'насправді було б параметризовано).

COALESCE документально підтверджено, що вона буде доступна від SQL Server 2005 і далі.

Майте на увазі, що возитися з таким запитом (використовуючи будь-який із методів, рекомендованих тут) може ускладнити базу даних для оптимізації вашого запиту. Для великих наборів даних IS NULLверсію, ймовірно, простіше оптимізувати.


1
COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'повинно працювати для будь-якої "деякої оцінки" та будь-яких даних у таблиці.
JimmyB

2

Існує вбудований оператор набору EXCEPT , який фактично видаляє результати другого запиту з першого запиту.

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

Будемо сподіватися, що це невеликий столик :-)
Леннарт

-4

Чи доступна COALESCE?

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

4
Так, COALESCE доступний, але ні, це не спрацює: (a) COALESCE не сприймає булевого виразу (до речі, і ISNULL) та (b) значення FALSE неправдиво безпосередньо в SQL не доступне, оскільки буквальний. Спробуйте, і ви отримаєте синтаксичну помилку.
Хайнзі

@Heinzi - я це спробував, він спрацював, тому я і розмістив його. Можливо, він не працює на T-SQL, але це добре на Postgres та MySQL.
Мальволіо

2
@Malvolio: Питання буде позначений sql-server, однак, не mysqlчи postgresql.
Андрій М

@Malvolio тому, що Postgres має BOOLEANтип, а MySQL має BOOLEANтип (підроблений), який може бути параметрами COALESCE()функції. Якби питання було позначено тегом sql-agnosticабо sql-standard, відповідь буде нормальним.
ypercubeᵀᴹ

@ ypercubeᵀᴹ - е-е, що я можу тобі сказати? Отримайте кращу базу даних.
Мальволіо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.