PostgreSQL Upsert With Multiple Constraints - postgresql

I'm trying to do an upsert on a table with two constraints. One is that the column a is unique, the other is that the columns b, c, d and e are unique together. What I don't want is that a, b, c, d and e are unique together, because that would allow two rows having the same value in column a.
The following fails if the second constraint (unique b, c, d, e) is violated:
INSERT INTO my_table (a, b, c, d, e, f, g)
SELECT (a, b, c, d, e, f, g)
FROM my_temp_table temp
ON CONFLICT (a) DO UPDATE SET
a=EXCLUDED.a,
b=EXCLUDED.b,
c=EXCLUDED.c,
d=EXCLUDED.d,
e=EXCLUDED.e,
f=EXCLUDED.f,
g=EXCLUDED.g;
The following fails if the first constraint (unique a) is violated:
INSERT INTO my_table (a, b, c, d, e, f, g)
SELECT (a, b, c, d, e, f, g)
FROM my_temp_table temp
ON CONFLICT ON CONSTRAINT my_table_unique_together_b_c_d_e DO UPDATE SET
a=EXCLUDED.a,
b=EXCLUDED.b,
c=EXCLUDED.c,
d=EXCLUDED.d,
e=EXCLUDED.e,
f=EXCLUDED.f,
g=EXCLUDED.g;
How can I bring those two together? I first tried to define a constraint that says "either a is unique or b, c, d and e are unique together" but it looks like that isn't possible. I then tried two INSERT statements with WHERE clauses making sure that the other constraint doesn't get violated, but there is a third case where a row might violate both constraints at the same time. To handle the last case I considered dropping one of the constraints and creating it after the INSERT, but isn't there a better way to do this?
I tried this, but according to the PostgreSQL documentation it can only DO NOTHING:
INSERT INTO my_table (a, b, c, d, e, f, g)
SELECT (a, b, c, d, e, f, g)
FROM my_temp_table temp
ON CONFLICT DO UPDATE SET
a=EXCLUDED.a,
b=EXCLUDED.b,
c=EXCLUDED.c,
d=EXCLUDED.d,
e=EXCLUDED.e,
f=EXCLUDED.f,
g=EXCLUDED.g;
I read in another question that it might work using MERGE in PostgreSQL 15 but sadly it's not available on AWS RDS yet. I need to find a way to do this using PostgreSQL 14.

I think what you need is a somewhat different design. I suppose "a" is a surrogate key and b,c,d,e,f,g make up the natural key. And I suppose there are other columns, that are the data.
So force column "a" to be automatically generated, like this:
CREATE TEMP TABLE my_table(
a bigint GENERATED ALWAYS AS IDENTITY,
b bigint NOT NULL,
c bigint NOT NULL,
d bigint NOT NULL,
e bigint NOT NULL,
f bigint NOT NULL,
g bigint NOT NULL,
data text,
CONSTRAINT my_table_unique_together_b_c_d_e UNIQUE (b,c,d,e,f,g)
);
And then just skip the a column from your insert:
INSERT INTO my_table (b, c, d, e, f, g)
SELECT (b, c, d, e, f, g)
FROM my_temp_table temp
ON CONFLICT ON CONSTRAINT my_table_unique_together_b_c_d_e DO UPDATE SET
data=EXCLUDED.data;

Related

Can I only select the values if it is not null?

I've got values stored in variables that I only want inserted if it doesn't return null - possible to do this with SQL? I've tried the below:
SELECT concat_ws(A, B, C, D, E) is not null;
So I think it's still returning (A, null, C, D, null) into variable2 where ideally I'd like it just to return (A, C, D) into variable2 instead. (Have also tried with concat, coalesce). Thanks!
I do not know about the selection into variables, but this should do what you ask if A through E are columns:
select array_to_string(array_remove(array[a, b, c, d, e], null), ',') as variable1
from testme;
db<>fiddle here

Is there a way to return all non-null values even if one is null in PostgreSQL?

The statement I've got is
select coalesce(A||','||B||','||C) into D
If C is null, then D will = < NULL >, but if I remove C and just have A and B, then D will = the values of A & B. There may be situations where any one of those is NULL and I still want to return any non-null values into D - is this possible? Thanks!
Use concat_ws() instead:
select concat_ws(',', a, b, c)

How do I count the number of unique pairs in Postgres 9.6?

I want to calculate the number of unique pairs. I am trying the query:
select count(distinct a, b) from t
But it gives the error:
ERROR: function count(integer, integer) does not exist
How can I resolve this issue?
The syntax for tuples is (a, b). thus your query should be
select count (distinct (a, b)) ab_count from t

How to use multiple "unique index inferences" in a Postgresql query

In a Postgresql 9.6 DB, there is a existing table named X that has four columns, a, b, c, and d with indices setup like this:
"uidx_a_b" UNIQUE, btree (a, b) WHERE c IS NULL AND d IS NULL
"uidx_a_c" UNIQUE, btree (a, b, c) WHERE c IS NOT NULL AND d IS NULL
"uidx_a_d" UNIQUE, btree (a, b, c, d) WHERE c IS NOT NULL AND d IS NOT NULL
I don't know why this was done as it was done by someone long gone and before I had to modify it.
I am trying to get the syntax correct for specifying all three of these in an ON CONFLICT statement. I tried every variation I could think of all with error. The Postgresql Documentation indicates this is possible, specifically the [, ...] described in the conflict_target here:
( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] )
Also, this blog from one of the committers says so. Finally, I looked at this unit test for the functionality again to no avail! Having thus given up, I am turning to SO to seek help.
This is what I believe is the closest syntax I tried that should work:
ON CONFLICT (
((a, b) WHERE c IS NULL AND d IS NULL),
((a, b, c) WHERE c IS NOT NULL AND d IS NULL),
((a, b, c, d) WHERE release_id IS NOT NULL AND d IS NOT NULL)
)
However this fails with:
ERROR: syntax error at or near ","
While I am open to suggestions to improve the design of those indices, I really want to know if there is a valid syntax for specifying the ON CONFLICT clause as it seems there should be!
In the following I am referring to the syntax description from the documentation:
ON CONFLICT [ conflict_target ] conflict_action
where conflict_target can be one of:
( { index_column_name | ( index_expression ) }
[ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ]
ON CONSTRAINT constraint_name
INSERT ... ON CONFLICT allows only a single conflict_target.
The [, ...] means that more than one column or expression can be specified (to indicate a single condition), like this:
ON CONFLICT (col1, (col2::text), col3)
Moreover, if it is a partial index, the WHERE condition must be implied by index_predicate.
So what can you do?
You can follow the advice from #joop and find a value that cannot occur in columns c and d.
Then you can replace your three indexes with:
CREATE UNIQUE INDEX ON x (a, b, coalesce(c, -1), coalesce(d, -1));
The conflict_target would then become:
ON CONFLICT (a, b, coalesce(c, -1), coalesce(d, -1))

Phantom-Cassandra Insert/update behaviour

I am trying to link up two tables with the same data like over here:
http://outworkers.com/blog/post/a-series-on-cassandra-part-1-getting-rid-of-the-sql-mentality
My second table contains the data I want to query by for example:
foo (
id text,
time timestamp,
a int,
b int,
c int,
d int,
PRIMARY KEY (time, id)
) WITH CLUSTERING ORDER BY (time DESC, id ASC)
So here I want to query by timestamp or id.
Now a,b,c,d are items which should be unique, i.e., the PRIMARY KEY(a, b, c, d). For this I create the first table:
bar (
id text,
time timestamp,
a int,
b int,
c int,
d int,
PRIMARY KEY (a, b, c, d)
)
The thing is, during the insert, id and time might change but a, b, c, d will remain the same.
Now, I was hoping to do something along the lines of that consistency thing mentioned in the blog post. The problem I am facing is that if I try to insert an item with the same (a, b, c, d), bar happily updates the corresponding row but foo creates a new entry. How would I go about deleting the older entry in foo or updating foo like bar???
According to Cassandra documentation:
UPDATE cannot update the values of a row's primary key fields
And here is example for phantom delete query: https://github.com/outworkers/phantom/wiki/Querying#delete-queries