Trigger function updates all rows instead of one (PostgreSQL) - sql

Using PostgreSQL.
What I have is:
3 tables
1.Customer2c with columns: CustomerID,PersonID,Number_Of_Items`.
2.SalesOrderHeader2c with columns: SalesOrderID,CustomerID.
SalesOrderDetail2c with columns: SalesOrderDetailID,SalesOrderID,OrderQty
I want to create a trigger function, that will trigger whenever someone uses
INSERT INTO 'SalesOrderDetail2c' table
and that is going to get the OrderQty that was inserted and update the correspondent Number_Of_Items field with it.
My trigger is working, but the problem is that whenever I insert a new value to the SalesOrderDetail2c, the function gets the OrderQty value and updates all the rows of Number_Of_Items with it, instead of updating just the correspondent one.
Any help appreciated. What I have so far is this (It may be copletely wrong, dont judge please!):
CREATE OR REPLACE FUNCTION FunctionTrigger2c() RETURNS TRIGGER AS
$BODY$
BEGIN
UPDATE Customer2c
SET Number_Of_Items =
(SELECT OrderQty
FROM SalesOrderDetail2c
INNER JOIN SalesOrderHeader2c ON (SalesOrderDetail2c.SalesOrderID = SalesOrderHeader2c.SalesOrderID)
INNER JOIN Customer2c ON (SalesOrderHeader2c.CustomerID = Customer2c.CustomerID)
ORDER BY SalesOrderDetailID DESC LIMIT 1
)
FROM SalesOrderHeader2c
WHERE SalesOrderHeader2c.CustomerID = Customer2c.CustomerID
;
RETURN NEW;
END;
$BODY$
language plpgsql;
CREATE TRIGGER Trigger2c
AFTER INSERT ON SalesOrderDetail2c
FOR EACH ROW
EXECUTE PROCEDURE FunctionTrigger2c();

I had to use .new as #Nicarus mentioned above! Thanks again by the way.
This is the new code and now it changes only the correspondent value.
CREATE OR REPLACE FUNCTION FunctionTrigger2c() RETURNS TRIGGER AS
$BODY$
BEGIN
UPDATE Customer2c
SET Number_Of_Items =
(SELECT new.OrderQty
FROM SalesOrderDetail2c
order by salesorderdetailid desc limit 1
)
FROM SalesOrderheader2c
WHERE (SalesOrderheader2c.salesorderID = new.salesorderID) and (salesorderheader2c.customerid = customer2c.customerid)
;
RETURN NEW;
END;
$BODY$
language plpgsql;
CREATE TRIGGER Trigger2c
AFTER INSERT ON SalesOrderDetail2c
FOR EACH ROW
EXECUTE PROCEDURE FunctionTrigger2c();

I am pointing out why your original trigger did not work. The reason it updated every row is because of your UPDATE statement. After you inserted something, it triggered the update.
When the update ran it got these things:
What table I am gonna update
What value am I gonna set on what column of the given table
What rows I am gonna implement this update on (FROM + WHERE)
The problem lies in the last part, basically calling FROM in an UPDATE statement is like calling a SELECT statement, it tried to process every row from the FROM clause tables with the given WHERE value.
PostgreSQL documentation about UPDATE statement from_list parameter states that "A
list of table expressions, allowing columns from other tables to
appear in the WHERE condition and the update expressions. This is
similar to the list of tables that can be specified in the FROM Clause
of a SELECT statement."
(https://www.postgresql.org/docs/9.1/sql-update.html)
Basically, your update statement went through every row in SalesOrderHeader2c which conveniently matched with every row of Customer2c (with SalesOrderHeader2c.CustomerID = Customer2c.CustomerID), updating the value of every row in Customer2c with the firstly found Number_Of_Items value from the UPDATE SET statement. This is why you got the same value for every row in Customer2c.
The reason the new trigger works is because the update statement is done with the correctly selected rows.

Here is my, quite general trigger that is used do count/sum values in dependent tables. You should take care of all order changes, ie not only INSERT but also DELETE and UPDATE. You also should read #Nicarus comment about using NEW (and OLD) in triggers.
I have altered it to match your schema, but didn't test it...
CREATE OR REPLACE FUNCTION FunctionTrigger2c()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
CASE TG_OP
WHEN 'INSERT' THEN
UPDATE
Customer2c
SET
Number_Of_Items = Number_Of_Items + NEW.OrderQty
FROM
SalesOrderHeader2c
WHERE
Customer2c.CustomerID = SalesOrderHeader2c.CustomerID AND
SalesOrderHeader2c.SalesOrderID = NEW.SalesOrderID AND
SalesOrderDetailID = NEW.SalesOrderDetailID;
WHEN 'UPDATE' THEN
UPDATE
Customer2c
SET
Number_Of_Items = Number_Of_Items - OLD.OrderQty
FROM
SalesOrderHeader2c
WHERE
Customer2c.CustomerID = SalesOrderHeader2c.CustomerID AND
SalesOrderHeader2c.SalesOrderID = OLD.SalesOrderID AND
SalesOrderDetailID = OLD.SalesOrderDetailID;
UPDATE
Customer2c
SET
Number_Of_Items = Number_Of_Items + NEW.OrderQty
FROM
SalesOrderHeader2c
WHERE
Customer2c.CustomerID = SalesOrderHeader2c.CustomerID AND
SalesOrderHeader2c.SalesOrderID = NEW.SalesOrderID AND
SalesOrderDetailID = NEW.SalesOrderDetailID;
WHEN 'DELETE' THEN
UPDATE
Customer2c
SET
Number_Of_Items = Number_Of_Items - OLD.OrderQty
FROM
SalesOrderHeader2c
WHERE
Customer2c.CustomerID = SalesOrderHeader2c.CustomerID AND
SalesOrderHeader2c.SalesOrderID = OLD.SalesOrderID AND
SalesOrderDetailID = OLD.SalesOrderDetailID;
END CASE;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
UPDATE also work well if you change the SalesOrderID :)
Notice, that this trigger returns NULL and it should be set as AFTER INSERT OR UPDATE OR DELETE:
CREATE TRIGGER Trigger2c
AFTER INSERT OR UPDATE OR DELETE
ON SalesOrderDetail2c
FOR EACH ROW
EXECUTE PROCEDURE FunctionTrigger2c();

Related

Update another table through a trigger where new value is the result of a SELECT query

I have these tables:
Users
Skills (name - string, count - integer)
Has_skills (skill_id - skills.id, user_id users.id)
Has_skills is a many to many table between the first two through these FK:
user_id (users.id) and skill_id (skills.id).
What I want to do is update the count column inside skills when a new row is inserted into has_skills. I want to do this through an update trigger on table has_skills. The new value for count I will get through a select query:
SELECT COUNT(*) AS cnt FROM skills
JOIN has_skills hs ON skills.id = hs.skill_id
WHERE hs.skill_id = 1;
The ID above is hardcoded (1), but it works.
I also tested this code in isolation, and it works (although hardcoded, as well):
UPDATE skills
SET count = subquery.cnt
FROM (
SELECT COUNT(*) AS cnt FROM skills
JOIN has_skills hs ON skills.id = hs.skill_id
WHERE hs.skill_id = 1
) AS subquery
WHERE skills.id = 1;
RETURN NEW;
Alright, so here's probably where the problem is. Below is the trigger function and also the trigger itself.
Function:
CREATE OR REPLACE FUNCTION update_skill_count() RETURNS trigger AS
$func$
BEGIN
UPDATE skills
SET count = subquery.cnt
FROM (
SELECT COUNT(*) AS cnt FROM skills
JOIN has_skills hs ON skills.id = hs.skill_id
WHERE hs.skill_id = NEW.skill_id
) AS subquery
WHERE skills.id = NEW.skill_id;
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
Trigger:
CREATE TRIGGER on_has_skills_insert
AFTER INSERT ON has_skills
FOR EACH ROW
EXECUTE PROCEDURE update_skill_count();
I successfully create the function and trigger, but when I insert new data into has_skills, it doesn't change the count column inside skills. What could be the problem?
There's no need for a select in the trigger function at all. The key for the skill table is directly available in new.skill_id so just use it directly:
-- trigger function and trigger
create or replace function update_skill_count()
returns trigger
as $func$
begin
update skills sk
set count = count+1
where sk.skill_id = new.skill_id;
return new;
end;
$func$ language plpgsql;
create trigger on_has_skills_insert
after insert on has_skills
for each row
execute procedure update_skill_count();
I'm not much familiar with postgresql, but having understanding of Oracle and SQL Server, this looks to be a mutating trigger problem, which is: Trying to read from or write into the same table within a row level trigger on which the trigger is placed.
One of the ways to get rid of mutating trigger/table problem can be changing the row level trigger to a statement level trigger and changing the function accordingly. Here is a psudo code you can try out (not providing the exact tested code as I do not have Postgresql installed):
Function:
CREATE OR REPLACE FUNCTION update_skill_count() RETURNS trigger AS
$func$
BEGIN
UPDATE skills
SET count = subquery.cnt
FROM (
SELECT hs.skill_id, COUNT(*) AS cnt
FROM new_table hs
GROUP BY hs.skill_id
) AS subquery
WHERE skills.id = subquery.skill_id;
RETURN NULL;
END;
$func$ LANGUAGE plpgsql;
Trigger:
CREATE TRIGGER on_has_skills_insert
AFTER INSERT ON has_skills
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE update_skill_count();

Creating trigger in PostgreSQL

I have 2 tables in PostgreSQL
Table A : https://2.pik.vn/201819cbcc97-8d59-4d3f-9e47-38b5e33d00df.jpg
Table B : https://2.pik.vn/20186c925ba2-7c9c-4253-ba28-497df1465b4f.jpg
I want to create a trigger in PostgeSQL so if I update value in column area in table A, the value in area_LUA will automatically change in table B.
area_LUA = total sum (area)*100 with condition A.parent_id = B.parent_id and CODE ='LUA'
Can someone guide me how to create a trigger like this? Thank you so much
The following trigger should get the job done.
Trigger area_lua will execute after each update on table A. Trigger function update_area_lua checks if the value of area changed, and accordingly updates column area_LUA in table B, with respect to the given parent_id.
CREATE OR REPLACE FUNCTION update_area_lua()
RETURNS trigger AS
$BODY$
BEGIN
IF NEW.area <> OLD.area THEN
UPDATE B
SET area_LUA = (
SELECT SUM(area) * 100
FROM A
WHERE parent_id = B.parent_id AND CODE ='LUA'
)
WHERE parent_id = NEW.parent_id;
END IF;
RETURN NEW;
END;
$BODY$
CREATE TRIGGER area_lua
AFTER UPDATE
ON A
FOR EACH ROW
EXECUTE PROCEDURE update_area_lua();

Oracle SQL Trigger with Conditional Update based on Information in Separate Table

I have two tables:
cost_bank (this is where I want the trigger)
master_data (this table has information I need to check)
I want to build an "after insert" trigger on cost_bank which runs when the newly inserted cost_bank.work_code is in the master_data table with a master_data.charge_type = 'EX'. If that scenario is true, I want the trigger to update the newly added row only (can specify by ID) in cost_bank with the value from master_data.r_cost_center for cost_bank.cost_center.
Here is what I have so far:
CREATE OR REPLACE TRIGGER expense_charge_cost_center
AFTER INSERT ON cost_bank FOR EACH ROW
WHEN (new.work_code in (select work_code from master_data where charge_type = 'EX'))
BEGIN
update cost_bank set cost_center = (select r_cost_center
from master_data where work_code = new.work_code) where id = new.id;
END;
Any insight is appreciated
If you want to modify the same table, you want a before insert trigger:
CREATE OR REPLACE TRIGGER expense_charge_cost_center
BEFORE INSERT ON cost_bank FOR EACH ROW
WHEN (new.work_code in (select work_code from master_data where charge_type = 'EX'))
BEGIN
select r_cost_center into :new.cost_center
from master_data
where work_code = new.work_code;
END;
Actually, I'm not sure if subqueries work with when, so you might need:
CREATE OR REPLACE TRIGGER expense_charge_cost_center
BEFORE INSERT ON cost_bank FOR EACH ROW
BEGIN
if (new.work_code in (select work_code from master_data where charge_type = 'EX')) then
select r_cost_center into :new.cost_center
from master_data
where work_code = new.work_code; -- I'm guessing you also want charge_type = 'EX'
end if;
END;

Merge statement issue in oracle

I came from Microsoft SQL environment.I have two tables tak_ne and tak_beb and my requirement was to insert values from tak_beb to tak_ne if value is not present,if it is present just update.So i made a merge statement as shown below.But the problem now i am facing is veryday 50000 count is getting increment for sequence number.Oracle is stable database, and i don't know why they made it like that.So i create a Function and prevented incrementing sequence number.My question is ,is it a right approach by creating function.Following is what i did
merge into tak_ne a using tak_beb b ON (a.NAME=b.NAME)
When matched then
Update
Set a.AC_NO = b.AC_NO
a.LOCATION = b.LOCATION
a.MODEL = b.MODEL
When not matched then
insert
(
sl_no,
AC_NO,
LOCATION
MODEL
)
Values
(
s_slno_nextval
b.AC_NO
b.LOCATION
b.MODEL
)
and then i created a function
CREATE OR REPLACE FUNCTION s_slno_nextval
RETURN NUMBER
AS
v_nextval NUMBER;
BEGIN
SELECT s_emp.nextval
INTO v_nextval
FROM dual;
RETURN v_nextval;
END;
Oracle uses this approach to generate unique id for each row inserted by a statement. Your TAK_BEB table has probably 50000 rows, so the sequence is incremented 50000 times.
To hide increment into a function does not help. Function is called AND EXECUTED for every row, it increments sequence for 50000 times again. And it adds overhead with 50000 selects from dual table.
If you really need to use ONE value from sequence for ALL rows inserted by statement, use package variable:
create package single_id_pkg is
id Number;
function get_id return number;
end;
/
create or replace package body single_id_pkg is
function get_id return number is
begin
return id;
end;
end;
/
Now use for example before statement trigger on table to set the variable:
create trigger tak_ne_BSI_trg
before insert
on tak_ne
begin
select s_emp.nextval
into single_id_pkg.id
from dual;
end;
Insert trigger has one disadvantage - with MERGE clause it fires even if the statement does only updates rows (see https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:25733900083512). If it is a problem, you have to initialize the variable in other way.
Next modify your statement to use a package variable:
merge into tak_ne a
using tak_beb b
on (a.NAME=b.NAME)
when matched then
update
set a.AC_NO = b.AC_NO
a.LOCATION = b.LOCATION
a.MODEL = b.MODEL
when not matched then
insert (sl_no,
AC_NO,
LOCATION,
MODEL)
values (single_id_pkg.get_id
b.AC_NO,
b.LOCATION,
b.MODEL)
In Oracle standard way to use autoincrement field is by using sequences. And of course it will increment sequence number each time you want to use it.
But you can omit calling sequence_name.nextval, hiding it in trigger it is considered the standard approach also.
CREATE OR REPLACE EDITIONABLE TRIGGER TAK_NE_ID_TR"
BEFORE INSERT ON tak_ne
FOR EACH ROW
BEGIN
IF :old.sl_no IS NULL THEN
:new.sl_no := s_emp.nextval;
END IF;
END;
If you want to add same id for a batch of your inserts you can use global temporary table for saving it. For example, like this:
create global temporary table tak_ne_id ("id" number) on commit delete rows
create or replace trigger tak_ne_BSI_trg
before insert
on tak_ne
begin
insert into tak_ne_id("id")
values(s_emp.nextval);
end
create or replace TRIGGER TAK_NE_ID_TR
BEFORE INSERT ON tak_ne
FOR EACH ROW
BEGIN
if :old.sl_no is null then
SELECT "id"
INTO :new.sl_no
FROM tak_ne_id;
end if;
END;
Then you can use you merge as before, and without calling nextval:
merge into tak_ne a using tak_beb b ON (a.NAME=b.NAME)
When matched then
update
set a.AC_NO = b.AC_NO,
a.LOCATION = b.LOCATION,
a.MODEL = b.MODEL
When not matched then
insert
(
AC_NO,
LOCATION,
MODEL
)
Values
(
b.AC_NO,
b.LOCATION,
b.MODEL
);

trigger to update particular column in table A after update on table A

I am using postgresql and I want to update a column by summing up other column in the table.
EX: table name is A. When col1 or col2 or any column value in table A
is updated then the value of col6 should also be updated as
col6=(col1+col2+col3)
for this i have written a update trigger as below.
CREATE FUNCTION update_total2() RETURNS TRIGGER AS $_$
BEGIN
UPDATE hr_contract SET "x_TOTAL"=(NEW.x_othr_allow+NEW.x_med_allw+NEW."x_LTA"+NEW.wage+NEW.supplementary_allowance) WHERE id = OLD.id;
RETURN OLD;
END $_$ LANGUAGE 'plpgsql';
CREATE TRIGGER hr_contract_after_update
AFTER update ON hr_contract
FOR EACH ROW
EXECUTE PROCEDURE update_total2();
It is giving error as below..
ERROR: stack depth limit exceeded
HINT: Increase the configuration parameter "max_stack_depth" (currently 2048kB), after ensuring the platform's stack depth limit is adequate.
CONTEXT: SQL statement "SELECT 1 FROM ONLY "public"."res_users" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR SHARE OF x"
SQL statement "UPDATE hr_contract SET "x_TOTAL"=(NEW.x_othr_allow+OLD.x_med_allw+OLD."x_LTA"+OLD.wage+OLD.supplementary_allowance) WHERE id = OLD.id"
PL/pgSQL function update_total_result() line 3 at SQL statement
SQL statement "UPDATE hr_contract SET "x_TOTAL"=(NEW.x_othr_allow+OLD.x_med_allw+OLD."x_LTA"+OLD.wage+OLD.supplementary_allowance) WHERE id = OLD.id"
Any help is really very useful...
Try to check if any thing has changed on that row, and set the value of field x_TOTAL if any:
CREATE FUNCTION update_total2() RETURNS TRIGGER AS $_$
BEGIN
--Check if any of columns has been updated:
if tg_op='UPDATE' and old <> new then
NEW."x_TOTAL"= NEW.x_othr_allow+NEW.x_med_allw+NEW."x_LTA"+NEW.wage+NEW.supplementary_allowance);
end if;
RETURN NEW;
END $_$ LANGUAGE 'plpgsql';
--Associate triger to table on BEFORE update events
CREATE TRIGGER hr_contract_after_update
before update ON hr_contract
FOR EACH ROW
EXECUTE PROCEDURE update_total2();
Trigger documentation
I'm not familiar with postgresql syntax but the problem is that you are doing a recursive update. Your function update_total2 must not use UPDATE to the same table being updated on the trigger.
Please check documentation for details, all you have to do in the trigger itself is something like:
FOR EACH ROW
NEW.x_TOTAL = NEW.x_othr_allow + NEW.x_med_allw + NEW."x_LTA" + NEW.wage + NEW.supplementary_allowance