Postgresql trigger to calculate total score - sql

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

Related

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;

trigger to create parent element and retrieve id in postgresql

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.

UPDATE one table upon INSERT INTO another table

I have a database with two tables:
devices
temperature
The schema follows:
CREATE TABLE IF NOT EXISTS devices(
device_id serial PRIMARY KEY,
device_name varchar(255) UNIQUE NOT NULL,
last_record_time timestamp without time zone DEFAULT '1995-10-30 10:30:00'
);
CREATE TABLE IF NOT EXISTS temperature(
device_id integer NOT NULL,
temperature decimal NOT NULL,
record_time timestamp without time zone NOT NULL,
CONSTRAINT temperature_device_id_fkey FOREIGN KEY (device_id)
REFERENCES devices (device_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
The devices table keeps a list of all the devices. So there is a unique id for each device. The temperature table aggregates data from all of the devices. You can select by device_id to see all the entries that pertain to a specific device.
I have the constraint that I cannot delete from the devices table because the temperature table depends on it. I would also like the devices table to be updated when a new record is inserted into the temperature table.
That is, the record_time from a new record in the temperature should become the last_record_time for that device's entry in the devices table. That way I always know when was the last time a device inserted data.
I am currently doing this programmatically. I insert records, and immediate select them right back out and write into the other table. This is introducing some bugs. So, I would prefer to automate this at the database level. How can I go about resolving this?
Alternative to using trigger would be CTE:
WITH ins AS (
INSERT INTO temperature (device_id, temperature, record_time)
VALUES (1, 35.21, '2018-01-30 09:55:23')
RETURNING device_id, record_time
)
UPDATE devices AS d SET last_record_time = ins.record_time
FROM ins
WHERE d.device_id = ins.device_id;
use trigger to do this implicitly.
create trigger on temperature table for events such as insert/delete/update and update temparature table inside that trigger.
CREATE OR REPLACE FUNCTION update_last_record_time()
RETURNS trigger AS
$$
BEGIN
UPDATE devices
SET last_record_time = NEW.record_time
WHERE device_id = NEW.device_id;
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER my_trigger
AFTER INSERT
ON temperature
FOR EACH ROW
EXECUTE PROCEDURE update_last_record_time();
For Deleting row from child table Use Cascaded delete when creating foreign key it will automatically delete records from the child table when Parent table record will be deleted

How to create a table with ONE existing row from another table?

I'm frankly new to sql and this is a project I'm doing.
I would like to know if there's a way to connect one column in one table to another table when creating tables. I know of the join method to show results of, but I want to minimized my code as possible.
CREATE TABLE players (
id INT PRIMARY KEY, -->code I want connect with table match_record
player_name CHARACTER
);
CREATE TABLE match_records (
(id INT PRIMARY KEY /*FROM players*/), --> the code I want it to be here
winner INT,
loser INT
);
CREATE TABLE players (
id INT not null PRIMARY KEY, -->code I want connect with table match_record
player_name CHARACTER
);
CREATE TABLE match_records (
id INT not null PRIMARY KEY references players(id), --> the code I want it to be here
winner INT,
loser INT
);
this way you restrict that match_records.id is only from players.id:
t=# insert into match_records select 1,1,0;
ERROR: insert or update on table "match_records" violates foreign key constraint "match_records_id_fkey"
DETAIL: Key (id)=(1) is not present in table "players".
So I add players:
t=# insert into players(id) values(1),(2);
INSERT 0 2
And now it allows insert:
t=# insert into match_records select 1,1,0;
INSERT 0 1
update
https://www.postgresql.org/docs/current/static/app-psql.html#APP-PSQL-PROMPTING
%#
If the session user is a database superuser, then a #, otherwise a >.
(The expansion of this value might change during a database session as
the result of the command SET SESSION AUTHORIZATION.)
in this way:
CREATE TABLE new_table as SELECT id,... from old_table where id = 1;

Tackling nested inserts using functions

Hi people i need some help deciding on the best way to do an insert into table ‘shop’ which has a serial id field. I also need to insert into tables ‘shopbranch’ and ‘shopproperties’ which both references shop.id.
In a nutshell I need to insert one shop record. Then two records for each table of the following tables, shopproperty and shopbranch, whose shopid (FK) references the just created shop.id field
I saw somewhere that i could wrap the ‘shop’ insert, inside a function called lets say ‘insert_shop’ which does the 'shop' insert and returns its id using a select statement
Then inside another function which inserts shoproperty and shopbranch records i could do one call to insert_shop function to return the shop id which can be used to be passed in as the shop id for the records.
Can you let me know if I’m looking at this in the correct way as I’m a newbie.
One way to approach this is to create a view on your three tables that shows all columns from all three tables that can be inserted or updated. If you then create an INSTEAD OF INSERT trigger on the view then you can manipulate the view contents as if it were a table. You can do the same with UPDATE and even combine the two into an INSTEAD OF INSERT OR UPDATE trigger. The function that your trigger calls then has three INSERT statements that redirect the insert on the view to the underlying tables:
CREATE TABLE shop (
id serial PRIMARY KEY,
nm text,
...
);
CREATE TABLE shopbranch (
id serial PRIMARY KEY,
shop integer NOT NULL REFERENCES shop,
branchcode text,
loc text,
...
);
CREATE TABLE shopproperties (
id serial PRIMARY KEY,
shop integer NOT NULL REFERENCES shop,
prop1 text,
prop2 text,
...
);
CREATE VIEW shopdetails AS
SELECT s.*, b.*, p.*
FROM shop s, shopbranch b, shopproperties p,
WHERE b.shop = s.id AND p.shop = s.id;
CREATE FUNCTION shopdetails_insert() RETURNS trigger AS $$
DECLARE
shopid integer;
BEGIN
INSERT INTO shop (nm, ...) VALUES (NEW.nm, ...) RETURNING id INTO shopid;
IF NOT FOUND
RETURN NULL;
END;
INSERT INTO shopbranch (shop, branchcode, loc, ...) VALUES (shopid, NEW.branchcode, NEW.loc, ...);
INSERT INTO shopproperties(shop, prop1, prop2, ...) VALUES (shopid, NEW.prop1, NEW.prop2, ...);
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER shopdetails_trigger_insert
INSTEAD OF INSERT
FOR EACH ROW EXECUTE PROCEDURE shopdetails_insert();
You could of course play with the view and show only those columns from the three tables that can be inserted or updated (such as excluding primary and foreign keys).