Postgres SQL Exclusive OR (XOR) CHECK CONSTRAINT,可能吗?
Postgres SQL Exclusive OR (XOR) CHECK CONSTRAINT, is it possible?
是否可以进行 XOR CHECK CONSTRAINT?
我正在做一个测试 table 我刚做的那个叫做 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
您不能将 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);
-- fails
INSERT INTO xor_test(id, a, b) VALUES (3, 1, 1);
或者检查约束可以简化为
check ( num_nonnulls(a,b) = 1 )
这也更容易适应更多列
正确,a = NULL
和 b = 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
比较。在类似测试 table 中进行三列或更多列 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)
);
这是明确的异或。为什么不先将其定义为布尔运算符呢?它也可能对其他情况有用。
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))
感谢维克。我在 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
是否可以进行 XOR CHECK CONSTRAINT?
我正在做一个测试 table 我刚做的那个叫做 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
您不能将 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);
-- fails
INSERT INTO xor_test(id, a, b) VALUES (3, 1, 1);
或者检查约束可以简化为
check ( num_nonnulls(a,b) = 1 )
这也更容易适应更多列
正确,a = NULL
和 b = 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
比较。在类似测试 table 中进行三列或更多列 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)
);
这是明确的异或。为什么不先将其定义为布尔运算符呢?它也可能对其他情况有用。
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))
感谢维克。我在 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