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 根据条件更新某些列