postgres many to one unique constraint - postgresql

I'm curious if there is a way to write a unique constraint to support the following situation.
Suppose I have table table1 with facts about a user, with four columns:
user_id: unique id for user
source: where the detail came from
d1: dimension 1 of the fact
d2: dimension 2 of the fact
The following is an example of data in this table:
| row_id | user_id | source | d1 | d2 |
|--------|---------|--------|--------|---------|
| 1 | aaa111 | foo | bar | 123 |
| 2 | aaa111 | foo | baz | 'horse' |
| 3 | aaa111 | scrog | bar | 123 |
| 4 | bbb222 | foo | goober | 456 |
Currently, a unique constraint exists on source + d1 + d2. This is good, because it allows the same user to have duplicates of (d1,d2), as long as they have a different source.
Rows #1 and #3 demonstrate this for user aaa111.
However, this constraint does not prevent the following row from getting added...
| row_id | user_id | source | d1 | d2 |
|--------|---------|--------|--------|---------|
| 1 | aaa111 | foo | bar | 123 |
| 2 | aaa111 | foo | baz | 'horse' |
| 3 | aaa111 | scrog | bar | 123 |
| 4 | bbb222 | foo | goober | 456 |
| 5 | bbb222 | turnip | baz | 'horse' | <---- allowed new row
...because source is different for rows #2 and #5.
I would like to add a unique constraint where the combination of (d1,d2) may only exist for a single user_id.
Said another way, a single user can have as many unique (source, d1, d2) combinations as needed, but cannot share (d1,d2) with another user_id.
Is this data model fundamentally flawed to support this constraint? or is there a unique constraint that might help enforce this? Thanks in advance for any suggestions.

It's a conditional-constraint, you can use a trigger BEFORE INSERT OR UPDATE that raise exception when violate the constraint:
CREATE OR REPLACE FUNCTION check_user_combination() RETURNS trigger AS
$$
DECLARE
vCheckUser INTEGER;
BEGIN
SELECT INTO vCheckUser user_id
FROM table1
WHERE d1 = NEW.d1
AND d2 = NEW.d2
AND user_id <> NEW.user_id;
IF vCheckUser IS NOT NULL THEN
RAISE EXCEPTION 'User % have already d1=% and d2=%',vCheckUser,NEW.d1, NEW.d2;
END IF;
RETURN NEW;
END;
$$
language 'plpgsql';
CREATE TRIGGER tr_check_combination BEFORE INSERT OR UPDATE ON table1 FOR EACH ROW EXECUTE PROCEDURE check_user_combination();
This prevent insert or update additional user for the same d1 and d2.

Related

Could I make a constraint on inserting 'if the record is not contained in another table'?

I have 3 tables.
Table 'number':
| x |
|---|
| 1 |
| 2 |
| 3 |
Table 'group':
| group_id |
|----------|
| 1 |
| 2 |
Table 'number_in_group':
| group_id | x |
|----------|---|
| 1 | 4 |
| 1 | 5 |
| 2 | 4 |
| 2 | 5 |
| 2 | 7 |
Could I make a constraint on inserting into table number_in_group that x does not exist in table number?
If yes, is it a good approach, or better to put this business logic on the backend?
There is no constraint in Postgres that say make sure something does not exist, but you can create one - a trigger. In this case you select from numbers for the new value of x in number_in_group. If it exists then raise an exception. (see demo)
create or replace function not_if_in_numbers()
returns trigger
language plpgsql
as $$
begin
if exists
(select null
from numbers
where x = new.x
)
then
raise exception 'Invalid value for x (%). Found in numbers table.',new.x::text;
end if;
return new;
end;
$$;
create trigger check_restricted_x_biur
before insert or update of x
on number_in_group
for each row
execute function not_if_in_numbers();
You should create a similar trigger for numbers selecting from number_in_group and throws a corresponding exception. Do not build around the concept of an immutable table. It will not happen.

How to use array values from string_to_array to JOIN to a look-up table

I have an animals table like this:
Column | Type | Collation | Nullable | Default
------------------+--------------------------+-----------+----------+---------
animal_code | character varying(3) | | not null |
animal_type_code | character(2) | | not null |
description | character varying(64) | | not null |
Typical content might be:
animal_code | animal_type_code | description
-------------+------------------+------------------------------
XAA | T | Not an animal, but a toaster
1 | D | This is a dog called Bob
2 | C | This cat is called Frank
3 | C | Wilf the cat has three legs
4 | D | Thunder is a dog
An existing stored procedure I'm working with receives a text string containing a comma-separated list of these animal_code values, like this:
store_pet_show_details(
'London', -- p_venue VARCHAR(64)
'2019-12-03', -- p_date TIMESTAMPTZ
'XAA,91,22,23,74,15,64,47,12' -- p_entrants TEXT
);
I'm using unnest(string_to_array(code_csv, ',')) to extract the animal entry codes.
It's probably very simple, but I just want to see if any entrants have an animal_type_code of "T"
Please note that trim.
select animal_code, animal_type_code, description
from animals
inner join (select trim(e) entrant from unnest(string_to_array(p_entrants, ',')) e) t
on animal_code = entrant
where animal_type_code = 'T';

Update current id based on next id in postgresql

I have a table t1
+-----+-------+------+
| id | tID | cID |
+-----+-------+------+
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 4 |
| 4 | 1 | 2 |
| 1 | 2 | 3 |
| 2 | 2 | 2 |
+-----+-------*------*
I have deleted a record where tID is 1 and id is 3 and cID is 4,
now I want that sequence to get updated like record with id 4 gets sequence 3
below is the sample data.
+-----+-------+------+
| id | tID | cID |
+-----+-------+------+
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 1 | 2 | 3 |
| 2 | 2 | 2 |
+-----+-------+------+
Can I get an updated statement for this problem? How can I achieve this in Postgres?
Given that you know which record you just deleted you can update like this:
UPDATE t1 SET id = id - 1 where id > 3 and tID = 1;
First off presuming that id is (or part of) the Primary Key, this is a very very bad idea. The Primary key really ought to be IMMUTABLE. But if you insist on perusing it then at least do delete and update in a single step. Create an SQL function that handles both: (see fiddle)
create or replace
function remove_from_table1(id_in integer, tid_in integer)
returns void
language sql
as $$
with bye_bye (id, tid) as
( delete
from table1 d
where d.id = id_in
and d.tid = tid_in
returning id, tid
)
update table1 u
set id = u.id - 1
from bye_bye
where u.tid = bye_bye.tid
and u.id > bye_byeid;
$$;
That way later development (developers) would not need to make two separate calls, to complete the task. Even further I would put the function into a protected schema. Revoke delete authority from all schema except the owner. Grant execute of the function as needed. That protects the table from forgetting to call the function and issuing a direct delete.

Reset column with numeric value that represents the order when destroying a row

I have a table of users that has a column called order that represents the order in they will be elected.
So, for example, the table might look like:
| id | name | order |
|-----|--------|-------|
| 1 | John | 2 |
| 2 | Mike | 0 |
| 3 | Lisa | 1 |
So, say that now Lisa gets destroyed, I would like that in the same transaction that I destroy Lisa, I am able to update the table so the order is still consistent, so the expected result would be:
| id | name | order |
|-----|--------|-------|
| 1 | John | 1 |
| 2 | Mike | 0 |
Or, if Mike were the one to be deleted, the expected result would be:
| id | name | order |
|-----|--------|-------|
| 1 | John | 1 |
| 3 | Lisa | 0 |
How can I do this in PostgreSQL?
If you are just deleting one row, one option uses a cte and the returning clause to then trigger an update
with del as (
delete from mytable where name = 'Lisa'
returning ord
)
update mytable
set ord = ord - 1
from del d
where mytable.ord > d.ord
As a more general approach, I would really recommend trying to renumber the whole table after every delete. This is inefficient, and can get tedious for multi-rows delete.
Instead, you could build a view on top of the table:
create view myview as
select id, name, row_number() over(order by ord) ord
from mytable

insert uid into column based

I have two tables in postgresql looks something like below. please help me with the query to insert into table 1uid column based on column name2.
table 1 table 2
|uid|name1| |uid|name2|table 1uid|
| 1 | a | | 1 | b | |
| 2 | b | | 2 | C | |
| 3 | c | | 3 | a | |
The keyword you need to look for is Update (which changes existing rows). Insert is for creating brand new rows.
But for your particular case, something along the lines of:
update table2 set table1uid = (select uid from table1 where table1.name1 = table2.name2)