为什么我插入的表格为空时会出现此错误?该表的上一次迭代(自已删除)有一个序列,所以我想知道Postgres是否正在缓存旧表模式?
这是错误:
-- Executing query:
DELETE FROM student
WHERE sno =101;
ERROR: duplicate key value violates unique constraint "cancel_pkey"
DETAIL: Key (eno, cdate)=(1, 2014-12-08 21:21:53.710883) already exists.
CONTEXT: SQL statement "INSERT INTO cancel(eno,excode,sno)
SELECT eno, excode, sno
FROM entry
WHERE eno = OLD.eno"
PL/pgSQL function cancel_entry() line 3 at SQL statement
SQL statement "DELETE FROM entry
WHERE sno = OLD.sno"
PL/pgSQL function delete_student() line 8 at SQL statement
实际的插入查询发生在触发器中,如下所示:
CREATE OR REPLACE FUNCTION delete_student()
RETURNS trigger AS $BODY$
BEGIN
IF (SELECT EXISTS (SELECT * FROM entry WHERE sno = OLD.sno)) THEN
INSERT INTO cancel(eno,excode,sno)
SELECT eno, excode, sno
FROM entry
WHERE sno = OLD.sno;
DELETE FROM entry
WHERE sno = OLD.sno;
END IF;
RETURN OLD;
END; $BODY$
LANGUAGE plpgsql;
CREATE TRIGGER DeleteStudent
BEFORE DELETE ON student
FOR EACH ROW
execute procedure delete_student();
cancel
表架构如下所示:
CREATE TABLE cancel (
eno INTEGER NOT NULL,
excode CHAR(4) NOT NULL, --not unique as many instances of cancellations for exam
sno INTEGER NOT NULL, --not unique as student may cancel several exams
cdate TIMESTAMP NOT NULL DEFAULT NOW(),
cuser VARCHAR(128) NOT NULL DEFAULT CURRENT_USER,
PRIMARY KEY(eno,cdate)
-- sno is not a foreign key, as it must still exist even where student is deleted.
);
entry
表架构是:
CREATE TABLE entry (
eno INTEGER NOT NULL DEFAULT NEXTVAL('eno_sequence'),
excode CHAR(4) NOT NULL,
sno INTEGER NOT NULL,
egrade DECIMAL(5,2) CHECK(egrade BETWEEN 0 AND 100),
PRIMARY KEY(eno),
FOREIGN KEY(excode) REFERENCES exam MATCH FULL ON DELETE RESTRICT,
FOREIGN KEY(sno) REFERENCES student MATCH FULL ON DELETE RESTRICT --handle with stored procedure to retain student reference in cancel table
);
'学生'表模式
CREATE TABLE student (
sno INTEGER NOT NULL,
sname VARCHAR(20) NOT NULL,
semail VARCHAR(20) NOT NULL,
PRIMARY KEY(sno)
);
答案 0 :(得分:2)
由于您正在从附加表entry
移动行,并且您首先运行SELECT / INSERT,因此存在潜在的竞争条件:多个并发事务可以同时尝试相同时间。
但是,触发器由表DELETE
上的student
调用,对受影响的行进行ROW EXCLUSIVE
锁定。您DELETE
(WHERE sno = 101
)的谓词位于 唯一 列上。尽管问题中缺少表student
的实际表定义,但我可以从表entry
中的FK定义中看出,它要求对引用列具有唯一或PK约束。这使得并发事务不会删除student
中会在竞争行上调用触发器的行。
首先让我感到困惑的是:您显示了触发器/函数DeleteStudent
/ delete_student()
,但异常是从 cancel_entry()
引发的,这是你问题中的 不是 。走出困境,看起来你在另一个触发器,中调用相同的INSERT
命令,这将解释异常。
无论哪种方式,我们看到的功能更昂贵,并且比竞争条件更容易受到影响。首先使用data-modifying CTE删除(锁定行),然后只插入另一个表(这可能会解决您的问题,缺少信息)。还简化:
CREATE OR REPLACE FUNCTION delete_student()
RETURNS trigger AS
$func$
BEGIN
IF (SELECT EXISTS (SELECT * FROM entry WHERE sno = OLD.sno)) THEN -- no need
WITH del AS (
DELETE FROM entry -- delete first, locking rows
WHERE sno = OLD.sno
RETURNING eno, excode, sno
)
INSERT INTO cancel(eno, excode, sno) -- only then insert
SELECT eno, excode, sno
FROM del;
END IF;
RETURN OLD;
END
$func$ LANGUAGE plpgsql;
您根本不需要IF
构造。如果SELECT
(或更新版本中的DELETE
)在entry
中找不到任何行,则INSERT
会一无所有。在CTE的情况下,当CTE INSERT
没有返回任何行时,永远不会执行最终del
。这就像它得到的那样快速和干净。
相关答案:
答案 1 :(得分:0)
目前,我决定在“条目”中使用FOREIGN KEY上的ON DELETE CASCADE约束下面的触发器
CREATE OR REPLACE FUNCTION delete_student()
RETURNS trigger AS
$func$
BEGIN
INSERT INTO cancel(eno, excode, sno)
SELECT eno, excode, sno
FROM entry
WHERE sno = OLD.sno;
RETURN OLD;
END
$func$ LANGUAGE plpgsql;