【问题标题】:Postgres SQL Exclusive OR (XOR) CHECK CONSTRAINT, is it possible?Postgresql Exclusive OR (XOR) CHECK CONSTRAINT,有可能吗?
【发布时间】:2017-01-26 13:28:38
【问题描述】:

是否可以进行 XOR CHECK CONSTRAINT?

我正在我刚刚制作的名为 test 的测试表上执行此操作,并且有 3 列:

  • id,大整数
  • a, bigint
  • b,大整数

我为此做了一个检查约束:

(a IS NOT NULL AND b = NULL) OR (b IS NOT NULL AND a = NULL)

Which apparently would work in MSSQL

我通过这样做测试了它:

INSERT INTO public.test(
    id, a, b)
    VALUES (1, 1, 1);

这应该失败,因为它在 OR 的任一侧都不会评估为 TRUE。 但是,它插入得很好。

当我查看 postgres 实际存储为约束时,我得到了:

(a IS NOT NULL AND b = NULL::bigint OR b IS NOT NULL AND a = NULL::bigint)

我听说 AND 优先于 OR,所以即使这仍然有效。

有人对此有解决方案吗?最好是三列或更多列也可以的?我知道这些可能会更复杂。

编辑:改变

= NULL

IS NULL

给我:

ERROR:  cannot cast type boolean to bigint

【问题讨论】:

  • 我是IS NULL
  • @jarlh “错误:不能将 boolean 类型转换为 bigint”,如果我这样做的话。
  • 似乎您正在(尝试)在这里使用一些 Postgresql 特定的 SQL(我不知道。)
  • @Blanen:你是说a IS NOT NULL 是允许的,而a IS NULL 会抛出错误?那不太可能。请显示您的完整检查约束
  • @ThorstenKettner 是的,你是对的。我猜只是 pgAdmin4 很奇怪。

标签: sql postgresql constraints xor


【解决方案1】:

没错,@a_horse_with_no_name 指出的问题是 a = NULLb = NULL 位。你也可以考虑这个派生词,它不需要OR 运算符:

create table test 
(
  id integer primary key, 
  a integer, 
  b integer, 
  check ((a IS NULL) != (b IS NULL))
);

当然,这仅适用于两列 XOR 比较。在类似的测试表中使用三列或更多列 XOR 比较,您可以采用类似的方法:

create table test 
(
  id integer primary key, 
  a integer, 
  b integer, 
  c integer, 
  check ((a IS NOT NULL)::INTEGER + 
         (b IS NOT NULL)::INTEGER + 
         (c IS NOT NULL)::INTEGER = 1)
);

【讨论】:

  • 您的多列示例不应该使用IS NOT NULL 而不是IS NULL吗?如果 A IS NULL (1) 和 B IS NULL (1),但 C IS NOT NULL(一个有效的 XOR 条件),那么结果和将是 2 而不是 1,并且检查失败。
  • 嗯,这取决于约束是如何定义的。 XOR 对于超过 2 个输入没有很好的定义,但“恰好 1 为真”是一种常见的直译。如此处所定义,检查需要一个且只有一个 NULL。一个变体检查只有一个且只有一个不为空,作为练习留给读者作为练习,所有“不超过/少于”变体也是如此。
  • 如所写,此示例检查“恰好 1 为 NULL”,而更可能需要的检查是“恰好 1 不为 NULL” - 与 3 路或更多路 XOR 的情况一样操作。
  • 阅读上面的 cmets。编辑答案以添加您的意见通常是不好的形式。
【解决方案2】:

你不能用=比较NULL值,你需要IS NULL

(a IS NOT NULL AND b is NULL) OR (b IS NOT NULL AND a is NULL)

对于检查约束,您需要将整个表达式括在括号中:

create table xor_test 
(
  id integer primary key, 
  a integer, 
  b integer, 
  check ((a IS NOT NULL AND b is NULL) OR (b IS NOT NULL AND a is NULL))
);

-- works
INSERT INTO xor_test(id, a, b) VALUES (1, null, 1);

-- works
INSERT INTO xor_test(id, a, b) VALUES (2, 1, null);

-- failse
INSERT INTO xor_test(id, a, b) VALUES (3, 1, 1); 

或者检查约束可以简化为

check ( num_nonnulls(a,b) = 1 )

这也更容易适应更多列

【讨论】:

    【解决方案3】:

    这是明确的异或。为什么不先将其定义为布尔运算符?它可能对其他情况也有用。

    CREATE OR REPLACE FUNCTION public.xor (a boolean, b boolean) returns boolean immutable language sql AS
    $$
    SELECT (a and not b) or (b and not a);
    $$;
    
    CREATE OPERATOR # 
    (
        PROCEDURE = public.xor, 
        LEFTARG = boolean, 
        RIGHTARG = boolean
    );
    

    然后检查((a IS NULL) # (b IS NULL))

    【讨论】:

      【解决方案4】:

      感谢维克。我在vue中进行了类似的测试。在左连接中,至少 2 列或更多列不能为空。

      SELECT
          (tbl1.col1 IS NOT NULL)::INTEGER +
          (tbl2.col1 IS NOT NULL)::INTEGER +
          (tbl3.col1 IS NOT NULL)::INTEGER +
          (tbl4.col1 IS NOT NULL)::INTEGER +
          (tbl5.col1 IS NOT NULL)::INTEGER +
          (tbl6.col1 IS NOT NULL)::INTEGER > 1 AS
          b_mult_cols
      FROM tlb1
          LEFT JOIN tbl2 ON tlb1.col1 = tlb2.col1
          LEFT JOIN tbl3 ON tlb1.col1 = tlb3.col1
          LEFT JOIN tbl4 ON tlb1.col1 = tlb4.col1
          LEFT JOIN tbl5 ON tlb1.col1 = tlb5.col1
          LEFT JOIN tbl6 ON tlb1.col1 = tlb6.col1
      

      【讨论】:

        猜你喜欢
        • 2010-10-06
        • 1970-01-01
        • 2022-12-04
        • 2017-06-01
        • 2011-03-15
        • 2015-05-09
        • 1970-01-01
        • 2017-09-30
        • 2017-07-26
        相关资源
        最近更新 更多