【问题标题】:T-SQL exactly 1 condition is true out of a set of conditionsT-SQL 在一组条件中恰好有一个条件为真
【发布时间】: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:

标签: sql sql-server sql-server-2008 tsql


【解决方案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
        

        【讨论】:

          猜你喜欢
          • 2011-04-01
          • 2016-05-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-09
          • 1970-01-01
          • 1970-01-01
          • 2020-02-03
          相关资源
          最近更新 更多