Update different table when row inserted - postgresql

I'm working on a marketplace platform, where I store user balances as a table of balance changes, along with a precalculated value in their user row. This allows me to check their balance easily, but still recalculate at specific intervals to ensure they stay in sync.
Here is an example of what the balance change table might look like:
| id | opening_balance | closing_balance | user_id |
|----|-----------------|-----------------|---------|
| 1 | 23.40 | 28.20 | 6 |
| 2 | 14.70 | 11.10 | 79 |
| 3 | 117.12 | 107.12 | 20 |
When a new row is inserted, I want to update the balance column on the user_id relation to the newly inserted closing_balance.
From what I've found online, I've got the following however it's for MySQL rather than Postgresql.
CREATE TRIGGER balance_update
AFTER INSERT ON balance_history
FOR EACH ROW
BEGIN
UPDATE users
SET balance = NEW.closing_balance
WHERE id = NEW.user_id;
END;
It looks like Postgresql requires you create a named procedure before creating a trigger? Not sure the best way to convert this.

Triggers in Postgres have a syntax a bit more complex than MySQL, because they execute procedures as the action. In this case, we can define a function which will perform the update you want as a result of the insert on the first table.
CREATE OR REPLACE FUNCTION your_proc()
RETURNS trigger AS
$$
BEGIN
UPDATE users
SET balance = NEW.closing_balance
WHERE id = NEW.user_id;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER balance_update
AFTER INSERT ON balance_history
FOR EACH ROW
EXECUTE PROCEDURE your_proc();

Related

Create Trigger For Update another row automatically on same table using postgresql

I want to create a trigger that can update another row on the same table on PostgreSQL.
if i run the query like these:
UPDATE tokens
SET amount = (SELECT amount FROM tokens WHERE id = 1)
where id = 2
these result that i expected.
description:
i want to set field amount on a row with id:2, where the amount value is from query result on a subquery, so the amount value on id:2 is same with id:1
Hopefully, with this created trigger, i can do update amount value on id=1 so the amount value on id:2 is same with id:1
Before update result:
id | amount|
1 | 200 |
2 | 200 |
When i update the amount value on id:1 to 100 on, so the amount value on id:2 become 100
After update result:
id | amount|
1 | 100 |
2 | 100 |
Update for my temporary solution:
i just create the UDF like these
CREATE FUNCTION update_amount(id_ops integer, id_mir integer) returns boolean LANGUAGE plpgsql AS $$
BEGIN
UPDATE tokens SET amount = (SELECT amount FROM tokens WHERE id = id_ops) WHERE id = id_mir;
RETURN 1;
END;
$$;
description:
id_ops: id where the amount i always update
id_mir: id where the amount automatically update after i update the amount with id_ops
Example of using my written UDF to resolve my problem:
I update the amount of id: 1 to 2000. The amount of id: 2 not updated to 2000
When i run query select update_amount(1,2);
The amount of id: 2 will same with amount on id: 1
I Need a trigger on PostgreSQL to automate or replace the function of UDF that i wrote
What you want to do it not really that difficult, I'll show you. But first: This is a very very bad idea. In fact bad enough that some databases, most notable Oracle, throw an exception if try it. Unfortunately Postgres allows it. You essentially create a recursive update as you are updating the table that initiated the trigger. This update in turn initiates the trigger. Without logic to stop this recursion you could update every row in the table.
I assume this is an extract for a much larger requirement, or perhaps you just want to know how to create a trigger. So we begin:
-- setup
drop table if exists tokens;
create table tokens( id integer, amount numeric(6,2));
-- create initial test data
insert into tokens(id, amount)
values (1,100), (2,150.69), (3,95.50), (4,75), (5,16.40);
Now the heart Postgres trigger: the trigger function, and the trigger. Note the function must be defined prior to the trigger which calls it.
-- create a trigger function: That is a function returning trigger.
create or replace function tokens_bur_func()
returns trigger
language plpgsql
as $$
begin
if new.id = 1
then
update tokens
set amount = new.amount
where id = 2;
end if;
return new;
end ;
$$;
-- create the trigger
create trigger tokens_bur
before update of amount
on tokens
for each row execute procedure tokens_bur_func();
--- test
select *
from tokens
order by id;
-- do an initial update
update tokens
set amount = 200
where id = 1;
-- Query returned successfully: one row affected, 31 msec execution time.
-- 1 row? Yes: DML count does not see change made from within trigger.
-- but
select *
from tokens
order by id;
Hard coding ids in a trigger however is not very functional after all "update ... where id in (1,2)" would be much easier, and safer as it does not require the recursion stop logic. So a slightly more generic trigger function is:
-- More general but still vastly limited:
-- trigger that mirrors subsequent row whenever an odd id is updated.
create or replace function tokens_bur_func()
returns trigger
language plpgsql
as $$
begin
if mod(new.id, 2)=1
then
update tokens
set amount = new.amount
where id = new.id+1;
end if;
return new;
end ;
$$;
-- test
update tokens
set amount = 900
where id = 3;
update tokens
set amount = 18.95
where id in (2,5);
select *
from tokens
order by id;
No matter how you proceed you required prior knowledge of update specifics. For example you said "might be id 2 I can set mirror from id 3" to do so you would need to alter the database in some manner either changing the trigger function or the trigger to pass parameters. (Triggers can pass parameters but they are static, supplied at create trigger time).
Finally make sure you got your recursion stop logic down cold. Because if not:
-- The Danger: What happens WITHOUT the 'stopper condition'
-- Using an almost direct conversion of your UDT
-- using new_id as
create or replace function tokens_bur_func()
returns trigger
language plpgsql
as $$
begin
update tokens
set amount = new.amount
where id = new.id+1;
return new;
end ;
$$;
-- test
update tokens
set amount = 137.92
where id = 1;
-- Query returned successfully: one row affected, 31 msec execution time.
-- but
select *
from tokens
order by id;

Back date Windows 2012 server to use program on another date

I have ERP application that uses the system date when posting transactions. The database is PostgreSQL. I'm able to use https://www.nirsoft.net/utils/run_as_date.html for backdate the application but I notice that the transactions are still posting as of "today" and I think that maybe because of PostgreSQL using the system date.
Is there any way I can set the date back for PostgreSQL? Or any other way to do this? The process in the ERP application does not have an option to back date.
The easiest would be to add a trigger to the database that would change the date for inserted rows:
create table testpast(
id serial primary key,
time timestamp with time zone not null default now()
);
insert into testpast (time) values (default);
select * from testpast;
id | time
----+-------------------------------
1 | 2018-03-16 00:09:20.219419+01
(1 row)
create function time_20_years_back() returns trigger as $$
begin
NEW.time = now()-'20 years'::interval;
return NEW;
end;
$$ language plpgsql;
create trigger testpast_time_20_years_back
before insert on testpast
for each row
execute procedure time_20_years_back();
insert into testpast (time) values (default);
select * from testpast;
id | time
----+-------------------------------
1 | 2018-03-16 00:09:20.219419+01
2 | 1998-03-16 00:09:55.741345+01
(2 rows)
Though I have no idea what would be the purpose of such a hack.

how to create a range of values and then use them to insert data into postgresql database

Background Information:
I need to auto generate a bunch of records in a table. The only piece of information I have is a start range and an end range.
Let's say my table looks like this:
id
widgetnumber
The logic needs to be contained within a .sql file.
I'm running postgresql
Code
This is what I have so far... as a test... and it seems to be working:
DO $$
DECLARE widgetnum text;
BEGIN
SELECT 5 INTO widgetnum;
INSERT INTO widgets VALUES(DEFAULT, widgetnum);
END $$;
And then to run it, I do this from a command line on my database server:
testbox:/tmp# psql -U myuser -d widgets -f addwidgets.sql
DO
Questions
How would I modify this code to loop through a range of widget numbers and insert them all?
for example, I would be provided with a start range and an end range (100 to 150 let's say)
Can you point me to a good online resource to learn the syntax i should be using?
Thanks.
How would I modify this code to loop through a range of widget numbers and insert them all?
You can use generate_series() for that.
insert into widgets (widgetnumber)
select i
from generate_series(100, 150) as t(i);
Can you point me to a good online resource to learn the syntax i should be using?
https://www.postgresql.org/docs/current/static/index.html
dvdrental=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
id | integer | | |
name | character varying(250) | | |
dvdrental=# begin;
BEGIN
dvdrental=# insert into test(id,name) select generate_series(1,100000),'Kishore';
INSERT 0 100000

Creating a specific exception for a function in PL/PgSQL

I am trying to write a sql trigger that compares the old and new values. If the two values are different then I need to display an error saying that you can't update the names. That is the problem that I seem to be having, I don't understand how to display an error for an exception in PSQL The exact definition of my trigger is
write a trigger function named disallow_team_name_update that compares the OLD and NEW records
team fields. If they are different raise an exception that states that changing the team name is
not allowed.
The table that I am using for this problem is
Table "table.group_standings"
Column | Type | Modifiers
--------+-----------------------+-----------
team | character varying(25) | not null
wins | smallint | not null
losses | smallint | not null
draws | smallint | not null
points | smallint| not null
Indexes:
"group_standings_pkey" PRIMARY KEY, btree (team)
Check constraints:
"group_standings_draws_check" CHECK (draws >= 0)
"group_standings_losses_check" CHECK (losses >= 0)
"group_standings_points_check" CHECK (points >= 0)
"group_standings_wins_check" CHECK (wins >= 0)
The code I have right now, I need help for telling the user that they aren't aloud to change team names, but I am having issues doing so.
CREATE OR REPLACE FUNCTION disallow_team_name_update() RETURNS trigger AS $$
BEGIN
if(NEW.team <> OLD.team)
/*tell the user to not change team names*/
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tr_disallow_team_name_update BEFORE INSERT OR UPDATE OF team ON group_standings
FOR EACH ROW EXECUTE PROCEDURE disallow_team_name_update();
You need the RAISE statement to raise an exception. There are examples in the manual.
RAISE EXCEPTION ....
(This looks like homework and you've asked about homework before, so I'm intentionally not giving a complete answer).

Apply postgreSQL trigger to existing rows in database

I am using PostgeSQL 9.2.2. My database schema is
pg_rocks_post
title | character varying(1024) | not null
body | text | not null
body_title_tsv | tsvector |
body_title_titleupweight_tsv | tsvector |
I created the body_title_titleupweight_tsv as a type tsvector.
I then defined a trigger using the examples in the documentation which up weighted the title as follows.
pgdj=# CREATE FUNCTION title_upweight_trigger() RETURNS trigger AS $$
begin
new.body_title_titleupweight_tsv :=
setweight(to_tsvector('pg_catalog.english', coalesce(new.title,'')), 'A') ||
setweight(to_tsvector('pg_catalog.english', coalesce(new.body,'')), 'D');
return new;
end
$$ LANGUAGE plpgsql;
I know the trigger works because when I update an entry in the pg_rocks_post and then query it : I see that it has correctly populated the body_title_titleupweight_tsv ts_vector with that updated row.
My Question is how do I have it apply the trigger to the existing rows in my table. I am only learning postgres and so have a few hundred entries in my test database and want to know how to populate the body_title_titleupweight_tsv column.
I think one way to do this would be to run an update and write the function all over with something like
pgdj=# UPDATE pg_rocks_post SET body_title_titleupweight_tsv =
setweight(to_tsvector( coalesce(title,'')),'A') ||
setweight(to_tsvector(coalesce(body,'')),'D');
Instead of re writing the logic for the trigger again in the update statement above. Is there a way to trigger the trigger above by doing a dummy update or a "touch" style operation that flips the trigger on all rows in the database.
I tried looking for syntax or examples of such dummy or "touch" type operations and could not find any that explained how to do this.
Since the table is small, just do a dummy update of the entire table:
update pg_rocks_post set title=title;
And let the trigger do its thing.
Normally triggers would run on a table on BEFORE or AFTER an insert, update or delete of a row. There are several options that allow you to decide on when to call the trigger.
Updating the row currently being inserted before insert would be a typical way to use a trigger. Then it is just a matter of creating a trigger on the actual table:
CREATE TRIGGER trig_title_upweight_trigger
BEFORE INSERT OR UPDATE
ON pg_rocks_post
FOR EACH ROW
EXECUTE PROCEDURE title_upweight_trigger();