Conditional composite key in MySQL? - sql

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');

Related

Unique constraint on inserting new row

I wrote a SQL statement within PostgreSQL 12 and I first created an unique constraint like:
CONSTRAINT post_comment_response_approval__tm_response__uidx UNIQUE (post_comment_response_id, team_member_id)
On a SQL query:
INSERT INTO post_comment_response_approval (post_comment_response_id, team_member_id, approved, note)
VALUES (:postCommentResponseId, :workspaceMemberId, :approved, :note)
ON CONFLICT ON CONSTRAINT post_comment_response_approval__tm_response__uidx DO
UPDATE SET approved = :approved, note = :note
Fist, I wanted to use it for the same row whenever ever some action is made, but now I just want to make sure the API shows them if multiple actions have been submitted by the same member.
An example is that someone might suggest a change, then that change is made, then that person who suggested it later approves it. That would generate multiple post_comment_response_approval rows for that approver.
Is there a way to make it happen without removing unique constraint or maybe it should be deleted? I am new with PostgreSQL.
I didn't understand your question in detail. But I think I understood what you need. You can use PostgreSQL partial indexing.
Examples for you:
CREATE TABLE table6 (
id int4 NOT NULL,
id2 int4 NOT null,
approve bool NULL
);
-- creating partial indexing
CREATE UNIQUE INDEX table6_id_idx ON table6 (id,id2) where approve is true;
insert into table6 (id, id2, approve) values (1, 1, false);
-- success insert
insert into table6 (id, id2, approve) values (1, 1, false);
-- success insert
insert into table6 (id, id2, approve) values (1, 1, false);
-- success insert
insert into table6 (id, id2, approve) values (1, 1, true);
-- success insert
insert into table6 (id, id2, approve) values (1, 1, true);
-- error: duplicate key value violates unique constraint "table6_id_idx"
So, you get unique fields by condition.

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;

How auto increment id and insert 2 rows

I have two table with one to one relation and I want to insert two rows to the tables with the same auto increment id. Is it possible?
create table first
(
id bigint primary key,
value varchar(100) not null
);
create table second
(
id bigint references first (id),
sign boolean
);
insert into first(id, value)
values (-- autoincremented, 'some_value');
insert into second(id, sign)
values (-- the same autoincremented, true);
Your id column must be defined as an "auto increment" one before you can use that:
create table first
(
id bigint generated always as identity primary key,
value varchar(100) not null
);
Then you can use lastval() to get the last generated id:
insert into first(id, value)
values (default, 'some_value');
insert into second(id, sign)
values (lastval(), true);
Or if you want to be explicit:
insert into first(id, value)
values (default, 'some_value');
insert into second(id, sign)
values (currval(pg_get_serial_sequence('first','id')), true);
One option uses a cte with the returning clause:
with i as (
insert into first(value) values('some_value')
returning id
)
insert into second(id, sign)
select i.id, true from i
This performs the two inserts at once; the id of the first insert is auto-generated, and then used in the second insert.
For this to work, you need the id of the first table to be defined as serial.

Simulating UPSERT in presence of UNIQUE constraints

Simulating UPSERT was already discusssed before. In my case though, I have PRIMARY KEY and additional UNIQUE constraint, and I want upsert semantic with respect to primary key - replacing existing row if it exists, while checking the unique constraint.
Here's an attempt using insert-or-replace:
drop table if exists test;
create table test (id INTEGER, name TEXT, s INTEGER,
PRIMARY KEY (id, s),
UNIQUE (name, s));
insert or replace into test values (1, "a", 0);
insert or replace into test values (1, "a", 0);
insert or replace into test values (2, "b", 0);
insert or replace into test values (2, "a", 0);
The last statement is replaces both rows. This is documented behavior of 'insert or replace', but not what I want.
Here is an attempt with "on conflict replace":
drop table if exists test;
create table test (id INTEGER, name TEXT, s INTEGER,
PRIMARY KEY (id, s) on conflict replace,
UNIQUE (name, s));
insert into test values (1, "a", 0);
insert into test values (1, "a", 0);
I get "UNIQUE constraint failed" right away. The problem disappears if don't share column between both primary key and unique constraint:
drop table if exists test;
create table test (id INTEGER, name TEXT,
PRIMARY KEY (id) on conflict replace,
UNIQUE (name));
insert into test values (1, "a");
insert into test values (1, "a");
insert into test values (2, "b");
insert into test values (2, "a");
Here, I get constraint violation on the very last statement, which is precisely right. Sadly, I do need to share a column between constraints.
Is this something I don't understand about SQL, or SQLite issue, and how do I get the desired effect, except for first trying insert and then doing update on failure?
Can you try to apply the ON CONFLICT REPLACE clause to the UNIQUE constraint also?
create table test (id INTEGER, name TEXT,
PRIMARY KEY (id) on conflict replace,
UNIQUE (name) on conflict replace);
SQLite is an embedded database without client/server communication overhead, so it is not necessary to try to do this in a single statement.
To simulate UPSERT, just execute the UPDATE/INSERT statements separately:
c.execute("UPDATE test SET s = ? WHERE id = ? AND name = ?", [0, 1, "a"])
if c.rowcount == 0:
c.execute("INSERT INTO test(s, id, name) VALUES (?, ?, ?)", [0, 1, "a"])
Since SQLite 3.24.0, you can just use UPSERT.

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.