postgresql Postgres SQL Exclusive OR(XOR)CHECK CONSTRAINT,这是可能的吗?

juud5qan  于 2023-06-22  发布在  PostgreSQL
关注(0)|答案(5)|浏览(121)

是否可以进行XOR CHECK CONSTRAINT?
我在一个我刚做的测试表上做,这个表叫做test,有3列:

  • id,bigint
  • a,bigint
  • B,bigint

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

(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
8xiog9wr

8xiog9wr1#

右,a = NULLb = NULL位是@a_horse_with_no_name的问题。你也可以考虑这个导数,它不需要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)
);
jvlzgdj9

jvlzgdj92#

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

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

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

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);

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

或者,check约束可以简化为

check ( num_nonnulls(a,b) = 1 )

这也更容易调整到更多的列

gjmwrych

gjmwrych3#

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

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))

wqsoz72f

wqsoz72f4#

感谢维克。我有一个类似的测试在一个视图。在左联接中,至少有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
6mzjoqzu

6mzjoqzu5#

类似于前面使用a_horse_with_no_name表示的num_nonnulls(a, b, c, ...) = 1,您也可以使用具有任意列数的数组。
示例:

CONSTRAINT chk_whatever CHECK (cardinality(
    array_remove(
        array[a, b, c, d, e, f, g], NULL
    )
) = 1)

逻辑:
1.将所有列值放入数组中
1.从该数组中删除所有NULL
1.检查是否只剩下一个值。
在Postgres 11上测试。

相关问题