Search in 3 tables using PL/pgSQL functions and triggers - sql

I am new to SQL and still learning functions and triggers.
I have 3 tables:
PRODUCTS_BOUGHT
CUSTOMER
DATE
PRODUCTS
3FG
2022-12-15
25
4HZ
2022-12-18
30
PRODUCTS_PRICE:
DATE
TYPE
PRICE
2022-12-15
A
125$
2022-12-18
B
147$
CUSTOMERS_REGISTER:
CUSTOMER
TYPE
3FG
A
4HZ
B
I need to add a column "COST" in the REF table with a value obtained using: COST = PRICE * PRODUCTS. But the function needs to check that the price is applied based on the type of product purchased by the customer in that certain date to obtain something like this:
PRODUCTS_BOUGHT
CUSTOMER
DATE
PRODUCTS
COST
3FG
2022-12-15
25
3125
4HZ
2022-12-18
30
4410
I need to use something like the following:
ALTER TABLE products_bought
ADD COLUMN cost;
CREATE OR REPLACE FUNCTION calc_cost()
RETURNS TRIGGER AS $$
BEGIN
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER cost_trigger
BEFORE INSERT OR UPDATE ON products_bought
FOR EACH ROW
EXECUTE FUNCTION calc_cost();
I have been trying creating the column first and then adding the value like this:
ALTER TABLE products_bought
ADD COLUMN cost;
CREATE OR REPLACE FUNCTION calc_cost()
RETURNS TRIGGER AS $$
BEGIN
SELECT(products_bought.products * products_price.price) INTO cost
FROM products_bought, products_price, customers_register
WHERE products_bought.rf_date = products_price.fp_date AND
customers_register.type = customers_register.type;
RETURN cost;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER cost_trigger
BEFORE INSERT OR UPDATE ON products_bought
FOR EACH ROW
EXECUTE FUNCTION calc_cost();

Selecting from the table products_bought in your trigger function looks like a misunderstanding. The trigger is fired BEFORE INSERT OR UPDATE ON products_bought, so just work with the special NEW record. And make sure you also RETRUN NEW;:
CREATE OR REPLACE FUNCTION calc_cost()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
SELECT INTO NEW.cost
NEW.products * p.price
FROM products_price p
WHERE p.fp_date = NEW.rf_date;
RETURN NEW;
END
$func$;
This only makes sense if there is a single matching row in table products_price. If there can be more, you have to define which row to pick. If there is none, cost will not be assigned.
I also removed the table customers_register from the query, since it didn't seem to do anything useful (unless you wanted to nullify cost if there is no related row in that table, which I doubt.)
Related:
PostgreSQL Update trigger

The returning trigger function should return the NEW instead of only the column affected.
notice that the insert into is also beign inserted at the NEW.cost value.
You can look here : PostgreSQLTriggers for trigger default values like NEW , OLD from the row it is beign edited.
CREATE OR REPLACE FUNCTION calc_cost()
RETURNS TRIGGER AS $$
BEGIN
SELECT(products_bought.products * products_price.price) INTO NEW.cost
FROM products_bought, products_price, customers_register
WHERE products_bought.rf_date = products_price.fp_date AND
customers_register.type = customers_register.type;
RETURN NEW.*;
END;
$$ LANGUAGE plpgsql;

Related

Return a table when a trigger function is called

I need some_fun() to be executed before a trigger. It will return a table.
I ran this:
INSERT INTO SomeTable(some_bool) VALUES (true);
I expected this:
returnColHeader
------------------
12
23
23
(3 row)
But I got this:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
I've read documentation on TRIGGERS https://www.postgresql.org/docs/current/plpgsql-trigger.html
and also RETURNING CLAUSE https://www.postgresql.org/docs/9.5/dml-returning.html
and a few other postgres related readings but I'm still unable to solve my problem.
DROP TRIGGER IF EXISTS run_some_fun on SomeTable CASCADE;
CREATE TRIGGER run_some_fun
BEFORE INSERT ON SomeTable
FOR EACH ROW WHEN (NEW.some_bool = TRUE)
EXECUTE FUNCTION run_some_fun();
CREATE OR REPLACE FUNCTION run_some_fun()
RETURNS TRIGGER AS $$
BEGIN
SELECT some_fun(NEW.eid); -- This is wrong and throws error
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION some_fun(eID INT)
RETURNS TABLE (returnColHeader INT) AS $$
BEGIN
RETURN QUERY
SELECT eid FROM Joins j1;
END;
$$ LANGUAGE plpgsql;
You cannot to return anything from after trigger. The trigger functions can returns value of composite type, but the returned value from after trigger is ignored. There is not any chance for what you want. And it looks little bit scary.

Move Data from One table to other table by using trigger in postgreSQL

I have the requirement to move data from one table to another table when the value of one of the columns is updated. And I just want to move the updated row to the new table.
Below is my trigger that I have written. The issue with my code is, that it is moving all the data and not just the row which was updated. Can anyone give a suggestion?
create or replace function moveToAC1ControlHist()
returns trigger as $$
begin if NEW.file_status='CO'
then
insert into ac1_control_hist (file_status,identitifier)
(
select file_status,identitifier
from
ac1_control where new.FILE_STATUS = 'CO'
);
end if;
return new;
end;
$$ language plpgsql;
create TRIGGER AC1_CONTROL_TRIGGER AFTER update of file_status ON AC1_CONTROL
FOR EACH ROW when (new.file_status ='CO')EXECUTE PROCEDURE moveToAC1ControlHist();
I think the logic you want is:
create or replace function moveToAC1ControlHist()
returns trigger as
$$
begin
insert into ac1_control_hist (file_status,identitifier)
values (new.file_status, new.identitifier);
return null;
end;
$$ language plpgsql;
create trigger ac1_control_trigger
after update of file_status on ac1_control
for each row
when (new.file_status ='co')
execute function movetoac1controlhist()
;
Rationale:
you just want to copy (part of) the row being updated, so there is no need to select; you can access the values of the current row with new in a row-level trigger
the trigger definition filters on new file_status that is equal to 'CO', so there is no need for a if construct in the function
this is an after trigger, so you can just return null - the result is discarded anyway

How to print NEW value with trigger Postgres

I want to create trigger for Insert operation, and procedure that prints inserted value.
CREATE TRIGGER added_product_info_trigger
BEFORE INSERT
ON products
EXECUTE PROCEDURE added_product_info();
And my procedure
CREATE OR REPLACE FUNCTION added_product_info()
RETURNS trigger
AS
$$
(Select p.productname, s.companyname from products as p, suppliers as s
where p.supplierid = s.supplierid)
$$ LANGUAGE SQL;
How can I print my inserted value?
Sql functions cannot return trigger. You probably wanted to write a plpgsql function.
A trigger function cannot generate any output (like results of a select query).
You can use raise notice to pass some results to the client program:
create or replace function added_product_info()
returns trigger as $$
declare
company text;
begin
select companyname
from suppliers
where supplierid = new.supplierid
into company;
raise notice 'inserted: "%" supplied by "%"', new.productname, company;
return new;
end;
$$ language plpgsql;
The record new is visible in a trigger function only if the trigger is declared for each row
(default is for each statement when records new and old are not accessible):
create trigger added_product_info_trigger
before insert on products
for each row
execute procedure added_product_info();
If a trigger is before insert for each row it must return new.
Note that the client must be ready to get and process the notice.
If you run the query in the standard psql shell program, you ll get:
insert into products values ('some product', 1);
NOTICE: inserted: "some product" supplied by "company"
INSERT 0 1
Read:
Overview of Trigger Behavior
Visibility of Data Changes
Trigger Procedures

SQL Trigger to Insert a row

I'm using postgresql. These are the table involved in the trigger:
Pricing (title,publisher,period,offer,price)
The DB has 4 tables describing a magazine business.
The table above shows the title, publisher of the magazine, period (an option the subscribe to the magazine, in months (integer), offer (a string, like 'regular' or 'renew'), and price (integer).
The question:
Write a trigger that adds a new row in the pricing table.
If a new row is inserted with offer='regular', insert a new row, exactly the same BUT:
offer='renew' and a price discount of 10%.
Here my trigger, which doesn't work:
create or replace function offer_f() returns trigger as $$
begin
if(TG_OP='insert') then
if new.offer='regular' then
insert into pricing (title,pusblisher,offer,period, price)
values (new.title,new.publisher,"renew",new.period,new.price*0.9);
end if;
end if;
return new;
end;
$$
language plpgsql;
This is the trigger itself:
create trigger reduced_offer
after insert or update on pricing
for each row
execute procedure offer_f()
I really don't know what's wrong here, and I have tried replacing "after" with "before" in the trigger.
Thanks,
Alan

PLPGSQL Cascading Triggers?

I am trying to create a trigger, so that when ever I add a new record it adds another record in the same table. The session field will only take values between 1 and 4. So when I add a 1 in session I want it to add another record but with session 3 blocked. But the problem is that it leads to cascading triggers and it inserts itself again and again because the trigger is triggered when inserted.
I have for example a simple table:
CREATE TABLE example
(
id SERIAL PRIMARY KEY
,name VARCHAR(100) NOT NULL
,session INTEGER
,status VARCHAR(100)
);
My trigger function is:
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO example VALUES (NEW.id + 1, NEW.name, NEW.session+2, 'blocked');
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
Trigger is:
CREATE TRIGGER add_block
AFTER INSERT OR UPDATE
ON example
FOR EACH ROW
EXECUTE PROCEDURE add_block();
I get error:
SQL statement "INSERT INTO example VALUES ( $1 +1, $2 , $3 + 2, $4)"
PL/pgSQL function "add_block" line 37 at SQL statement
This error repeats itself so many times that I can't see the top.
How would I solve this?
EDIT:
CREATE TABLE block_rules
(
id SERIAL PRIMARY KEY
,session INTEGER
,block_session INTEGER
);
This table holds the block rules. So if a new record is inserted into the EXAMPLE table with session 1 then it blocks session 3 accordingly by inserting a new record with blocked status in the same (EXAMPLE) table above (not block_rules). Same for session 2 but it blocks session 4.
The block_rules table holds the rules (or pattern) to block a session by. It holds
id | session | block_session
------------------------------
1 | 1 | 3
2 | 2 | 4
3 | 3 | 2
How would I put that in the WHEN statement of the trigger going with Erwin Branstetter's answer below?
Thanks
New answer to edited question
This trigger function adds blocked sessions according to the information in table block_rules.
I assume that the tables are linked by id - information is missing in the question.
I now assume that the block rules are general rules for all sessions alike and link by session. The trigger is only called for non-blocked sessions and inserts a matching blocked session.
Trigger function:
CREATE OR REPLACE FUNCTION add_block()
RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO example (name, session, status)
VALUES (NEW.name
,(SELECT block_session
FROM block_rules
WHERE session = NEW.session)
,'blocked');
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
Trigger:
CREATE TRIGGER add_block
AFTER INSERT -- OR UPDATE
ON example
FOR EACH ROW
WHEN (NEW.status IS DISTINCT FROM 'blocked')
EXECUTE PROCEDURE add_block();
Answer to original question
There is still room for improvement. Consider this setup:
CREATE OR REPLACE FUNCTION add_block()
RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO example (name, session, status)
VALUES (NEW.name, NEW.session + 2, 'blocked');
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
CREATE TRIGGER add_block
AFTER INSERT -- OR UPDATE
ON example
FOR EACH ROW
WHEN (NEW.session < 3)
-- WHEN (status IS DISTINCT FROM 'blocked') -- alternative guess at filter
EXECUTE PROCEDURE add_block();
Major points:
For PostgreSQL 9.0 or later you can use a WHEN condition in the trigger definition. This would be most efficient. For older versions you use the same condition inside the trigger function.
There is no need to add a column, if you can define criteria to discern auto-inserted rows. You did not tell, so I assume that only auto-inserted rows have session > 2 in my example. I added an alternative WHEN condition for status = 'blocked' as comment.
You should always provide a column list for INSERTs. If you don't, later changes to the table may have unexpected side effects!
Do not insert NEW.id + 1 in the trigger manually. This won't increment the sequence and the next INSERT will fail with a duplicate key violation.
id is a serial column, so don't do anything. The default nextval() from the sequence is inserted automatically.
Your description only mentions INSERT, yet you have a trigger AFTER INSERT OR UPDATE. I cut out the UPDATE part.
The keyword plpgsql doesn't have to be quoted.
OK so can't you just add another column, something like this:
ALTER TABLE example ADD COLUMN trig INTEGER DEFAULT 0;
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
IF NEW.trig = 0 THEN
INSERT INTO example VALUES (NEXTVAL('example_id_seq'::regclass), NEW.name, NEW.session+2, 'blocked', 1);
END IF;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
it's not great, but it works :-)
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
SET SESSION session_replication_role = replica;
INSERT INTO example VALUES (NEXTVAL('example_id_seq'::regclass), NEW.name, NEW.session+2, 'blocked');
SET SESSION session_replication_role = origin;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';