Postgres inserts with ON CONSTRAINT DO UPSET, and FOR loops to avoid violating constraints

Elvis Ciotti
1 min readNov 4, 2021

If you need to insert a record, and in case of conflict (e.g. UNIQUE key) you can solve it by updating a value, you can use the Postgres construct ON CONSTRAINT constraint_name DO UPDATE SET column=<expression>

See the documentation.

In case the query is part of a loop, you can use a Postgres FOR … IN inside a DECLARE.

Below you can see an example that iterates the records in a table, then runs an AGGREGATE query on another table, and uses its results as a parameter to perform an insert inside the record. This might be useful for example to create corresponding records into another table in case a INSERT INTO … SELECT FROMcannot solve the problem with a single query.

DO $$ DECLARE
yourRecord RECORD;
nextAvailableValue bigint;
BEGIN
FOR yourRecord IN (SELECT * from yourTable) LOOP
SELECT MAX(col1)+1 INTO nextAvailableValue
FROM table_with_values
WHERE col2 = yourRecord.id;

INSERT INTO other_table(
id,
col3
) VALUES (
yourRecord.id,
nextAvailableValue
);
END LOOP;
END $$;

You can run the above as a Liquibase migration query as well.

Note that this syntax

  • does not allow reading a value from a subquery inside <expression>
  • does not allow to perform multiple times in case the constraint is violated more than once.

A more generic approach is running a pre-query to get the data you need and avoid violating the constraint.

--

--

Elvis Ciotti

Software Contractor — Java, Spring, k8s, AWS, Javascript @ London - hire me at https://www.linkedin.com/in/elvisciotti