duplicate key value violates unique constraint with on conflict is not working - sql

I am trying to use on conflict with unique on multiple fields. I have this structure.
|---------------------|------------------|
| id | uuid |
|---------------------|------------------|
| name | string |
|---------------------|------------------|
| field_a | uuid |
|---------------------|------------------|
| field_b | uuid |
|---------------------|------------------|
| field_c | uuid |
|---------------------|------------------|
field_a,field_b,field_c are unique and field_b can be NULL.
This is my query:
INSERT INTO table (field_a, field_b,field_c, name)
values ('434d1d67-df03-4310-b3eb-93bf1c6e319e',
'd3a3745e-ad97-4fcd-1fed-26bb406dc265',
'd5a4232e-ad56-6ecd-5fed-25bb106dc114')
on conflict(field_a,field_b,field_c)
do update
set name = 'abc'
If I try this with same query again it works. It updates on conflict. But when I use null like this:
INSERT INTO
table (field_a, field_b,field_c, name)
values ('434d1d67-df03-4310-b3eb-93bf1c6e319e',
null,
'd5a4232e-ad56-6ecd-5fed-25bb106dc114')
on conflict(field_a,field_b,field_c)
do update
set name = 'abc'
This does not work. This will add new row in my table. To prevent adding new row I created an index and set NULL values like this
CREATE
UNIQUE INDEX uidx_uniq ON table USING btree (
(COALESCE(field_a, '00000000-0000-0000-0000-000000000000'::uuid)),
(COALESCE(field_a, '00000000-0000-0000-0000-000000000000'::uuid)),
(COALESCE(field_a, '00000000-0000-0000-0000-000000000000'::uuid)))
This does not allow adding new value in db if any exists with null but on conflict does not work with this it gives me Error:
duplicate key value violates unique constraint "uidx_uniq"
How can I resolve this with null?

As the documentation says:
Null values are not considered equal.
So there is no conflict if one of the values is NULL.
You cannot use the unique index you created with the ON CONFLICT clause, because you can only use unique constraints there. Unique constraints cannot be defined on expressions, only on columns.
Perhaps you should use a different value that NULL so model what you mean. NULL means “unknown” in SQL, so PostgreSQL's interpretation makes sense.

I think you also want a filtered unique index:
CREATE UNIQUE INDEX uidx_uniq2 ON table (field_a, field_c)
WHERE field_b IS NULL;
You'll need to check both indexes for conflicts in ON CONFLICT.

Related

SQL : unique values across two columns

Is there a constraint for values not being unique taking in consideration two columns, ex -
id | secondid
+---------------+
3 | 4
4 | 5
id | secondid
+---------------+
3 | 4
5 | 4
id | secondid
+---------------+
4 | 4
4 | 4
All the above cases are not okay, as 4 occurs twice in either id or secondid but something like
id | secondid
+---------------+
1 | 3
2 | 4
is okay as all the values in both the columns are unique, is there any way for me to achieve this without using any packages in postgresql?
You can do this with a combination of an exclusion constraint and a check constraint. The check constraint is needed to prevent duplicates within one row.
create table t (
id int,
id2 int,
check (id <> id2),
exclude using gist ( (array[id, id2]) with &&)
);
The exclusion constraint operates by checking the specified operator never returns "true" for the column in the "new" row and all rows already in the table. It does not check values within the current row, which is why the check constraint is also needed.
Here is a db<>fiddle.
You want a unique constraint that works on the two columns as if these were just one column. I think this is not possible directly. (Others may correct me.)
What you can do is create another table
create table check_unique_id (id int primary key);
and fill it via a trigger. I.e. every time you insert a row in your table, the trigger creates two rows in the check_unique_id table. If an ID occurs twice that other table will raise the exception.

Primary key of a simple 1:1-mapping table with NULL values?

This feels like a very basic question, but I really don't see the obvious answer at the moment.
I have a simple table that maps object ids between two namespaces:
|---------------------|------------------|
| id_in_ns1 | id_in_ns2 |
|---------------------|------------------|
| 1 | 5 |
|---------------------|------------------|
| 2 | 17 |
|---------------------|------------------|
| 3 | NULL |
|---------------------|------------------|
| NULL | 1 |
|---------------------|------------------|
The mapping is basically 1:1, but as you can see, some objects from namespace 1 do not exist in namespace 2, and vice versa, so that there are NULL values in the table.
So, what would be the primary key of this table? As a PK cannot be NULL, I can neither use (id_in_ns1) nor (id_in_ns2) nor the composite.
The only idea I have is to replace NULL by a definite value, say -1, and to use (id_in_ns1, id_in_ns2)as PK. However, this feels not only hackish but also "unnormal" because the non-NULL (or non--1)) value alone is already sufficient to uniquely identify an object.
Only add entries that have a valid id on both sides. This will effectively get rid of all NULL values, allowing you to specify a proper composite key on (id_in_ns1, id_in_ns2).
Ultimately, those are the values that allow you to identify a single row and you will not lose relevant information - a SELECT id_in_ns2 FROM mapping_table WHERE id_in_ns1 = x will return NULL either way, whether there is a (x, NULL) row or not.
If you insist on keeping those NULLs you could add another column with an artificial (auto incrementing) primary key, but that feels as hacky as using -1.
Use a synthetic primary key and use unique constraints for the rest:
create table mapping (
mappingId int auto_increment primary key, -- or whatever for your database
id_in_ns1 int references ns1(id),
id_in_ns2 int references ns2(id),
unique (id_in_ns1),
unique (id_in_ns2)
);
Just one caveat: some databases only allow one NULL value for UNIQUE constraints. You might need to use a filtered unique index instead (or some other mechanism) for this construct.

Row not created but increments primary key

I am attempting to insert new rows into the following PostgreSQL table:
Table "public.users"
Column | Type | Collation | Nullable | Default
---------------+--------------------------+-----------+----------+----------------------------------------------
user_id | integer | | not null | nextval('define_user_user_id_seq'::regclass)
time_created | timestamp with time zone | | not null |
is_active | boolean | | not null | true
email_address | text | | not null |
password_hash | character varying(255) | | not null |
first_name | text | | |
second_name | text | | |
Indexes:
"users_pkey" PRIMARY KEY, btree (user_id)
Referenced by:
TABLE "user_to_device" CONSTRAINT "user_to_device_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(user_id)
TABLE "user_to_horse" CONSTRAINT "user_to_horse_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(user_id)
The table currently only contains 10 records as it is still being used for development. There is no scope to modify the table.
My issue is that, when updating the table from a REST API the operation seemingly successfully and returns a new user_id; upon querying the table, the supposedly created user is not in the table.
If I then create a user manually (SSH'd into the server that's running psql) and use the exact same query then the operation is successful and the newly created user can be seen. Interestingly, the user_id value increments from the value created by the query triggered by the REST API.
This suggests to me that the query triggered via the REST API is successful (?) because the user_id that it creates seems to be recognised by subsequent queries - so why then does the new user not appear in the table?
No errors are thrown at all. Here's the query that I'm using to create a user:
INSERT INTO users (password_hash, is_active, first_name, email_address, second_name, time_created) VALUES ('mypasswordhash', True, 'Orson', 'user#example.com', 'Cart', '2018-11-23T12:23:00Z') RETURNING user_id;
I am using psycopg2 from within Python 3.6 when querying via the API. I have multiple other API endpoints that INSERT successfully into other tables so I'm not sure at all what the issue is. Any help is greatly appreciated as this has me truly stumped, thanks.
Are you absolutely sure
your commit function is called?
In some cases
if you yield or return
before committing,
the function is aborted
before your changes get committed.
In this case, I would expect to see
an incremented ID without an inserted row,
as primary keys get incremented
before the query is checked.
If your connection terminates abruptly,
the row won't get committed.
Your best bet would be to
examine your PostgreSQL server logs.

How to update rows of two tables that have foreign key restrictions

I have two tables: one is foreign reference table lets say table a and other one is the data table lets say table b.
Now, when I need to change the data in table b, but I get restricted by table a.
How can I change "rid" in both tables without getting this message?
"ERROR: insert or update on table "table a" violates foreign key
constraint "fk_boo_kid" SQL state: 23503
Detail: Key (kid)=(110) is not present in table "table b".
Example query to update both tables:
UPDATE table b table a SET rid = 110 WHERE rid =1
table b
+-----+-------+-------+
| rid | ride | qunta |
+-----+-------+-------+
| 1 | car | 1 |
| 2 | bike | 1 |
+-----+-------+-------+
table a
+-----+-----+------------+
| kid | rid | date |
+-----+-----+------------+
| 1 | 1 | 20-12-2015 |
| 2 | 2 | 20-12-2015 |
+-----+-----+------------+
In Postgres you can use a writeable CTE to update both tables in a single statement.
Assuming this table setup:
create table a (rid integer primary key, ride text, qunta integer);
create table b (kid integer primary key, rid integer references a, date date);
The CTE would be:
with new_a as (
update a
set rid = 110
where rid = 1
)
update b
set rid = 110
where rid = 1;
As (non-deferrable) foreign keys are evaluated on statement level and both the primary and foreign key are changed in the same statement, this works.
SQLFiddle: http://sqlfiddle.com/#!15/db6d1/1
you can not update/delete primary key in table B, because the primary key is used in table A.
you can delete primary key in table B, IF >>
you must delete the row in table A which is used primary key table B.
you can delete the row in table B
you have to change both manual
SET session_replication_role = 'replica';
UPDATE table a SET rid=110 WHERE rid=1 ;
UPDATE table b SET rid=110 WHERE rid=1 ;
SET session_replication_role = 'origin';
This is too long for a comment.
You should really explain why you want to change ids into something else. Primary keys really should be considered immutable, so they identify rows both within a table and over time.
If you do need to change them for some reason, then define proper foreign key constraints for the tables in question. Then define the foreign keys to be on update cascade. This will "cascade" changes to all affected changes when a primary key changes.

Inserting 2 rows, each to different tables where one row refrences the other's primary key

Hello folks
Checkout this scenario
Table 1 columns -> | table_1_id (pkey) | some_column | comments |
Table 2 columns -> | table_2_id (pkey) | some_other_column | table_1_id (fkey) | comments |
All primary keys are of type serial or auto number.
The 3rd column on Table 2 is an fk that references Table 1's primary key.
I would like to insert rows into both programmaticaly (from a c++ app)
Do i have to insert to table one then SELECT-query the entry's primary key then insert the Table 2 row with the pkey result?
Is there a more efficient way of handling this? Say using almost 2 queries?
I would suggest looking http://wiki.postgresql.org/wiki/FAQ
The site is a useful resource to go through to get familiar with PostgreSQL
Specifically, the section How do I get the value of a SERIAL insert?
The simplest way is to retrieve the
assigned SERIAL value with RETURNING.
Using the example table in the
previous question, it would look like
this:
INSERT INTO person (name) VALUES
('Blaise Pascal') RETURNING id;
You can also call nextval() and use that value in the INSERT, or call currval() after the INSERT.
If you don't need the table_1_id value in your application, you can skip retrieving it completely:
INSERT INTO table_1(cols...) VALUES(vals...)
INSERT INTO table_2(table_1_id, cols...) VALUES(currval('table_1_table_1_id_seq'), vals...)