I created a trigger that updates the number of species counted from the "effectif" table to the "citation" table.
It works well except when I delete the last row, the calculation does not perform and remains at the last state. For example, if I delete the last row on the "effectif" table that represents 6 species, I would still have 6 on my "citation" table where I'm suppose to find 0 or null.
Here is my trigger :
CREATE OR REPLACE FUNCTION data.del_eff_tot()
RETURNS trigger AS
$BODY$
BEGIN
IF OLD.effectif IS NOT NULL THEN
UPDATE data.citation
SET effectif =
(SELECT sum(a.effectif) FROM data.effectif a, data.citation b WHERE OLD.id_cit = a.id_cit AND OLD.id_cit = b.id)+COALESCE((SELECT a.effectif FROM data.effectif a, data.citation b WHERE OLD.id_cit = a.id_cit AND OLD.id_cit = b.id AND sexe = 'sexe_4_1'),0)
WHERE id IN (SELECT id_cit FROM data.effectif a, data.citation b WHERE OLD.id_cit = b.id AND b.id = a.id_cit);
END IF;
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION data.del_eff_tot()
OWNER TO postgres;
------------------------------
CREATE TRIGGER del_eff_tot
AFTER DELETE
ON data.effectif
FOR EACH ROW
EXECUTE PROCEDURE data.del_eff_tot();
Related
CREATE OR REPLACE FUNCTION public.flowrate7(
)
RETURNS TABLE(oms_id integer, flowrate numeric, chakno character varying)
LANGUAGE 'plpgsql'
COST 100
VOLATILE
ROWS 1000
AS $BODY$
declare temp_omsId integer;
declare temp_flowrate numeric(20,3);
declare temp_chakno varchar(100);
begin
DROP TABLE IF EXISTS tbl_oms;
DROP TABLE IF EXISTS tbl_calFlow;
CREATE temporary TABLE tbl_oms(omsid__ integer) ON COMMIT DELETE ROWS;
CREATE temporary TABLE tbl_calFlow(OmsId_ integer,FlowRate_ numeric(20,3),ChakNo_ varchar(100)) ON COMMIT DELETE ROWS;
insert into tbl_oms (select OmsId from MstOms);
while (select count(*) from tbl_oms) <> 0 LOOP
select temp_omsId = omsid__ from tbl_oms LIMIT 1;
temp_flowrate = (select (case when(InLetPressure > 0.5) then 1 else 0 end) from MstOms where OmsId = temp_omsId);
temp_chakno = (select ChakNo from MstOms where OmsId = temp_omsId);
insert into tbl_calFlow values (temp_omsId,temp_flowrate,temp_chakno);
delete from tbl_oms where omsid__ = temp_omsId;
END LOOP;
return query (select OmsId_,FlowRate_,ChakNo_ from tbl_calFlow);
end;
$BODY$;
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function flowrate7() line 19 at SQL statement
SQL state: 42601
You are retrieving values into the variables incorrectly. To store the value of a query result into a variable (or two) you need to use select .. into variable ...
CREATE OR REPLACE FUNCTION public.flowrate7()
RETURNS TABLE(oms_id integer, flowrate numeric, chakno character varying)
LANGUAGE plpgsql
AS $BODY$
declare
temp_omsId integer;
temp_flowrate numeric(20,3);
temp_chakno varchar(100);
begin
DROP TABLE IF EXISTS tbl_oms;
DROP TABLE IF EXISTS tbl_calFlow;
CREATE temporary TABLE tbl_oms(omsid__ integer) ON COMMIT DELETE ROWS;
CREATE temporary TABLE tbl_calFlow(OmsId_ integer,FlowRate_ numeric(20,3),ChakNo_ varchar(100)) ON COMMIT DELETE ROWS;
insert into tbl_oms
select OmsId
from MstOms;
while (select count(*) from tbl_oms) <> 0 LOOP
select omsid__
into temp_omsId --<< here
from tbl_oms LIMIT 1;
select case when inletpressure> 0.5 then 1 else 0 end, chakno
into temp_flowrate, temp_chakno --<< here
from MstOms
where omsid = temp_omsId;
insert into tbl_calFlow values (temp_omsId,temp_flowrate,temp_chakno);
delete from tbl_oms where omsid__ = temp_omsId;
END LOOP;
return query select omsid_, flowrate_, chakno_
from tbl_calflow;
end;
$BODY$;
However the processing of that function is unnecessarily complicated
if first copies all rows MstOms to tmp_MstOms
retrieve the ID for one row from tbl_oms
retrieve one row from MstOms calculating the flowrate
stores that one row in the temp table
deletes the just processed row from the (other) temp table
counts the number or rows in tbl_oms and if that is not zero moves on to the "next" row
This is an extremely inefficient and complicate way of calculating a simple value which will not scale well for large tables.
Doing a row-by-row processing of tables in the database is an anti-pattern to begin with (and doing that by deleting, inserting and counting rows is making that even slower).
This is not how things are done in SQL. The whole inefficient loop can be replaced with a single query which also allows you to change the whole thing to a SQL function.
CREATE OR REPLACE FUNCTION public.flowrate7()
RETURNS TABLE(oms_id integer, flowrate numeric, chakno character varying)
LANGUAGE sql
AS $BODY$
select omsid,
case when inletpressure> 0.5 then 1 else 0 end as flowrate,
chakno
from mstoms;
$BODY$;
Actually a view would be more appropriate for this.
create or replace view flowrate7
as
select omsid,
case when inletpressure> 0.5 then 1 else 0 end as flowrate,
chakno
from mstoms;
I have two tables, A and P.
A
________________
id | num_cars
----------
1 | 2
2 | 0
3 0
P
__________________________
id_driver | id_car
--------------------------
1 | Porsche
1 | BMW
A.id and P.id_driver referes to the same person. I created the below trigger. The idea is, every time I add a new row in P for an existing driver its correspondent row in A must be updated with the number of total cars owned by the person with that id.
CREATE OR REPLACE FUNCTION update_a() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE A a
SET num_cars = (SELECT COUNT(NEW.id_driver)
FROM P p
WHERE (a.id = p.id_driver AND a.id=NEW.id_driver));
ELSIF TG_OP = 'DELETE' THEN
UPDATE A a
SET num_cars = num_cars - 1
WHERE a.id = OLD.id_driver AND a.num_cars<>0;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER add_car
AFTER INSERT OR DELETE ON PARTICIPATION
FOR EACH ROW EXECUTE PROCEDURE update_a();
The trigger works fine when I add a row in B for a driver. However, if I then add a row for a different driver in B the rest of the rows in A are set back to 0. I would like the procedure to run only when A.id = P.id_driver. How can I do this?
The update query makes a cross product between A and P, and therefore updates the entire table, counting 0 cars most of the time.
You would need to restrict the update to the proper driver only, and also to compute the number of cars only for this driver:
UPDATE A a
SET num_cars = (SELECT COUNT(*)
FROM P p
WHERE p.id_driver = NEW.id_driver)
WHERE a.id = NEW.id_driver;
I have a table a with 3 triggers that insert, update, or delete corresponding rows in b whenever a row in a is inserted, updated, or deleted. All 3 triggers use the same trigger function p.
CREATE OR REPLACE FUNCTION p ()
RETURNS TRIGGER
AS $$
BEGIN
IF (TG_OP = 'INSERT') THEN
-- INSERT INTO b ...
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
-- UPDATE b ...
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
-- DELETE FROM b ...
RETURN NEW;
ELSE
RETURN NULL;
END IF;
END;
$$ LANGUAGE PLPGSQL;
CREATE TRIGGER i AFTER INSERT ON a FOR EACH ROW EXECUTE PROCEDURE p ();
CREATE TRIGGER u AFTER UPDATE ON a FOR EACH ROW EXECUTE PROCEDURE p ();
CREATE TRIGGER d AFTER DELETE ON a FOR EACH ROW EXECUTE PROCEDURE p ();
a also has a foreign key a1 into c (with primary key c1), and I would like to alter p in such a way that it enters the IF/ELSIF branches also depending on a column c2 in c: if that joined column changed, enter the INSERT and UPDATE branches; if it stayed the same, enter the UPDATE branch. In effect, something like this:
IF (TG_OP = 'INSERT') OR ((TG_OP = 'UPDATE') AND (oldC.c2 <> newC.c2)) THEN
-- ...
ELSIF (TG_OP = 'UPDATE') OR (oldC.c2 = newC.c2) THEN
-- ...
ELSIF (TG_OP = 'DELETE') OR ((TG_OP = 'UPDATE') AND (oldC.c2 <> newC.c2)) THEN
-- ...
ELSE
-- ...
END IF;
where oldC and newC would result from joins similar to these (with approx. syntax):
SELECT oldC.* FROM a, c AS oldC WHERE OLD.a1 = c.c1;
SELECT newC.* FROM a, c AS newC WHERE NEW.a1 = c.c1;
So what is needed in effect are two joins outside the IF statement, which would allow it to refer to oldC and newC (or something analogous). Is this possible and how would the altered version of p look (with correct PostgreSQL syntax)?
First off, there is no NEW in case of a DELETE, so RETURN NEW; doesn't make sense. This would raise an exception before Postgres 11, where this was changed:
In PL/pgSQL trigger functions, the OLD and NEW variables now read as NULL when not assigned (Tom Lane)
Previously, references to these variables could be parsed but not
executed.
It doesn't matter what you return for AFTER triggers anyway. Might as well be RETURN NULL;
There is no OLD in case of an INSERT, either.
You would only need a single trigger the way you have it right now:
CREATE TRIGGER a_i_u_d -- *one* trigger
AFTER INSERT OR UPDATE OR DELETE ON a
FOR EACH ROW EXECUTE FUNCTION p ();
However, I suggest separate trigger functions for INSERT, UPDATE and DELETE to avoid complications. Then you need three separate triggers, each calling its respective trigger function.
The case you want to add can only affect UPDATE. Nothing can "change" like you describe with INSERT or DELETE. And strictly speaking, what you ask for is impossible even for an UPDATE trigger:
depending on a column c2 in c: if that joined column changed ...
A trigger function on table a only sees a single snapshot of table c. There is no way to detect any "change" in that table. If you really meant to write:
depending on column a.a1: if that changed so that the referenced value c.c2 is different now ...
.. then there is a way:
Since a BEFORE trigger is less prone to endless loops and other complications, I demonstrate a BEFORE UPDATE trigger. (Changing to AFTER is trivial.):
CREATE OR REPLACE FUNCTION p_upbef()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF NEW.a1 <> OLD.a1 THEN -- assuming a1 is defined NOT NULL
IF (SELECT count(DISTINCT c.c2) > 1 -- covers possible NULL in c2 as well
FROM c
WHERE c.c1 IN (NEW.a1, OLD.a1)) THEN
-- do something
END IF;
END IF;
RETURN NEW;
END
$func$;
If a1 can be NULL and you need to track changes from / to NULL as well, you need to do more ...
Trigger:
CREATE TRIGGER upbef
BEFORE UPDATE ON a
FOR EACH ROW EXECUTE FUNCTION p_upbef ();
Since everything depends on a change in a.a1 now (and you don't have other things in the trigger) you can move the outer IF to the trigger itself (cheaper):
CREATE OR REPLACE FUNCTION p_upbef()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF (SELECT count(DISTINCT c.c2) > 1 -- covers NULL as well
FROM c
WHERE c.c1 IN (NEW.a1, OLD.a1)) THEN -- assuming a1 is NOT NULL!
-- do something
END IF;
RETURN NEW;
END
$func$;
Trigger:
CREATE TRIGGER upbef
BEFORE UPDATE OF a1 ON a -- !
FOR EACH ROW EXECUTE FUNCTION p_upbef();
It's not exactly the same, since an UPDATE involving the column a1 might actually leave the value unchanged , but it's good enough either way for our purpose: to only run the expensive check on c.c2 in relevant cases.
Related:
How To Avoid Looping Trigger Calls In PostgreSQL 9.2.1
Cycloning in trigger
My Check constraint is as follows:
ALTER TABLE tablename
ADD CONSTRAINT check_duplicate_rows
CHECK (reject_duplicate_rows(columnB, columnC, columnD) < 2);
I want the constraint to be evaluated only when you insert a record.
Currently it does for both the insert and update statements, The problem is that my system needs to update the inserted rows and the check constraint blocks the updates.
The reject_duplicate_rows function is as follows:
CREATE OR REPLACE FUNCTION reject_duplicate_rows(columnB integer, columnC integer, columnD integer)
RETURNS integer AS
$BODY$
DECLARE
results INTEGER := 1;
v_count INTEGER := 0;
BEGIN
IF columnC <> 23 THEN
RETURN results;
END IF;
SELECT total INTO v_count FROM
(SELECT columnB,
columnC,
columnD,
count(*) AS total
FROM table_name
WHERE B = columnB AND C = columnC AND D = columnD
GROUP BY 1, 2, 3)
as temp_table;
IF COALESCE(v_count, 0) = 0 THEN
RETURN results;
END IF;
IF v_count >= 1 THEN
results := 2;
END IF;
RETURN results;
EXCEPTION
WHEN OTHERS THEN
RETURN results;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 1000;
ALTER FUNCTION reject_duplicate_rows(integer, integer, integer)
OWNER TO postgres
Have you tried to create an UPDATE trigger? see Creating postgresql trigger
On Unique_violation exception how to update or delete the row which raised the exception
table code and insert
create table test
(
id serial not null,
str character varying NOT NULL,
is_dup boolean DEFAULT false,
CONSTRAINT test_str_unq UNIQUE (str)
);
INSERT INTO test(str) VALUES ('apple'),('giant'),('company'),('ap*p*le');
Function
CREATE OR REPLACE FUNCTION rem_chars()
RETURNS void AS
$BODY$
BEGIN
begin
update test set str=replace(str,'*','');
EXCEPTION WHEN unique_violation THEN
--what to do here to delete the row which raised exception or
--to update the is_dup=true to that row
end;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION rem_chars() OWNER TO postgres;
-- this will show you all the potential key collisions
SELECT a.id, a.str, b.id , b.str
FROM test a, test b
WHERE a.str = replace(b.str,'*','')
AND a.id < b.id;
-- this will delete them
DELETE FROM test WHERE id IN (
SELECT b.id
FROM test a, test b
WHERE a.str = replace(b.str,'*','')
AND a.id < b.id
);
I think the only solution is to do this in two steps:
UPDATE test
SET str = replace(str,'*','')
WHERE str NOT IN (SELECT replace(str,'*','') FROM test);
UPDATE test
SET is_dup = true
WHERE str IN (SELECT replace(str,'*','') FROM test);
At least I can't think of a more efficient way.