T-SQL 在一组条件中恰好有一个条件为真

Posted

技术标签:

【中文标题】T-SQL 在一组条件中恰好有一个条件为真【英文标题】:T-SQL exactly 1 condition is true out of a set of conditions 【发布时间】:2017-05-24 20:05:42 【问题描述】:

在 T-SQL 中表达多个布尔条件中只有 1 个且恰好 1 个为真(需要在 CHECK 约束中可用)的最简单方法是什么?

XOR 适用于 2 个条件,例如 A XOR B 将确保设置为 1,但不适用于 3 个条件:

一种解决方案是从中获取某种集合,过滤条件为真,执行和聚合/求和并检查结果是否等于 1。

【问题讨论】:

人们可能不清楚您展示的真值表是在展示 XOR 如何对您不起作用,而不是您的预期结果是,我的解释应该是如图所示,除了顶行中的F而不是T @Damien_The_Unbeliever 我认为很明显该表显示了它是如何工作的,而不是它应该如何工作,因为我之前提到过:but it does not work for 3 conditions: 【参考方案1】:

我会按照以下方式构建您的CHECK

CHECK (
    CASE WHEN <condition 1> THEN 1 ELSE 0 END +
    CASE WHEN <condition 2> THEN 1 ELSE 0 END +
    CASE WHEN <condition 3> THEN 1 ELSE 0 END
    = 1
)

这有点冗长,但希望 可读 看看你的意图是什么。它还比 XOR 更容易扩展到其他类似的要求(例如,“必须匹配 5 个条件中的 2 个”可以遵循相同的结构)

对于 SQL Server 2012 或更高版本,您可以使用IIF 更简洁:

CHECK (
    IIF(<condition 1>,1,0) +
    IIF(<condition 2>,1,0) +
    IIF(<condition 3>,1,0)
    = 1
)

【讨论】:

这将是一个解决方案,但其中仍然存在大量代码重复。请参阅我的问题中的更新。 @RăzvanPanda - 我认为无法避免冗长。您可能知道,SQL Server 没有用户可见的布尔数据类型 - 因此您不能按照您建议的方式做一些事情,因为没有办法将中间结果表示为 data @RăzvanPanda - 而且,显然,这种结构比基于 XOR 的结构更容易扩展到涵盖其他要求(例如,如果您的要求是“必须满足 5 个条件中的 2 个”,你可以简单地调整上面的内容以匹配) 知道是否可以映射布尔条件(因为它们不能存储在表文字中)?请参阅我添加的答案以了解我的意思【参考方案2】:

无耻取自this SO question,三个变量异或的通用公式可以写成:

(a ^ b ^ c) && !(a && b && c)

我们可以在 SQL Server 中将其表示为:

(A XOR B XOR C) AND NOT (A AND B AND C)

请注意,这仅适用于三个变量,并不能推广到更高的数字。如果变量超过三个,则需要做更多的工作。

【讨论】:

只是想发布这个:)【参考方案3】:

假设您的所有条件都表示为BIT 列,您可以使用以下格式进行约束:

alter table [table_name] add constraint [constraint_name]
check ( ( a ^ b ^ c ) = 1 AND NOT ( a & b & c ) = 1 )

这样做,您还可以在 case 语句中使用相同的条件,例如:

select a, b, c,
    case when (( a ^ b ^ c ) = 1 AND NOT ( a & b & c ) = 1) then 1
    else 0 end
    as true_or_false
from [table_name]

把这些放在一起,我们可以用这样的脚本来演示它:

create table #bits  (a bit, b bit, c bit)
create table #bits2  (a bit, b bit, c bit)

alter table #bits2 add constraint ck_xor
    check ( ( a ^ b ^ c ) = 1 AND NOT ( a & b & c ) = 1 )

insert into #bits
values
( 0, 0, 0 ), ( 0, 0, 1 ), ( 0, 1, 0 ), ( 0, 1, 1 ), ( 1, 0, 0 ), ( 1, 0, 1 ), ( 1, 1, 0 ), ( 1, 1, 1 )

select a, b, c,
    case when ( a ^ b ^ c ) = 1 AND NOT ( a & b & c ) = 1 then 1
    else 0 end
    as true_or_false
from #bits

insert into #bits2
select * from #bits
where ( a ^ b ^ c ) = 1 AND NOT ( a & b & c ) = 1 

-- the below line will fail because of the check constraint    
insert into #bits2 (a,b,c) values (1,1,0)

select * from #bits2

drop table #bits
drop table #bits2

【讨论】:

【参考方案4】:

声明三个变量@a, @b, @c 和它的位类型。其中 1 为真,0 为假。这里有所有可能的例子。

  declare @a bit, @b bit, @c bit;
set  @a=1; set @b=1;  set @c=1; select @a as a,@b as b,@c as c,  case when @a=@b then (case when @c=1 then 1 else 0 end) else (case when @c=0 then 1 else 0 end) end as xor;
set  @a=1; set @b=1;  set @c=0; select @a as a,@b as b,@c as c,  case when @a=@b then (case when @c=1 then 1 else 0 end) else (case when @c=0 then 1 else 0 end) end as xor;
set  @a=1; set @b=0;  set @c=0; select @a as a,@b as b,@c as c,  case when @a=@b then (case when @c=1 then 1 else 0 end) else (case when @c=0 then 1 else 0 end) end as xor;
set  @a=1; set @b=0;  set @c=0; select @a as a,@b as b,@c as c,  case when @a=@b then (case when @c=1 then 1 else 0 end) else (case when @c=0 then 1 else 0 end) end as xor;

set  @a=0; set @b=1;  set @c=0; select @a as a,@b as b,@c as c,  case when @a=@b then (case when @c=1 then 1 else 0 end) else (case when @c=0 then 1 else 0 end) end as xor;
set  @a=0; set @b=1;  set @c=0; select @a as a,@b as b,@c as c, case when @a=@b then (case when @c=1 then 1 else 0 end) else (case when @c=0 then 1 else 0 end) end as xor;
set  @a=0; set @b=0;  set @c=0; select @a as a,@b as b,@c as c, case when @a=@b then (case when @c=1 then 1 else 0 end) else (case when @c=0 then 1 else 0 end) end as xor;
set  @a=0; set @b=0;  set @c=0; select @a as a,@b as b,@c as c,  case when @a=@b then (case when @c=1 then 1 else 0 end) else (case when @c=0 then 1 else 0 end) end as xor;

【讨论】:

【参考方案5】:

为了进一步减少代码重复并使其更具可读性,可以使用以下方法:

(SELECT SUM(ExpressionValue)
FROM
    (VALUES
        (CASE WHEN 42 = 42 THEN 1 ELSE 0 END),
        (CASE WHEN 42 = 42 THEN 1 ELSE 0 END),
        (CASE WHEN 42 = 1 THEN 1 ELSE 0 END),
        (CASE WHEN 42 = 42 THEN 1 ELSE 0 END)
    ) AS conditions(ExpressionValue))
=
1

示例用法:

DECLARE @say as VARCHAR(MAX) =
    CASE
        WHEN (
            (SELECT SUM(ExpressionValue)
            FROM
                (VALUES
                    (CASE WHEN 42 = 42 THEN 1 ELSE 0 END),
                    (CASE WHEN 42 = 42 THEN 1 ELSE 0 END),
                    (CASE WHEN 42 = 1 THEN 1 ELSE 0 END),
                    (CASE WHEN 42 = 42 THEN 1 ELSE 0 END)
                ) AS conditions(ExpressionValue))
            =
            1
        ) THEN 'Only one set'
        ELSE 'Non only one set'
    END

PRINT @say

如果能以某种方式优雅地应用类似于地图的案例操作,它仍然可以改进。

【讨论】:

【参考方案6】:
declare @tt table (i int, b1 bit, b2 bit, b3 bit);
insert into @tt values (1,0,0,0), (2,1,1,1), (3,1,0,0)
select i, b1, b2, b3 
from @tt 
where cast(b1 as tinyint) + cast(b2 as tinyint) + cast(b3 as tinyint) = 1

【讨论】:

以上是关于T-SQL 在一组条件中恰好有一个条件为真的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL:在 UPDATE 语句中使用 CASE 根据条件更新某些列

如何在一组行之后或有条件地在没有 PL/SQL 块的情况下增加 oracle 序列?

如何测试一个条件是不是为真?

多个重写条件:如何在一组规则之前链接它们?

根据多个筛选条件 - SQL访问排除记录

没有连接条件的 T-SQL 匹配记录 1 到 1