trigger to create parent element and retrieve id in postgresql - sql

I have created the following tables:
CREATE TABLE IF NOT EXISTS public.teams (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.submissions (
id SERIAL PRIMARY KEY,
team_id INTEGER REFERENCES public.teams NOT NULL,
records_num INTEGER NOT NULL,
timestamp TIMESTAMP NOT NULL
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.predictions (
id SERIAL PRIMARY KEY,
submission_id INTEGER REFERENCES public.submissions NOT NULL,
customer INTEGER REFERENCES public.real NOT NULL,
date DATE NOT NULL,
billing NUMERIC(20, 2) NOT NULL
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.real (
customer INTEGER PRIMARY KEY,
date DATE NOT NULL,
billing NUMERIC(20, 2) NOT NULL
) WITH (OIDS = FALSE);
The relation for submissions-predictions is one-to-many; users will submit predictions in packets of 1000 rows that should get the same submission id.
I am trying to create a trigger that runs BEFORE INSERT ON predictions that creates a submissions row. This is what I have so far:
CREATE OR REPLACE FUNCTION insert_submission() RETURNS TRIGGER AS
$$
BEGIN
INSERT INTO submissions(team_id, records_num, timestamp)
VALUES (1, 1, '2018-04-21 00:00:00'); /*example values, need to fill with dynamically assigned ones, specially for records_num and team_id*/
RETURN NULL;
END
$$ LANGUAGE plpgsql;
DROP TRIGGER trigger_submission ON public.predictions;
CREATE TRIGGER trigger_submission BEFORE INSERT ON predictions
EXECUTE PROCEDURE insert_submission();
So, my questions are:
How do I go about retrieving the newly created submissions.id for the row inserted by the trigger, in order to add it to all the rows inserted in predictions by the user? Do I have to run another trigger AFTER INSERT for this?
EDIT: to clarify following #bignose answer, the sequence of events would go like this:
User inserts 1000 rows into public.predictions:
INSERT INTO predictions(customer, date, billing)
VALUES
(1, '2018-01-05', 543.42),
(4, '2018-04-02', 553.21),
...
(423, '2019-11-18', 38.87) /* 1000th row */
He does not know which submission_id to insert in those rows and indeed, the submissions row for this packet of predictions doesn't exist yet so a trigger runs before to create a row in submissions that would execute something like this:
INSERT INTO public.submisssions(team_id, records_num, timestamp)
VALUES (
4, /* I will need something to retrieve team_id here */
1000, /* I will need something to count the rows of the insert that triggered this */
NOW() /* convert to timestamp */
)
This last query should return the public.submission.id value that it has just created to the insert the user requested so that it ends up being something like this:
INSERT INTO predictions(customer, date, billing)
VALUES
(#submission_id, 1, '2018-01-05', 543.42),
(#submission_id, 4, '2018-04-02', 553.21),
...
(#submission_id, 423, '2019-11-18', 38.87) /* 1000th row */
Where #submission_id should be the value retrieved from the trigger (and the some for all the 1000 rows)
How could I count the rows inserted by the user to use them as value for submissions.records_num?
How could I retrieve team.id to insert during the trigger execution, assuming I know team.name beforehand?
Thank you!
Kind regards

A trigger function, when used for a row-level trigger, has access to the old and new state of the table.
CREATE OR REPLACE FUNCTION insert_submission() RETURNS TRIGGER AS
$$
BEGIN
INSERT INTO submissions(team_id, records_num, timestamp)
VALUES (NEW.foo, NEW.bar, '2018-04-21 00:00:00');
RETURN NULL;
END
$$ LANGUAGE plpgsql;
It's not clear from the description, which fields you expect to retrieve from the row that triggers this function. So you'll need to substitute NEW.foo and NEW.bar with field references in the NEW row state.

Related

Create a record automatically in another record when table gets a new record

I have two tables. I want to automatically create a record in the payment record with status false when a record has been added to service table. I would want the payment to look like this for e.g if service has been added
It should create a record in payment automatically like this:
id: autoincrement, fk_service 1, price null, payment_date null, status false
In my web app I will just update the values.
Original tables:
Service
id BIGSERIAL PK,
start_date date not null,
end_date date null,
status VARCHAR(20)
Payment
id BIGSERIAL PK,
fk_service BIGSERIAL,
price DECIMAL,
payment_date date,
status BOOLEAN not null,
FOREIGN KEY(fk_service) references service(id)
Can someone advice, how can I achieve this?
I tried writing a trigger
CREATE FUNCTION insert_payment() RETURNS TRIGGER
AS $insert_payment$
BEGIN
IF NEW service THEN
insert into payment(fk_service, status) values (service.id, false)
END IF;
END;
I am getting syntax error at or near create line 5 as $BODY$CREATE FUNCTION insert_payment() RETURNS TRIGGER^
You have to "attach" the function into at trigger, which will be listening events on the given table, e.g. an AFTER INSERT trigger
Function
CREATE OR REPLACE FUNCTION insert_payment() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO payment(fk_service, status) values (NEW.id, false);
RETURN NEW;
END;$$ LANGUAGE plpgsql;
Trigger on table service
CREATE OR REPLACE TRIGGER on_insert_service AFTER INSERT ON service
FOR EACH ROW EXECUTE PROCEDURE insert_payment();
Demo: db<>fiddle

How to create date trigger?

How can I create a trigger to increase a date data from my table with each next row? I had an attempt, is below the table
What I want to do is to increase date from training_date_end by 1 week. But I don't know how to do it, just studying. Can anyone help?
CREATE TABLE training
(
coach_id int NOT NULL,
customer_id int NOT NULL,
training_date_start date NULL,
training_date_end date NULL,
training_place_id int NOT NULL,
CONSTRAINT training_pk PRIMARY KEY (training_place_id)
);
create or replace trigger lucky_week
before insert or update on training
for each row
begin
update training
set training_date_end = :new.training_date_end + 7
where training_date_end = :new.training_date_end;
end;
Most probably like this:
create or replace trigger lucky_week
before insert or update on training
for each row
begin
:new.training_date_end := :new.training_date_end + 7;
end;
Because, your trigger will suffer from the mutating table error (if you insert more than a single row), and - won't do anything in that case (because row you'd like to update doesn't exist yet).

How to insert a newly generated id into another table with a trigger in postgresql?

Basically, users when they create a new record in mytable1, there is an id field that needs to be the same across multiple tables. I achieve this by having mytable2 with the s_id as primary key
My current function looks like
CREATE OR REPLACE FUNCTION test.new_record()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
case when new.s_id in (select s_id from mytable1) then
insert into mytable2 (sprn, date_created) select max(s_id) +1, now() from mytable2 ;
update mytable1 set new.s_id = (select max(b.s_id) from mytable2 b);
end case;
RETURN new;
END;
$function$;
Intended was when the s_id is replicated then it would create a new entry on mytable2. This new entry would then be updated onto mytable1
Problem with this function is that right now it does not recognise the new on the update part of the function.
How to keep the s_id take the value on every new insert ?
If you want to have one "generator" across multiple tables, create one sequence that is used across all those tables for the default value:
create sequence the_id_sequence;
create table one
(
id integer primary key default nextval('the_id_sequence')
.... other columns
);
create table two
(
id integer primary key default nextval('the_id_sequence')
.... other columns ...
);
If you want to replicate an ID from one table to another during insert, you only need one sequence:
create table one
(
-- using identity is the preferred over "serial" to auto-generate PK values
id integer primary key generated always as identity
);
create table two
(
id integer primary key
);
create or replace function insert_two()
returns trigger
as
$$
begin
insert into two (id) values (new.id);
return new;
end;
$$
language plpgsql;
create trigger replicate_id
before insert on one
for each row
execute procedure insert_two();
Then if you run:
insert into one (id) values (default);
A row with exactly the same id value will be inserted into table two.
If you don't have a generated ID column so far, use the following syntax:
alter table one
add testidcolumn bigint generated always as identity;

Postgresql trigger to calculate total score

I am trying to learn how to create a trigger in postgresql. I have a table
Thread_user - table name
thread_id
user_id
points
Thread - table name
thread_id
total_points
I want to on update of any thread_user row to update the total points in the thread table. I need to basically select * from thread_user where thread_id = thread_id of inserted item and then add the points then update thread_points in the threads table. I believe this is done in triggers but maybe a stored procedure would be better.
First step is to make a function which calculates the sum of points and updates a row in the calculated_points table.
Thereafter you'll ahve to create a trigger which is called upon inserting a row in the user_points table.
DROP TABLE IF EXISTS user_points CASCADE;
CREATE TABLE user_points (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
points INT NOT NULL
);
DROP TABLE IF EXISTS calculated_points CASCADE;
CREATE TABLE calculated_points (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL UNIQUE,
points INT NOT NULL
);
INSERT INTO calculated_points (user_id, points)
VALUES
(1, 0),
(2, 0);
CREATE OR REPLACE FUNCTION calculate_total_points()
RETURNS trigger AS $calculate_total_points$
BEGIN
UPDATE calculated_points
SET points = (SELECT SUM(points)
FROM user_points
WHERE user_id = NEW.user_id)
WHERE user_id = NEW.user_id;
RETURN NEW;
END;
$calculate_total_points$ LANGUAGE plpgsql;
CREATE TRIGGER points_added
AFTER INSERT
ON user_points
FOR EACH ROW
EXECUTE PROCEDURE calculate_total_points();

Insert into partitioned table return violates check constraint but shouldn't

I've a table in a postgresql and I want it to be partitioned. The structure is below
TABLE "DTD1"."logAdminActivity" (
"username" CHARACTER VARYING( 200 ) NOT NULL,
"action" CHARACTER VARYING( 100 ) NOT NULL,
"pk" CHARACTER VARYING( 5 ) NOT NULL,
"tabel" CHARACTER VARYING( 200 )NOT NULL,
"timestamp" TIMESTAMP WITHOUT TIME ZONE
);
Then I've create some partition table that inherit Tabel "DTD1"."logAdminActivity" above look like this:
CREATE TABLE "DTD1".logAdminActivity_kategori (
CHECK ('tabel'='kategori')
) INHERITS ("DTD1"."logAdminActivity");
CREATE TABLE "DTD1".logAdminActivity_subyek (
CHECK ('tabel'='subyek')
) INHERITS ("DTD1"."logAdminActivity");
...
CREATE TABLE "DTD1".logAdminActivity_satuan (
CHECK ('tabel'='satuan')
) INHERITS ("DTD1"."logAdminActivity");
CREATE TABLE "DTD1".logAdminActivity_memberfilter (
CHECK ('tabel'='memberFilter')
) INHERITS ("DTD1"."logAdminActivity");
After that I create indexes each partitioned table in username coloumn. Then I create this trigger function and trigger that call that trigger function in below. So, when I insert the data, the coresponding tabel coloumn will be redirected into proper partition table.
CREATE OR REPLACE FUNCTION "DTD1".logAdminActivity_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( New."tabel" = 'kategori' ) THEN
INSERT INTO "DTD1".logAdminActivity_kategori VALUES (NEW.*);
ELSIF ( New."tabel" = 'subyek' ) THEN
INSERT INTO "DTD1".logAdminActivity_subyek VALUES (NEW.*);
..
ELSE
RAISE EXCEPTION 'Tabel out of range. Fix the logAdminActivity_insert_trigger() function!' ;
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER insert_logAdminActivity_trigger
BEFORE INSERT ON "DTD1"."logAdminActivity"
FOR EACH ROW EXECUTE PROCEDURE "DTD1".logAdminActivity_insert_trigger();
Then I test it by insert procedure such as
INSERT INTO "DTD1"."logadminactivity_subyek" ( "action", "pk", "tabel", "timestamp", "username")
VALUES ( 'bla', '12312', 'subyek', '2014-01-01 02:02:03', 'asdf' );
But why it return error look like this
ERROR: new row for relation "logadminactivity_subyek" violates check
constraint "logadminactivity_subyek_check" DETAIL: Failing row
contains (asdf, bla, subyek, 12312, 2014-01-01 02:02:03).
How it can be happened because I've try to follow the documentation in this ?
http://www.postgresql.org/docs/9.3/static/ddl-partitioning.html
I think the 'tabel' value from query above ('subyek') is not match with trigger function but when I check with check constrain it pass.
Is there any part I miss about it or is there any solution to solve this problem?
Regards
CHECK ('tabel'='subyek')
That check constraint is incorrect because 'tabel' is a constant.
It is equivalent to CHECK (false).
You want
CHECK ("tabel"='subyek')