sql - 如何处理相互依赖的插入

标签 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);
```sql

最佳答案

如果您在带有 data-modifying CTE单个语句中输入问题和答案,您甚至不需要 DEFERRABLE FK 约束。更不用说实际制作(或 SETting)它们 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

解决方案

将主键生成委托(delegate)给序列(serial 列)后,我们可以使用 INSERTRETURNING 子句获取自动生成的 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 & CTE')
   RETURNING correct_answer_id, question_id
   )
INSERT INTO answer
      (answer_id        , question_id, answer)
SELECT correct_answer_id, question_id, 'Use DEFERRABLE FK & CTE'
FROM   q;

知道序列的名称 ('answer_answer_id_seq'),因为我查过它。这是默认名称。如果您不知道,请使用安全形式 @IMSoP provided in a comment :

nextval(pg_get_serial_sequence('answer', 'answer_id'))

DEFERRABLEDEFERRED 约束?

The manual on SET CONSTRAINTS:

IMMEDIATE constraints are checked at the end of each statement.

我的解决方案是单个 语句。这就是为什么它在两个单独的语句会失败的情况下工作——是否包含在一个事务中。你需要 SET CONSTRAINTS ... DEFERRED; 就像 IMSoP first commented@Jaaz implemented in his answer .
但是,请注意下面几段的免责声明:

Uniqueness and exclusion constraints that have not been declared DEFERRABLE are also checked immediately.

所以 UNIQUEEXCLUDE 需要是 DEFERRABLE 才能让 CTE 为它们工作。这包括 PRIMARY KEY 约束。 The documentation on CREATE TABLE has more details :

Non-deferred Uniqueness Constraints

When a UNIQUE or PRIMARY KEY constraint is not deferrable, PostgreSQL checks for uniqueness immediately whenever a row is inserted or modified. The SQL standard says that uniqueness should be enforced only at the end of the statement; this makes a difference when, for example, a single command updates multiple key values. To obtain standard-compliant behavior, declare the constraint as DEFERRABLE but not deferred (i.e., INITIALLY IMMEDIATE). Be aware that this can be significantly slower than immediate uniqueness checking.

我们在这个相关问题下对此进行了非常详细的讨论:

关于sql - 如何处理相互依赖的插入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24813000/

相关文章:

mysql - SQL 查询返回特定标签(如果存在)(如果不存在则返回 0)

database-design - 将三元关系分解为二进制关系

mysql - 购物车订购表

mysql View 与存储过程

SQL删除字符串中间多余的逗号

php - 构建和搜索 GPS 坐标的 SQL 数据库/表

postgresql - 在 postgresql 10 中删除 archive_status 日志文件是否安全

mysql - 目录、模式、用户和数据库实例之间的关系

sql - 减去2个表的计数

mysql - 查找 SQL 查询中的最后一条记录