Re: "reverse" (?) UPSERT -- how to ?

From: Greg Sabino Mullane <htamfids(at)gmail(dot)com>
To: Karsten Hilbert <Karsten(dot)Hilbert(at)gmx(dot)net>
Cc: pgsql-general(at)lists(dot)postgresql(dot)org
Subject: Re: "reverse" (?) UPSERT -- how to ?
Date: 2024-02-17 16:54:05
Message-ID: CAKAnmmL2o6eLMu11voLJ8rAH15Hj9O1joKN2Kt8S3YV3WmHiTQ@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general

On Sat, Feb 17, 2024 at 10:24 AM Karsten Hilbert <Karsten(dot)Hilbert(at)gmx(dot)net>
wrote:

> There will be a view giving rows for
> each detail row enriched with master table data
> UNION ALL
> rows for each master row that does not have any detail row with
> detail table columns NULLed
>

A better way to do that is to just use a LEFT JOIN.

Given a pk_detail (and pk_master) having been obtained from the view
> (therefore pk_detail being NULL or an integer value)
> UPDATE that detail row (namely when pk_detail is distinct from NULL) or
> insert a new detail row (when pk_detail IS
> NULL) linking that row to the master row identified by pk_master.

What you want is the MERGE command:

https://www.postgresql.org/docs/current/sql-merge.html

To simplify your example a little, let's create two tables, linked to each
other by foreign keys, in which only 2 of the five rows have matching
details:

drop table if exists t2;
drop table if exists t1;

create table t1(id serial primary key, v text);

insert into t1(v) select 'foo' from generate_series(1,5);

create table t2(id serial primary key, fk int references t1(id), w text);

insert into t2(fk,w) values (2, 'bar1'), (2, 'bar2'), (3,'bar3');

select * from t2 order by 1;

id | fk | w
----+----+------
1 | 2 | bar1
2 | 2 | bar2
3 | 3 | bar3
(3 rows)

Now we can use that left join, plus a merge based on the results, to
conditionally update or insert:

WITH x AS (SELECT t1.id as t1id, t2.id AS t2id, * FROM t1 LEFT JOIN t2 ON (
t1.id=t2.fk) )
MERGE INTO t2
USING x ON (x.t2id = t2.id)
WHEN MATCHED THEN UPDATE SET w='new info for existing row'
WHEN NOT MATCHED THEN INSERT (fk,w) VALUES (x.t1id, 'new info for a new
row');

select * from t2 order by 1;

id | fk | w
----+----+---------------------------
1 | 2 | new info for existing row
2 | 2 | new info for existing row
3 | 3 | new info for existing row
4 | 5 | new info for a new row
5 | 4 | new info for a new row
6 | 1 | new info for a new row
(6 rows)

Cheers,
Greg

In response to

Responses

Browse pgsql-general by date

  From Date Subject
Next Message Adrian Klaver 2024-02-17 16:55:57 Re: "reverse" (?) UPSERT -- how to ?
Previous Message Adrian Klaver 2024-02-17 16:46:18 Re: Version 6 binaries for RHEL 7