我有一个定义相互递归表的模型:
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 约束。更不用说实际制作(或 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
。
解决方案
将主键生成委托(delegate)给序列(serial
列)后,我们可以使用 INSERT
的 RETURNING
子句获取自动生成的 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'))
DEFERRABLE
或 DEFERRED
约束?
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.
所以 UNIQUE
和 EXCLUDE
需要是 DEFERRABLE
才能让 CTE 为它们工作。这包括 PRIMARY KEY
约束。 The documentation on CREATE TABLE
has more details :
Non-deferred Uniqueness Constraints
When a
UNIQUE
orPRIMARY 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 asDEFERRABLE
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/