Multiple postgres sequences for each foreign key - postgresql

I'm using postgres 14 and my invoice table looks something like that:
name
type
location_id
uuid
invoice_id
int
...
...
The location_id is a foreign key from the location table. location_id and invoice_id are a composite primary key.
What I want to achieve now is that the invoice id for each location starts at 1 and increments automatically.
Is there a way to implement this with something like a sequence?

I solved the issue by using a before insert trigger:
create or replace Function private.invoice_before_insert()
returns trigger
language plpgsql
as $$
begin
new.invoice_id = (
select coalesce(max(invoice_id), 0) + 1
from private.invoice
where invoice_location_id = new.invoice_location_id
);
new.invoice_created_at = current_timestamp;
return new;
end;
$$;
create trigger invoice_before_insert
before insert
on private.invoice
for each row
execute function private.invoice_before_insert();
I'm not sure if this aprouch has any drawbacks tho. ¯_(ツ)_/¯

Related

Conditionally set 'GENERATED ALWAYS AS IDENTITY' if ID is not provided on insert

I have a the following DDL and would like to generate an ID on insert to the table, but ONLY if the ID has not been provided already.
drop table if exists table1;
create table table1 (
pk_id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
other_id INT GENERATED ALWAYS AS IDENTITY -- DO THIS ONLY IF NULL
)
I was thinking of using a stored procedure to do this and have attempted a start with the below code, but have been unable to get it working:
create or replace function conditionally_generated_identity(other_id integer)
returns table (id integer) as $$
begin
if other_id is null then
return query other_id generated always as identity;
elsif other_id is not null then
return query other_id;
end if;
end;
$$ language plpgsql;

Trigger insert into another table only if unique value

I have a trigger function that copy row of unique values to another table on update or insert that ALMOST work.
The trigger should only insert a new row to the sample table if the number don't exist in it before. Atm. it insert a new row to the sample table with the value NULL if the number already exist in the table. I dont want it to do anything if maintbl.number = sample.nb_main
EDIT: sample table and sample data
CREATE TABLE schema.main(
sid SERIAL NOT NULL,
number INTEGER,
CONSTRAINT sid_pk PRIMARY KEY (sid)
)
CREATE TABLE schema.sample(
gid SERIAL NOT NULL,
nb_main INTEGER,
CONSTRAINT gid_pk PRIMARY KEY (gid)
Example and desired result
schema.main schema.sample
number nb_main
234233 234233
234234 555555
234234
555555
555555
CREATE OR REPLACE FUNCTION schema.update_number()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO schema.sample(
nb_main)
SELECT DISTINCT(maintbl.number)
FROM schema.maintbl
WHERE NOT EXISTS (
SELECT nb_main FROM schema.sample WHERE maintbl.number = sample.nb_main);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION schema.update_number()
OWNER TO postgres;
CREATE TRIGGER update_number
AFTER INSERT OR UPDATE
ON schema.maintbl
FOR EACH ROW
EXECUTE PROCEDURE schema.update_number();
I just found out that my select query is probably wrong, if I run SELECT query by itself it return one row 'NULL' but i should not?
SELECT DISTINCT(maintbl.number)
FROM schema.maintbl
WHERE NOT EXISTS (
SELECT nb_main FROM schema.sample WHERE maintbl.number = sample.nb_main);
Any good advice?
Best
If I understood correctly, you wish to append to schema.sample a number that has been inserted or updated in schema.maintbl, right?
CREATE OR REPLACE FUNCTION schema.update_number()
RETURNS trigger AS
$BODY$
BEGIN
IF (SELECT COUNT(*) FROM schema.sample WHERE number = NEW.number) = 0 THEN
INSERT INTO schema.sample(nb_main) VALUES (NEW.number);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;

postgresql inheritance / insert in parent then same record only in child

I am trying to insert in child table ONLY the same record that is already exsisting in the parent table.
Because ONLY is not aplicable i've tried
to create before insert trigger on parent table that checks for existing id(primary key) but it seems it doesn`t work -> the data are still duplicated:
Example:
parent table:
CREATE TABLE public.store(
id serial PRIMARY KEY,
name text);
child table :
CREATE TABLE public.db_store(
) INHERITS(store);
alter table public.db_store
add constraint db_store_pkey_id primary key (id);
function trigger:
create or replace function store_before_insert()
returns trigger language plpgsql as $$
declare old_s_id integer;
begin
old_s_id=null;
if (new.id is not null ) then
select s.id into old_s_id from store s where s.id=new.id;
if (old_s_id is not null) then
return null;
end if;
end if;
return new;
end $$;
trigger itself:
create trigger insert_storehouse_trg
before insert on storehouse
for each row
execute procedure storehouse_before_insert();
if execute:
insert into store(name) ('test');
insert into db_store(id,name) (1,'test');
(1, 'test') appers two time in store.
Is there are way to achive it without delete record from parent table?

Postgres triggers - adding old and new values

I am trying to learn Postgres triggers, using some simple examples. I have created a simple table:
create table emp (empname text, salary integer, last_user text);
My goal is to replace the old salary with a new salary computed as the salary inserted (new) + the old salary. I could not get them to sum even when I did not put a condition (i.e. empname is the same)
Here is my code:
-- this table returns a new row instead of summing
CREATE OR REPLACE FUNCTION emp_stamp() RETURNS trigger
AS $emp_stamp$
BEGIN
new.salary = new.salary + old.salary ;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp
BEFORE UPDATE on emp
FOR EACH ROW
EXECUTE PROCEDURE emp_stamp();
When I insert into the table, I get a new row added and no existing rows follow the formula:
INSERT INTO EMP VALUES('BR',39970,'BR')
I have also unsuccessfully tried the UPDATE command.
Your CREATE TRIGGER script says BEFORE UPDATE. So this trigger is not fired at all for INSERT commands.
Moreover, the same trigger function would raise an error for INSERT commands anyway because, obviously, there is no "old" version for newly inserted rows.
It should work just fine as is for UPDATE, though. I just cleaned it up a bit:
CREATE OR REPLACE FUNCTION emp_stamp()
RETURNS trigger AS
$func$
BEGIN
NEW.salary := NEW.salary + OLD.salary;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp
BEFORE UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
Just a proof of concept, I fail to see the point of adding up old an new value.
For starters, your table needs a proper PRIMARY KEY, a serial column for instance (empname is hardly unique):
CREATE TABLE emp (
emp_id serial PRIMARY KEY
, empname text
, salary integer
, last_user text);
Then the UPDATE could work reliably:
UPDATE EMP
SET salary = 39970
WHERE emp_id = 123;
I still don't see how the mentioned trigger would make sense. You could increase an existing salary like this, no trigger involved:
UPDATE EMP
SET salary = salary + 39970
WHERE emp_id = 123;

CONSTRAINT to check values from a remotely related table (via join etc.)

I would like to add a constraint that will check values from related table.
I have 3 tables:
CREATE TABLE somethink_usr_rel (
user_id BIGINT NOT NULL,
stomethink_id BIGINT NOT NULL
);
CREATE TABLE usr (
id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role (
id BIGINT NOT NULL,
type BIGINT NOT NULL
);
(If you want me to put constraint with FK let me know.)
I want to add a constraint to somethink_usr_rel that checks type in role ("two tables away"), e.g.:
ALTER TABLE somethink_usr_rel
ADD CONSTRAINT CH_sm_usr_type_check
CHECK (usr.role.type = 'SOME_ENUM');
I tried to do this with JOINs but didn't succeed. Any idea how to achieve it?
CHECK constraints cannot currently reference other tables. The manual:
Currently, CHECK expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
One way is to use a trigger like demonstrated by #Wolph.
A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints, which are the first choice to enforce referential integrity. Related answer on dba.SE with detailed instructions:
Enforcing constraints “two tables away”
Another option would be to "fake" an IMMUTABLE function doing the check and use that in a CHECK constraint. Postgres will allow this, but be aware of possible caveats. Best make that a NOT VALID constraint. See:
Disable all constraints and table checks while restoring a dump
A CHECK constraint is not an option if you need joins. You can create a trigger which raises an error instead.
Have a look at this example: http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html#PLPGSQL-TRIGGER-EXAMPLE
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
...i did it so (nazwa=user name, firma = company name) :
CREATE TABLE users
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
nazwa character varying(20),
firma character varying(50)
);
CREATE TABLE test
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
firma character varying(50),
towar character varying(20),
nazwisko character varying(20)
);
ALTER TABLE public.test ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION whoIAM3() RETURNS varchar(50) as $$
declare
result varchar(50);
BEGIN
select into result users.firma from users where users.nazwa = current_user;
return result;
END;
$$ LANGUAGE plpgsql;
CREATE POLICY user_policy ON public.test
USING (firma = whoIAM3());
CREATE FUNCTION test_trigger_function()
RETURNS trigger AS $$
BEGIN
NEW.firma:=whoIam3();
return NEW;
END
$$ LANGUAGE 'plpgsql'
CREATE TRIGGER test_trigger_insert BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE test_trigger_function();