我有一个定义相互递归表的模型:
Answer
questionId QuestionId
text
Question
text
correct AnswerId
实际插入问题需要做什么?我需要先知道正确答案是什么。但是要插入答案,我需要知道它回答了什么问题。
我正在运行Postgres,如果重要的话。
DDL是:
CREATE TABLE answer (
id integer NOT NULL, -- answer id
text character varying NOT NULL, -- answer text
question_id bigint NOT NULL -- question id
);
CREATE TABLE question (
id integer NOT NULL, -- question id
question character varying NOT NULL, -- question text
correct bigint NOT NULL, -- correct answer
solution character varying NOT NULL -- solution text
);
ALTER TABLE ONLY answer ALTER COLUMN id SET DEFAULT nextval('answer_id_seq'::regclass);
ALTER TABLE ONLY answer
ADD CONSTRAINT answer_question_id_fkey FOREIGN KEY (question_id) REFERENCES question(id);
ALTER TABLE ONLY question ALTER COLUMN id SET DEFAULT nextval('question_id_seq'::regclass);
ALTER TABLE ONLY question
ADD CONSTRAINT question_correct_fkey FOREIGN KEY (correct) REFERENCES answer(id);
答案 0 :(得分:3)
如果您使用data-modifying CTE在单一陈述中输入问题和答案,则甚至不需要DEFERRABLE
FK约束。更不用说实际制作(或SET
ting)DEFERRED
- 这将会更加昂贵。
首先我清理了你的数据模型:
CREATE TABLE question (
question_id serial PRIMARY KEY
, correct_answer_id int NOT NULL
, question text NOT NULL
, solution text NOT NULL
);
CREATE TABLE answer (
answer_id serial PRIMARY KEY
, question_id int NOT NULL REFERENCES question
, answer text NOT NULL
);
ALTER TABLE question ADD CONSTRAINT question_correct_answer_id_fkey
FOREIGN KEY (correct_answer_id) REFERENCES answer(answer_id);
text
等基本类型名作为列名。bigint
是不必要的,integer
应该足够了。serial
columns简化您的架构定义。NOT NULL
。我将主键生成委派给序列(serial
列),就像应该在大多数数据模型中一样。我们可以使用RETURNING
语句的INSERT
子句获取自动生成的ID。但在这种特殊情况下,我们需要为每个INSERT
提供两个 ID,因此我使用nextval()
获取其中一个ID以启动事件。
WITH q AS (
INSERT INTO question (correct_answer_id, question, solution)
VALUES (nextval('answer_answer_id_seq'), 'How?', 'DEFERRABLE FK & wCTE')
RETURNING correct_answer_id, question_id
)
INSERT INTO answer (answer_id, question_id, answer)
SELECT correct_answer_id, question_id, 'Use DEFERRABLE FK & data-modifying CTE'
FROM q;
我知道序列的名称('answer_answer_id_seq'
),因为我查了一下。它是默认名称。如果您不知道,请使用以下安全表单@IMSoP provided in the comment:
nextval(pg_get_serial_sequence('answer', 'answer_id'))
DEFERRABLE
或DEFERRED
约束? Per documentation on SET CONSTRAINTS
在每个语句的末尾检查
IMMEDIATE
个约束。
我的解决方案是单语句。这就是为什么它在两个单独的语句失败的情况下工作的原因 - 包装在一个或多个事务中。您需要SET CONSTRAINTS ... DEFERRED;
和IMSoP first commented等DEFERRABLE
但请注意免责声明中的一些段落:
尚未声明的唯一性和排除约束
UNIQUE
也会立即检查。
所以EXCLUDE
和DEFERRALBE
需要PRIMARY KEY
才能让wCTE为他们服务。这包括UNIQUE
个约束。 @Jaaz implemented in his answer:
非延迟唯一性约束
当
PRIMARY KEY
或DEFERRABLE
约束无法推迟时,PostgreSQL 插入行时立即检查唯一性 改性。 SQL标准说应该强制执行唯一性 只在声明的最后;这对于 例如,单个命令更新多个键值。获得 符合标准的行为,将约束声明为INITIALLY IMMEDIATE
但是 不推迟(即{{1}})。请注意,这可以 明显慢于直接唯一性检查。
我们在这个相关问题下非常详细地讨论过这个问题:
答案 1 :(得分:1)
我会插入问题,使用null正确的AnswerId。然后我会插入Answer,最后我会更新Question并设置正确的answerId。
答案 2 :(得分:1)
看到DDL后,我环顾四周。考虑一个函数,用于调用插入一个正确答案的问题,一个用于添加(错误)给定问题的答案。第一个函数的结构允许应用程序获取questionID的匿名返回记录,并将其用于后续调用第二个函数,以添加错误答案。
CREATE FUNCTION newQuestion (questionText varchar, questionSolutionText varchar, answerText varchar, OUT questionID integer) AS $$
BEGIN
START TRANSACTION;
SET CONSTRAINTS question_correct_fkey DEFERRED;
questionID := nextval('question_id_seq');
answerID := nextval('answer_id_seq');
INSERT INTO question (id, question, correct, solution) values (questionID, questionText, answerID, questionSolutionText);
INSERT INTO answer (id, text, question_id) values (answerID, answerText, questionID);
SET CONSTRAINTS question_correct_fkey IMMEDIATE;
COMMIT TRANSACTION;
END;
$$
CREATE FUNCTION addFalseAnswer (questionID integer, answerText varchar) AS $$
BEGIN
INSERT INTO answer (text, question_id) VALUES (answerText, questionID);
END;
$$
我很久没有为PostGreSQL编写SQL了,所以我希望所有这些都在这里。如果有任何问题,请告诉我。