Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
244 views
in Technique[技术] by (71.8m points)

sql - How to deal with mutually dependent inserts

I have a model that defines mutually recursive tables:

Answer
  questionId QuestionId
  text

Question
  text
  correct AnswerId

What do I need to do to actually insert a question? I need to know what the correct answer is first. But to insert an answer, I need to know what question it answers.

I'm running Postgres, if it matters.

The DDL is:

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
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If you enter question and answer in a single statement with a data-modifying CTE, you do not even need a DEFERRABLE FK constraints. Not to speak of actually making (or SETting) them DEFERRED - which would be a lot more expensive.

Data model

First I cleaned up your data model:

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);
  • Don't use the non-descriptive "id" or "text" (also a basic type name) as column names.
  • Put integer columns first for space efficiency. See:
  • bigint was uncalled for, integer should suffice.
  • Simplify your schema definition with serial columns.
  • Define primary keys. PK columns are NOT NULL automatically.

Solution

After delegating primary key generation to sequences (serial columns), we can get auto-generated IDs with the RETURNING clause of the INSERT statement. But in this special case we need both IDs for each INSERT, so I fetch one of them with nextval() to get it going.

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;

I know the name of the sequence ('answer_answer_id_seq') because I looked it up. It's the default name. If you don't know it use the safe form @IMSoP provided in a comment:

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

DEFERRABLE or DEFERRED constraints?

The manual on SET CONSTRAINTS:

IMMEDIATE constraints are checked at the end of each statement.

My solution is a single statement. That's why it works where two separate statements would fail - wrapped in a single transaction or not. And you'd need SET CONSTRAINTS ... DEFERRED; like IMSoP first commented and @Jaaz implemented in his answer.
However, note the disclaimer some paragraphs down:

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

So UNIQUE and EXCLUDE need to be DEFERRABLE to make CTEs work for them. This includes PRIMARY KEY constraints. 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.

We discussed this in great detail under this related question:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...