POSTGRES - handle several unique indexes differently in ON CONFLICT clause - sql

I have a table with several unique indexes. Is it possible to ON CONFLICT UPDATE only on one of them and DO NOTHING on all others, handling with DO NOTHING future unique indexes without going back and modifying code or at least existing ones. Ex:
ON CONFLICT (name, age)
DO UPDATE SET
occupation = 'teacher'
ELSE ON CONFLICT(all others) DO NOTHING;
Right now, of course, it throws an error for all other indexes besides 1st one. Or alternatively what is the best way to handle it so no errors are thrown. Thank you.
CREATE UNIQUE INDEX index1 ON contact USING btree (name, age);
CREATE UNIQUE INDEX index2 ON contact USING btree (name, address) WHERE (address IS NOT NULL);
CREATE UNIQUE INDEX index3 ...
CREATE UNIQUE INDEX index4 ...
INSERT INTO contact
(name,
age,
address,
occupation)
VALUES
('John',
25,
'1 main st',
'doctor')
ON CONFLICT (name, age)
DO UPDATE SET
occupation = 'teacher';

How about just avoiding the insert? It's a bit verbose and will need to be kept up to date with your unique constraints, but at least it shouldn't error.
INSERT INTO contact
(name,
age,
address,
occupation)
SELECT
'John',
25,
'1 main st',
'doctor'
WHERE NOT EXISTS (
SELECT 1
FROM contact WHERE
(name, address) = ('John', '1 main st')
OR (... next unique constraint...)
)
ON CONFLICT (name, age)
DO UPDATE SET
occupation = 'teacher';

Related

CONTAINS cannot be used in a JOIN clause when using a FREE TEXT Index?

I am looking at tables like this (I did not design them):
--drop table person
CREATE TABLE EventIDsToSearch (id int not null)
insert into EventIDsToSearch values (1)
insert into EventIDsToSearch values (92)
insert into EventIDsToSearch values (106)
--etc
CREATE TABLE Person (ID INT IDENTITY NOT NULL, [type] int, Notes nvarchar(1000), CONSTRAINT [PersonPK] PRIMARY KEY CLUSTERED (ID,[TYPE]))
insert into Person ([type], notes) values (1, 'This person is linked to Event ID 92')
insert into Person ([type], notes) values (2, 'Look at ID 67!')
insert into Person ([type], notes) values (3, 'ID 87(3/10/15)')
insert into Person ([type], notes) values (4, '!!!187(this is the event id)')--Notice the exclamation marks at the start - I don't believe WHERE CONTAINS (Notes,67) would find this.
--etc
I have tried this:
CREATE UNIQUE INDEX PersonIndex ON Person(ID);
CREATE FULLTEXT CATALOG ft AS DEFAULT;
CREATE FULLTEXT INDEX ON Person(Notes)
KEY INDEX PersonIndex
WITH STOPLIST = SYSTEM;
and I can find all the IDs except 187. For example this finds one row:
SELECT *
FROM Person
WHERE CONTAINS(Notes,'92') ;
but this does not:
SELECT *
FROM Person
WHERE CONTAINS(Notes,'187') ;
I don't think a free text index is suitable for me here because:
Entity.Notes sometimes contains characters e.g. exclamation marks before the ID.
EventIDsToSearch contains millions of rows and it would be too slow to loop through them in a cursor. This question tells me that a cursor is the only option i.e. EventIDsToSearch and Person cannot be joined by a CONTAINS clause: Using JOIN statement with CONTAINS function
I am using SQL Server 2019.
Have I understood this correctly?

INSERT unless date is the same

Is it possible (in Postgres) to do the following 2 INSERTs, or something that's logically equivalent (the proposed INSERTs don't work as they are, but maybe they can be slightly modified?):
CREATE TABLE IF NOT EXISTS table01(
userid int8 NOT NULL,
save date NOT NULL,
followers int4
);
CREATE UNIQUE INDEX ON table01 (userid,save);
INSERT 0:
INSERT INTO table01 (userid,save,followers) VALUES (%s,%s,%s)
ON CONFLICT (userid) DO INSERT INTO table01 (userid,save,followers) VALUES (%s,%s,%s)
WHERE table01.save!=save;
INSERT 1:
INSERT INTO table01 (userid,save,followers) VALUES (%s,%s,%s) WHERE table01.save!=save;
The logic is:
Try to insert a row
If there's a conflict of userid, then insert the row anyway UNLESS the date (save) is the same
Summary:
Are the 2 shown INSERTs (or something equivalent) possible?
Is it possible to do ON CONFLICT DO INSERT (just like one does ON CONFLICT DO UPDATE)?
Is it possible to do INSERT INTO WHERE (just like one does SELECT FROM WHERE)?
A simple insert would seem to do what you want:
INSERT INTO table01 (userid, save, followers)
VALUES (%s, %s, %s);
This will insert a new row unless the userid/save pair is already there. In that case, it would generate an error. If you don't want an error, you can use on conflict do nothing:
INSERT INTO table01 (userid, save, followers)
VALUES (%s, %s, %s)
ON CONFLICT (userid, save) DO NOTHING;

T-SQL constraint to check if value is either 0 OR unique

I'm designing a DB where employees need to be activated by an Admin. When a new employee registers, their workstation (int) gets set to 0 by default. However, I also can't allow to have more than 1 employee per workstation. So I was thinking if there's a way to allow for duplicates of the value 0, but enforce that any other number be unique.
You can use a partial unique index. For example:
create unique index ix1 on employees (workstation) where workstation <> 0;
As in:
create table employees (
id int,
name varchar(10),
workstation int not null
);
create unique index ix1 on employees (workstation) where workstation <> 0;
insert into employees (id, name, workstation) values (1, 'Anne', 100);
insert into employees (id, name, workstation) values (2, 'Peter', 101);
insert into employees (id, name, workstation) values (3, 'Joel', 100); -- fails
insert into employees (id, name, workstation) values (4, 'Niko', 0);
insert into employees (id, name, workstation) values (5, 'Akina', 0); -- succeeds
This is one of the constraints that you cannot enforce using traditional constraints.
See running example at db<>fiddle. As you see only Joel, 100 is rejected. The other two cases with workstation = 0 are inserted.

Can a postgres check *conditionally* restrict duplicates?

I have to implement a rule that only one 'group' with a given ID can have status 'In Progress' or 'Problem' at one time. Is it possible to represent this in a Postgres check, or would I have to resort to logic in the application?
For example:
INSERT INTO group (group_id, status) VALUES (1, 'In Progress'); -- okay
INSERT INTO group (group_id, status) VALUES (2, 'Problem'); -- okay
INSERT INTO group (group_id, status) VALUES (3, 'In Progress'); -- okay
INSERT INTO group (group_id, status) VALUES (4, 'Problem'); -- okay
INSERT INTO group (group_id, status) VALUES (1, 'Something else'); -- okay
INSERT INTO group (group_id, status) VALUES (2, 'Foo bar'); -- okay
INSERT INTO group (group_id, status) VALUES (1, 'In Progress'); -- should fail
INSERT INTO group (group_id, status) VALUES (1, 'Problem'); -- should fail
INSERT INTO group (group_id, status) VALUES (2, 'In Progress'); -- should fail
INSERT INTO group (group_id, status) VALUES (2, 'Problem'); -- should fail
I think you need a partial unique index.
CREATE UNIQUE INDEX group_status_unq_idx
ON "group"(group_id, status) WHERE (status IN ('In Progress', 'Problem'));
However, it isn't clear to me from your description why the second expected-failure would fail. Do you want to allow only one of In Progress or Problem for any given group_id? If so, you could write something like:
CREATE UNIQUE INDEX group_status_unq_idx
ON "group"(group_id) WHERE (status IN ('In Progress', 'Problem'));
... omitting the status from the partial unique index and using it only for the predicate. See this SQLFiddle.
Note that this cannot be expressed as a UNIQUE constraint, since unique constraints do not take a predicate. A PostgreSQL unique constraint is implemented using a (non-partial) UNIQUE index, but it also creates metadata entries that the mere index creation does not. A partial unique index works like a unique constraint with a predicate, but it won't be discoverable via metadata like information_schema's constraint info.
If I understand correctly:
create unique index group_restriction_index on group
(status) where status in ('In Progress', 'Problem')
You can make a unique constraint on multiple columns:
CREATE TABLE group (
group_id integer,
status char(100),
UNIQUE (group_id, status)
);
(From the Postgresql documentation).
This would prevent duplicates for any status, though, not just 'In Progress' and 'Problem'. I'm not sure if that is what you want.

Conditional composite key in MySQL?

So I have this table with a composite key, basically 'userID'-'data' must be unique (see my other question SQL table - semi-unique row?)
However, I was wondering if it was possible to make this only come into effect when userID is not zero? By that I mean, 'userID'-'data' must be unique for non-zero userIDs?
Or am I barking up the wrong tree?
Thanks
Mala
SQL constraints apply to every row in the table. You can't make them conditional based on certain data values.
However, if you could use NULL instead of zero, you can get around the unique constraint. A unique constraint allows multiple entries that have NULL. The reason is that uniqueness means no two equal values can exist. Equality means value1 = value2 must be true. But in SQL, NULL = NULL is unknown, not true.
CREATE TABLE MyTable (id SERIAL PRIMARY KEY, userid INT, data VARCHAR(64));
INSERT INTO MyTable (userid, data) VALUES ( 1, 'foo');
INSERT INTO MyTable (userid, data) VALUES ( 1, 'bar');
INSERT INTO MyTable (userid, data) VALUES (NULL, 'baz');
So far so good, now you might think the following statements would violate the unique constraint, but they don't:
INSERT INTO MyTable (userid, data) VALUES ( 1, 'baz');
INSERT INTO MyTable (userid, data) VALUES (NULL, 'foo');
INSERT INTO MyTable (userid, data) VALUES (NULL, 'baz');
INSERT INTO MyTable (userid, data) VALUES (NULL, 'baz');