尝试在 postgresql 中创建条件唯一索引但无法执行此操作并收到此错误
Query 1 ERROR: ERROR: cannot use subquery in index predicate
LINE 3: WHERE (
^
这就是我的查询的样子
CREATE UNIQUE INDEX conditional_unique_index
ON test_table (a, b)
WHERE (
SELECT COUNT(*)
FROM test_table t2
WHERE t2.a = test_table.a AND t2.b = test_table.b
) = 1;
我的要求是这样的:
CREATE TABLE test_table (
a integer,
b integer,
c integer
);
INSERT INTO test_table (a, b, c) VALUES
(1, 2, 22),
(1, 2, 22),
(1, 2, 22),
(1, 3, 34),
(2, 3, 26),
(2, 3, 26);
条件是如果列 (a, b) 中有多个行具有相同的值,则列 c 中的值必须相同,如下所示 (1, 2, 22),(1, 2, 22), (1,2,22);这是不允许的 (1, 2, 22),(1, 2, 23),(1, 2, 22);这里的 c 必须相同 (1, 2, 23)
现在第二个条件是如果列 (a, b) 是唯一的,那么 c 也应该是唯一的 像这样 (1, 3, 34),(2, 3, 26);这是不允许的 (1, 3, 34),(2, 3, 34);在这种情况下不允许 c 具有相同的值
到目前为止,对于第一种情况,我已经通过使用触发器来管理它
CREATE OR REPLACE FUNCTION check_conditional_unique() RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM test_table t2
WHERE t2.a = NEW.a AND t2.b = NEW.b AND t2.c <> NEW.c
) THEN
RAISE EXCEPTION 'Duplicate values in c for the same a and b combination.';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_check_conditional_unique
BEFORE INSERT ON test_table
FOR EACH ROW
EXECUTE FUNCTION check_conditional_unique();
但是对于第二种情况,我面临的问题是这个查询是错误的,我为此编写了
CREATE UNIQUE INDEX conditional_unique_index
ON test_table (a, b)
WHERE (
SELECT COUNT(*)
FROM test_table t2
WHERE t2.a = test_table.a AND t2.b = test_table.b
) = 1;
最佳答案
您可以在触发器中处理您描述的两种情况,而不受约束:demo
CREATE OR REPLACE FUNCTION check_conditional_unique() RETURNS TRIGGER AS $f$
BEGIN
IF EXISTS (--reject same (a,b) for different c
SELECT FROM test_table t2
WHERE (t2.a,t2.b) = (NEW.a,NEW.b)
AND t2.c <> NEW.c
) THEN
RAISE EXCEPTION 'New values in c for the same a and b combination.';
END IF;
IF EXISTS (--reject same c for different (a,b)
SELECT FROM test_table t2
WHERE (t2.a,t2.b) <> (NEW.a,NEW.b)
AND t2.c = NEW.c
) THEN
RAISE EXCEPTION 'Duplicate values in c for different a and b combination.';
END IF;
RETURN NEW;
END;
$f$ LANGUAGE plpgsql;
您给出的示例与 unique
不符或部分唯一约束用于;只要他们有共同的 c
您确实需要重复的 (a,b)
对被允许。
如suggested by @Laurenz Albe ,为了避免竞争条件,您可以将结构拆分为由表引用的组合目录,并在目录级别应用约束。要完全涵盖您的规则:( demo )
CREATE TABLE available_combinations (
a integer NOT NULL,
b integer NOT NULL,
c integer NOT NULL UNIQUE,
UNIQUE(a,b),
UNIQUE(a,b,c)--this is a natural consequence of the previous two UNIQUEs but
--it's required for foreign keys to link complete combinations
);
CREATE TABLE test_table (
pkey integer generated by default as identity PRIMARY KEY,
a integer NOT NULL,
b integer NOT NULL,
c integer NOT NULL,
FOREIGN KEY (a, b, c) REFERENCES available_combinations (a, b, c)
);
test_table
允许对同一组合进行多次引用,从而允许您所描述的有效重复。
全部三个UNIQUE
available_combinations
上需要 s :
- 仅留下
UNIQUE(a,b,c)
允许多个不同的c
同样(a,b)
配对和不同(a,b)
配对相同c
. - 仅留下
UNIQUE(c)
和UNIQUE(a,b,c)
仍然允许多个不同的c
同样(a,b)
一对。 - 仅留下
UNIQUE(a,b)
和UNIQUE(a,b,c)
仍然允许多个不同的不同(a,b)
配对相同c
. - 其实只留下
UNIQUE(a,b)
也可以和UNIQUE(c)
。它已经在逻辑上暗示了UNIQUE(a,b,c)
同样,但需要完整的约束来强制外部记录引用完整的组合。
您也可以留下 c
或(a,b)
完全关闭test_table
它允许您将外键规范限制为其中之一并删除 UNIQUE(a,b,c)
,但它也迫使您通过不可插入的 View 或 join
来抓取它每隔 select
就会回来。 Demo :
CREATE TABLE available_combinations (
a integer NOT NULL,
b integer NOT NULL,
c integer NOT NULL UNIQUE,
UNIQUE(a,b) );
CREATE TABLE test_table (
pkey integer generated by default as identity PRIMARY KEY,
c integer REFERENCES available_combinations (c) NOT NULL );
CREATE VIEW v_test_table AS SELECT pkey,a,b,c
FROM test_table t NATURAL JOIN available_combinations a;
您可以考虑使用一个触发器来创建新的 available_option
当您将一个插入test_table
时无需先创建它。
关于sql - postgres 中的条件索引和触发器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77257389/