反转可返回UNKNOWN的布尔表达式


11

我有桌子

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

以及一个T-SQL布尔表达式,其值可以为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并且得到一个“可逆”表达式,它永远不会产生未知:

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年更高的版本,我也很想知道这一点。

Answers:


15

您可以将条件包含在返回二进制结果(例如1或0)的CASE表达式中:

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

表达式的取反将为您提供来自同一数据源的所有其他行,包括someColumn为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
;

从SQL Server 2012开始,您还具有IIF函数,该函数只是上述二进制CASE的包装。因此,此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始终返回truefalse,永远不会未知的方式。需要的SELECT 1 WHERE是不幸的是必要的,但它可能是可行的为你的要求,例如:

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

请参见EXISTS(Transact-SQL)


一个稍微复杂一点的工作示例,展示了如何将EXISTSCASE/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'
)

在我看来,它比CASEor 更干净,更简单,更明显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

有COALESCE吗?

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

4
是的,可以使用COALESCE,但是不能,这是行不通的:(a)COALESCE将不接受布尔表达式(顺便说一句,ISNULL也不会)和(b)真值FALSE在SQL中不直接可用,因为一个文字。试试看,您会收到语法错误。
Heinzi

@Heinzi-我确实尝试过,它起作用了,所以才发布了它。也许它在T-SQL上不起作用,但是在Postgres和MySQL上就可以了。
马尔沃里奥16-3-21

2
@Malvolio:问题标记了sql-server,但是不是mysqlpostgresql
Andriy M

@Malvolio这是因为Postgres有一个BOOLEAN类型,而MySQL有一个(伪造的)BOOLEAN类型,它可以作为COALESCE()函数的参数。如果问题已用sql-agnostic或标记sql-standard,则可以。
ypercubeᵀᴹ

@ypercubeᵀᴹ-嗯,我能告诉你什么?获得更好的数据库。
马尔沃里奥16-3-23
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.