Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Postgres on conflict do update on composite primary keys

I have a table where a user answers to a question. The rules are that the user can answer to many questions or many users can answer one question BUT a user can answer to a particular question only once. If the user answers to the question again, it should simply replace the old one. Generally the on conflict do update works when we are dealing with unique columns. In this scenario the columns person_id and question_id cannot be unique. However the combination of the two is always unique. How do I implement the insert statement that does update on conflict?

CREATE TABLE "answer" (
  "person_id" integer NOT NULL REFERENCES person(id), 
  "question_id" integer NOT NULL REFERENCES question(id) ON DELETE CASCADE, /* INDEXED */
  "answer" character varying (1200) NULL,
  PRIMARY KEY (person_id, question_id) 
);
like image 565
pewpewlasers Avatar asked Jul 17 '18 07:07

pewpewlasers


People also ask

Can primary key be updated Postgres?

Suppose you have to change the primary key of a PostgreSQL database. You can achieve this by executing the following statements: ALTER TABLE "order" DROP CONSTRAINT order_pkey CASCADE, ADD PRIMARY KEY(column_i_want_to_use_as_a_pkey_now);

What does on conflict do PostgreSQL?

ON CONFLICT DO UPDATE updates the existing row that conflicts with the row proposed for insertion as its alternative action.

Can we have two primary keys in PostgreSQL?

There can only be one primary key per table - as indicated by the word "primary".

What is Upsert in PostgreSQL?

The UPSERT statement is a DBMS feature that allows a DML statement's author to either insert a row or if the row already exists, UPDATE that existing row instead. That is why the action is known as UPSERT (simply a mix of Update and Insert).


2 Answers

Just place both keys in the ON CONFLICT clause:

INSERT INTO answer VALUES (1,1,'q1') 
ON CONFLICT (person_id,question_id) 
DO UPDATE SET answer = EXCLUDED.answer; 

Example:

INSERT INTO answer VALUES (1,1,'q1') 
ON CONFLICT (person_id,question_id) 
DO UPDATE SET answer = EXCLUDED.answer;             

SELECT * FROM answer;
 person_id | question_id | answer 
-----------+-------------+--------
         1 |           1 | q1
(1 Zeile)

INSERT INTO answer VALUES (1,1,'q1-UPDATED') 
ON CONFLICT (person_id,question_id) 
DO UPDATE SET answer = EXCLUDED.answer;             

SELECT * FROM answer;
 person_id | question_id |   answer   
-----------+-------------+------------
         1 |           1 | q1-UPDATED
(1 Zeile)

Demo: db<>fiddle

like image 158
Jim Jones Avatar answered Oct 17 '22 03:10

Jim Jones


You could also define the primary externally to the table and then you don't need to re-write all columns included therein.

CREATE TABLE "answer" (
  "person_id" integer NOT NULL REFERENCES person(id), 
  "question_id" integer NOT NULL REFERENCES question(id) ON DELETE CASCADE, /* INDEXED */
  "answer" character varying (1200) NULL);

ALTER TABLE "answer" ADD CONSTRAINT answer_pk PRIMARY KEY (person_id, question_id);

And then:

INSERT INTO answer VALUES (1,1,'q1') ON CONFLICT ON CONSTRAINT answer_pk DO UPDATE SET answer = EXCLUDED.answer;

When the constraint changes in the future you don't need to manually adjust the insert statements to reflect this.

like image 14
Stefan Sonnenberg-Carstens Avatar answered Oct 17 '22 01:10

Stefan Sonnenberg-Carstens