如何处理相互递归的插入

时间:2014-07-17 20:32:15

标签: sql postgresql database-design foreign-keys referential-integrity

我有一个定义相互递归表的模型:

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

3 个答案:

答案 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);
  • 不要使用非描述性的" id"作为专栏名称。
  • 请勿使用text等基本类型名作为列名。
  • 首先放置整数列以提高空间效率:
  • bigint是不必要的,integer应该足够了。
  • 使用serial columns简化您的架构定义。
  • 定义主键。 PK列自动为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'))

DEFERRABLEDEFERRED约束?

Per documentation on SET CONSTRAINTS

  在每个语句的末尾检查

IMMEDIATE个约束。

我的解决方案是语句。这就是为什么它在两个单独的语句失败的情况下工作的原因 - 包装在一个或多个事务中。您需要SET CONSTRAINTS ... DEFERRED;IMSoP first commentedDEFERRABLE 但请注意免责声明中的一些段落:

  

尚未声明的唯一性和排除约束   UNIQUE也会立即检查。

所以EXCLUDEDEFERRALBE需要PRIMARY KEY才能让wCTE为他们服务。这包括UNIQUE个约束。 @Jaaz implemented in his answer

  

非延迟唯一性约束

     

PRIMARY KEYDEFERRABLE约束无法推迟时,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了,所以我希望所有这些都在这里。如果有任何问题,请告诉我。