How do I upsert depending on a second table?

Started by Samuel Marks7 months ago17 messagesgeneral
Jump to latest
#1Samuel Marks
samuelmarks@gmail.com

Attempt:
```sql
CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
WHERE EXISTS (SELECT 1
FROM org org_tbl
WHERE org_tbl.name = EXCLUDED.org
AND org_tbl.owner = 'wrong user')
RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

#2Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Samuel Marks (#1)
Re: How do I upsert depending on a second table?

On 9/23/25 13:36, Samuel Marks wrote:

Attempt:
```sql
CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
WHERE EXISTS (SELECT 1
FROM org org_tbl
WHERE org_tbl.name = EXCLUDED.org
AND org_tbl.owner = 'wrong user')

Where is org_tbl?

Or is this a copy and paste error?

RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

--
Adrian Klaver
adrian.klaver@aklaver.com

#3Samuel Marks
samuelmarks@gmail.com
In reply to: Adrian Klaver (#2)
Re: How do I upsert depending on a second table?

the AS syntax can alternatively be used for aliases
https://www.postgresql.org/docs/current/sql-select.html

`SELECT actual_tablename table0 WHERE table0.column00 = 1`

(I used a space)

Show quoted text

On Tue, Sep 23, 2025 at 3:52 PM Adrian Klaver <adrian.klaver@aklaver.com> wrote:

On 9/23/25 13:36, Samuel Marks wrote:

Attempt:
```sql
CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
WHERE EXISTS (SELECT 1
FROM org org_tbl
WHERE org_tbl.name = EXCLUDED.org
AND org_tbl.owner = 'wrong user')

Where is org_tbl?

Or is this a copy and paste error?

RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

--
Adrian Klaver
adrian.klaver@aklaver.com

#4Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Samuel Marks (#1)
Re: How do I upsert depending on a second table?

On 9/23/25 13:36, Samuel Marks wrote:

Attempt:
```sql
CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
WHERE EXISTS (SELECT 1
FROM org org_tbl
WHERE org_tbl.name = EXCLUDED.org
AND org_tbl.owner = 'wrong user')
RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

Also, as shown, there is no conflict so I don't see the condition being
run per:

https://www.postgresql.org/docs/current/sql-insert.html#SQL-ON-CONFLICT

"
condition

An expression that returns a value of type boolean. Only rows for
which this expression returns true will be updated, although all rows
will be locked when the ON CONFLICT DO UPDATE action is taken. Note that
condition is evaluated last, after a conflict has been identified as a
candidate to update.

"

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

--
Adrian Klaver
adrian.klaver@aklaver.com

#5Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Samuel Marks (#3)
Re: How do I upsert depending on a second table?

On 9/23/25 13:56, Samuel Marks wrote:

the AS syntax can alternatively be used for aliases
https://www.postgresql.org/docs/current/sql-select.html

`SELECT actual_tablename table0 WHERE table0.column00 = 1`

Ok, I missed the:

... FROM org org_tbl ...

(I used a space)

Yeah, I use tbl_name AS alias_name to help these old eyes catch this
sort of thing in my queries.

--
Adrian Klaver
adrian.klaver@aklaver.com

#6Samuel Marks
samuelmarks@gmail.com
In reply to: Adrian Klaver (#4)
Re: How do I upsert depending on a second table?

Yeah I know my approach doesn't work, my question is, what is the
correct way to do an upsert for this schema?

Specifically:

- Create a new repo if one by that name doesn't exist + requestor is
`owner` of associated `org`
- Update an existing repo if one by that name does exist + requestor
is `owner` of associated `org`

Show quoted text

On Tue, Sep 23, 2025 at 3:57 PM Adrian Klaver <adrian.klaver@aklaver.com> wrote:

On 9/23/25 13:36, Samuel Marks wrote:

Attempt:
```sql
CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
WHERE EXISTS (SELECT 1
FROM org org_tbl
WHERE org_tbl.name = EXCLUDED.org
AND org_tbl.owner = 'wrong user')
RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

Also, as shown, there is no conflict so I don't see the condition being
run per:

https://www.postgresql.org/docs/current/sql-insert.html#SQL-ON-CONFLICT

"
condition

An expression that returns a value of type boolean. Only rows for
which this expression returns true will be updated, although all rows
will be locked when the ON CONFLICT DO UPDATE action is taken. Note that
condition is evaluated last, after a conflict has been identified as a
candidate to update.

"

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

--
Adrian Klaver
adrian.klaver@aklaver.com

#7David G. Johnston
david.g.johnston@gmail.com
In reply to: Samuel Marks (#1)
Re: How do I upsert depending on a second table?

On Tuesday, September 23, 2025, Samuel Marks <samuelmarks@gmail.com> wrote:

$subject

You can only upsert/provoke a meaningful conflict on the singular table
being inserted into.

There are other features like functions and triggers that may get you
something usable.

David J.

#8Juan Rodrigo Alejandro Burgos Mella
rodrigoburgosmella@gmail.com
In reply to: Samuel Marks (#1)
Re: How do I upsert depending on a second table?

Hi Samuel

Using ON CONFLICT is a headache.
It's better to use the versatility of a Trigger: you have the full record
at your fingertips, and if you're going to UPDATE, you have the previous
record too.
There's much more control.

Also, you can always count on the beloved foreign keys, which are also
quite useful.

Atte.
JRBM

El mar, 23 sept 2025 a las 15:37, Samuel Marks (<samuelmarks@gmail.com>)
escribió:

Show quoted text

Attempt:
```sql
CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
WHERE EXISTS (SELECT 1
FROM org org_tbl
WHERE org_tbl.name = EXCLUDED.org
AND org_tbl.owner = 'wrong user')
RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

#9Samuel Marks
samuelmarks@gmail.com
In reply to: Juan Rodrigo Alejandro Burgos Mella (#8)
Re: How do I upsert depending on a second table?

Ok so you're thinking I give up on putting it all in one query and
instead use a transaction? - Is that the recommended way?

```sql
TRUNCATE repo, org;
INSERT INTO org(name, owner) VALUES ('org0', 'user0');
```

```sql
START TRANSACTION READ WRITE;

SELECT 1/COUNT(*)
FROM org
WHERE name = 'org0'
AND owner = 'wrong user';

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
RETURNING id;

COMMIT;
```

On Tue, Sep 23, 2025 at 7:25 PM Juan Rodrigo Alejandro Burgos Mella
<rodrigoburgosmella@gmail.com> wrote:

Show quoted text

Hi Samuel

Using ON CONFLICT is a headache.
It's better to use the versatility of a Trigger: you have the full record at your fingertips, and if you're going to UPDATE, you have the previous record too.
There's much more control.

Also, you can always count on the beloved foreign keys, which are also quite useful.

Atte.
JRBM

El mar, 23 sept 2025 a las 15:37, Samuel Marks (<samuelmarks@gmail.com>) escribió:

Attempt:
```sql
CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
WHERE EXISTS (SELECT 1
FROM org org_tbl
WHERE org_tbl.name = EXCLUDED.org
AND org_tbl.owner = 'wrong user')
RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

#10Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Juan Rodrigo Alejandro Burgos Mella (#8)
Re: How do I upsert depending on a second table?

On 9/23/25 17:25, Juan Rodrigo Alejandro Burgos Mella wrote:

Hi Samuel

Using ON CONFLICT is a headache.

Like any tool ON CONFLICT has usage it is best for, if you try to force
it do something it was not designed for then it will not perform as
expected. Stick to what it good at and it will not be a headache. It is
good at moving data into a table where the incoming data is a mix of
entirely new rows and changes to existing rows for which there is some
sort of arbiter to decide on whether there is a conflict or not.

It's better to use the versatility of a Trigger: you have the full
record at your fingertips, and if you're going to UPDATE, you have the
previous record too.

https://www.postgresql.org/docs/current/sql-insert.html#SQL-ON-CONFLICT

"
The SET and WHERE clauses in ON CONFLICT DO UPDATE have access to the
existing row using the table's name (or an alias), and to the row
proposed for insertion using the special excluded table.
"

There's much more control.

Also, you can always count on the beloved foreign keys, which are also
quite useful.

Atte.
JRBM

--
Adrian Klaver
adrian.klaver@aklaver.com

#11Juan Rodrigo Alejandro Burgos Mella
rodrigoburgosmella@gmail.com
In reply to: Samuel Marks (#9)
Re: How do I upsert depending on a second table?

The insert works because there is no data in the repo table that conflicts
with the entered full name.

JRBm

El mar, 23 sept 2025, 23:19, Samuel Marks <samuelmarks@gmail.com> escribió:

Show quoted text

Ok so you're thinking I give up on putting it all in one query and
instead use a transaction? - Is that the recommended way?

```sql
TRUNCATE repo, org;
INSERT INTO org(name, owner) VALUES ('org0', 'user0');
```

```sql
START TRANSACTION READ WRITE;

SELECT 1/COUNT(*)
FROM org
WHERE name = 'org0'
AND owner = 'wrong user';

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
RETURNING id;

COMMIT;
```

On Tue, Sep 23, 2025 at 7:25 PM Juan Rodrigo Alejandro Burgos Mella
<rodrigoburgosmella@gmail.com> wrote:

Hi Samuel

Using ON CONFLICT is a headache.
It's better to use the versatility of a Trigger: you have the full

record at your fingertips, and if you're going to UPDATE, you have the
previous record too.

There's much more control.

Also, you can always count on the beloved foreign keys, which are also

quite useful.

Atte.
JRBM

El mar, 23 sept 2025 a las 15:37, Samuel Marks (<samuelmarks@gmail.com>)

escribió:

Attempt:
```sql
CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
WHERE EXISTS (SELECT 1
FROM org org_tbl
WHERE org_tbl.name = EXCLUDED.org
AND org_tbl.owner = 'wrong user')
RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

#12Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Juan Rodrigo Alejandro Burgos Mella (#11)
Re: How do I upsert depending on a second table?

On 9/23/25 23:44, Juan Rodrigo Alejandro Burgos Mella wrote:

The insert works because there is no data in the repo table that
conflicts with the entered full name.

Except this part:

SELECT 1/COUNT(*)
FROM org
WHERE name = 'org0'
AND owner = 'wrong user';

will cause a divide by 0 error and abort the transaction preventing the
INSERT from happening.

Example:

test=# begin ;
BEGIN
test=*# select 1/0;
ERROR: division by zero
test=!# select 1;
ERROR: current transaction is aborted, commands ignored until end of
transaction block
test=!# rollback ;
ROLLBACK

JRBm

El mar, 23 sept 2025, 23:19, Samuel Marks <samuelmarks@gmail.com
<mailto:samuelmarks@gmail.com>> escribió:

Ok so you're thinking I give up on putting it all in one query and
instead use a transaction? - Is that the recommended way?

```sql
TRUNCATE repo, org;
INSERT INTO org(name, owner) VALUES ('org0', 'user0');
```

```sql
START TRANSACTION READ WRITE;

SELECT 1/COUNT(*)
FROM org
WHERE name = 'org0'
  AND owner = 'wrong user';

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
    SET full_name = EXCLUDED.full_name,
        org       = EXCLUDED.org
RETURNING id;

COMMIT;
```

On Tue, Sep 23, 2025 at 7:25 PM Juan Rodrigo Alejandro Burgos Mella
<rodrigoburgosmella@gmail.com <mailto:rodrigoburgosmella@gmail.com>>
wrote:

Hi Samuel

Using ON CONFLICT is a headache.
It's better to use the versatility of a Trigger: you have the

full record at your fingertips, and if you're going to UPDATE, you
have the previous record too.

There's much more control.

Also, you can always count on the beloved foreign keys, which are

also quite useful.

Atte.
JRBM

El mar, 23 sept 2025 a las 15:37, Samuel Marks

(<samuelmarks@gmail.com <mailto:samuelmarks@gmail.com>>) escribió:

Attempt:
```sql
CREATE TABLE org
(
     "name"      VARCHAR(50) PRIMARY KEY,
     owner       VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
     "id"           INTEGER PRIMARY KEY,
     full_name      VARCHAR(255) UNIQUE NOT NULL,
     org            VARCHAR(50)         NOT NULL REFERENCES org

("name")

);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
     SET full_name = EXCLUDED.full_name,
         org       = EXCLUDED.org
WHERE EXISTS (SELECT 1
               FROM org org_tbl
               WHERE org_tbl.name <http://org_tbl.name&gt; =

EXCLUDED.org

                 AND org_tbl.owner = 'wrong user')
RETURNING *;

SELECT * FROM repo WHERE id = 0;
```

This all succeeds. It should fail because the 'wrong user' is trying
to create a new—or update an existing—repo.

Thanks for all suggestions

--
Adrian Klaver
adrian.klaver@aklaver.com

#13David G. Johnston
david.g.johnston@gmail.com
In reply to: Adrian Klaver (#12)
Re: How do I upsert depending on a second table?

This thread is annoyingly full of replies that do not follow the
established conventions for making threads like this readable online and in
the archive.

Please:
1. Avoid top-posting and instead include your replies inline (or, at worse,
at the end)
2. Remove content not relevant to your immediate reply.
3. After you’ve made your last inline comment REMOVE all subsequent
content. This is just a special case of point 2 but this last message
makes it extremely obvious just how obnoxious leaving trailing off-topic
content is.

David J.

#14Samuel Marks
samuelmarks@gmail.com
In reply to: Adrian Klaver (#12)
Re: How do I upsert depending on a second table?

On Wed, Sep 24, 2025 at 10:13 AM Adrian Klaver <adrian.klaver@aklaver.com>
wrote:

On 9/23/25 23:44, Juan Rodrigo Alejandro Burgos Mella wrote:

The insert works because there is no data in the repo table that
conflicts with the entered full name.

Except this part:

SELECT 1/COUNT(*)
FROM org
WHERE name = 'org0'
AND owner = 'wrong user';

will cause a divide by 0 error and abort the transaction preventing the
INSERT from happening.

Example:

test=# begin ;
BEGIN
test=*# select 1/0;
ERROR: division by zero
test=!# select 1;
ERROR: current transaction is aborted, commands ignored until end of
transaction block
test=!# rollback ;
ROLLBACK

Yes but it's meant to divide by zero. That cancels the whole transaction
stopping it from going through. It being a transaction lets me guarantee
that at point of update or insert [upsert] the org owner matches the
requestor.

I would preference a single statement (one semicolon) solution; but for now
at least this works 🤷

Show quoted text

El mar, 23 sept 2025, 23:19, Samuel Marks <samuelmarks@gmail.com
<mailto:samuelmarks@gmail.com>> escribió:

Ok so you're thinking I give up on putting it all in one query and
instead use a transaction? - Is that the recommended way?

```sql
TRUNCATE repo, org;
INSERT INTO org(name, owner) VALUES ('org0', 'user0');
```

```sql
START TRANSACTION READ WRITE;

SELECT 1/COUNT(*)
FROM org
WHERE name = 'org0'
AND owner = 'wrong user';

INSERT INTO repo (id, full_name, org)
VALUES (0, 'org0/name0 by wrong user', 'org0')
ON CONFLICT (full_name) DO UPDATE
SET full_name = EXCLUDED.full_name,
org = EXCLUDED.org
RETURNING id;

COMMIT;
```

#15Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Samuel Marks (#14)
Re: How do I upsert depending on a second table?

On 9/24/25 10:02, Samuel Marks wrote:

On Wed, Sep 24, 2025 at 10:13 AM Adrian Klaver
<adrian.klaver@aklaver.com <mailto:adrian.klaver@aklaver.com>> wrote:

Yes but it's meant to divide by zero. That cancels the whole transaction
stopping it from going through. It being a transaction lets me guarantee
that at point of update or insert [upsert] the org owner matches the
requestor.

My reply was to Juan Rodrigo Alejandro Burgos Mella referencing the comment:

"The insert works because there is no data in the repo table that
conflicts with the entered full name. "

I was pointing out that in your second example the INSERT would not
happen as the org table does not have a row:

name owner
org0 wrong_user

So the SELECT 1/COUNT(*) [...] would result in a divide by 0 error and
the transaction would abort. Therefore ON CONFLICT (full_name) DO UPDATE
does not apply as the INSERT never happens.

I should have added previously this only applies for the 'wrong user'
case. For cases where the correct name/owner exists in the org table
then the INSERT and it's ON CONFLICT come into play and what happens
then is dependent on whether there is an existing row in the repo with
the same full_name or not. The issue I see is that the full_name is
UNIQUE across all orgs and I not sure that is good idea. It would seem
to me UNIQUE(org, full_name) would be better.

I would preference a single statement (one semicolon) solution; but for
now at least this works 🤷

--
Adrian Klaver
adrian.klaver@aklaver.com

#16Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Samuel Marks (#14)
Re: How do I upsert depending on a second table?

On 9/24/25 10:02, Samuel Marks wrote:

On Wed, Sep 24, 2025 at 10:13 AM Adrian Klaver

Yes but it's meant to divide by zero. That cancels the whole transaction
stopping it from going through. It being a transaction lets me guarantee
that at point of update or insert [upsert] the org owner matches the
requestor.

I would preference a single statement (one semicolon) solution; but for
now at least this works 🤷

I don't have enough experience with below to come up with an off the top
of my head examples, but they look like they may offer alternatives.

MERGE:

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

and/or Row level Security:

https://www.postgresql.org/docs/current/ddl-rowsecurity.html

In above link see example that starts below the phrase:

"... If it is necessary to consult other rows or other tables to make a
policy decision, that can be accomplished using sub-SELECTs, or
functions that contain SELECTs, in the policy expressions. ... "

--
Adrian Klaver
adrian.klaver@aklaver.com

#17Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Adrian Klaver (#16)
Re: How do I upsert depending on a second table?

On 9/24/25 16:03, Adrian Klaver wrote:

On 9/24/25 10:02, Samuel Marks wrote:

On Wed, Sep 24, 2025 at 10:13 AM Adrian Klaver

I don't have enough experience with below to come up with an off the top
of my head examples, but they look like they may offer alternatives.

MERGE:

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

First time working with MERGE, so approach the below with caution:

CREATE TABLE org
(
"name" VARCHAR(50) PRIMARY KEY,
owner VARCHAR(50) NOT NULL
);

CREATE TABLE repo
(
"id" INTEGER PRIMARY KEY,
full_name VARCHAR(255) UNIQUE NOT NULL,
org VARCHAR(50) NOT NULL REFERENCES org ("name")
);

INSERT INTO org(name, owner) VALUES ('org0', 'user0');

WITH t AS (SELECT
*
FROM
org
RIGHT JOIN
(values(0 , 'org0/name0 by wrong user', 'org0', 'wrong_user'))
AS v(id, full_name, org, user_name)
ON
org.name = v.org
AND
org.owner = v.user_name
)
MERGE INTO repo as r
USING t
ON
r.org = t.name

WHEN MATCHED AND t.id = r.id THEN
UPDATE SET (id, full_name, org) = (t.id, t.full_name, t.org)
WHEN NOT MATCHED AND t.user_name = COALESCE(t.owner, '') THEN
INSERT VALUES(t.id, t.full_name, t.org)

RETURNING r.*;

id | full_name | org
----+-----------+-----
(0 rows)

MERGE 0

select * from repo ;
id | full_name | org
----+-----------+-----

WITH t AS (SELECT
*
FROM
org
RIGHT JOIN
(values(0 , 'org0/name0 by right user', 'org0', 'user0'))
AS v(id, full_name, org, user_name)
ON
org.name = v.org
AND
org.owner = v.user_name
)
MERGE INTO repo as r
USING t
ON
r.org = t.name

WHEN MATCHED AND t.id = r.id THEN
UPDATE SET (id, full_name, org) = (t.id, t.full_name, t.org)
WHEN NOT MATCHED AND t.user_name = COALESCE(t.owner, '') THEN
INSERT VALUES(t.id, t.full_name, t.org)

RETURNING r.*;
id | full_name | org
----+--------------------------+------
0 | org0/name0 by right user | org0
(1 row)

MERGE 1

select * from repo ;
id | full_name | org
----+--------------------------+------
0 | org0/name0 by right user | org0

WITH t AS (SELECT
*
FROM
org
RIGHT JOIN
(values(0 , 'org0/name0 by right user update', 'org0', 'user0'))
AS v(id, full_name, org, user_name)
ON
org.name = v.org
AND
org.owner = v.user_name
)
MERGE INTO repo as r
USING t
ON
r.org = t.name

WHEN MATCHED AND t.id = r.id THEN
UPDATE SET (id, full_name, org) = (t.id, t.full_name, t.org)
WHEN NOT MATCHED AND t.user_name = COALESCE(t.owner, '') THEN
INSERT VALUES(t.id, t.full_name, t.org)

RETURNING r.*;
id | full_name | org
----+---------------------------------+------
0 | org0/name0 by right user update | org0
(1 row)

select * from repo ;
id | full_name | org
----+---------------------------------+------
0 | org0/name0 by right user update | org0
(1 row)

--
Adrian Klaver
adrian.klaver@aklaver.com