How can I use 'BEFORE DELETE' trigger with conditions - sql

colleagues! I created 'product' table with the next query:
CREATE TABLE product (
id serial not null,
product_name text not null,
description varchar(50),
delivery_date timestamp,
warehouse jsonb
)
And I'm trying to use trigger before delete with the sort by list:
CREATE OR REPLACE FUNCTION product_delete_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF (OLD.product_name IN ('Milk','Egg','Cheese'))
THEN
DELETE FROM product WHERE product_name = OLD.product_name;
ELSE
RAISE EXCEPTION
'Value out of list. Fix the product_delete_trigger() function!';
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER delete_product_trigger
BEFORE DELETE ON product
FOR EACH ROW EXECUTE PROCEDURE product_delete_trigger();
How I got it I need to use 'OLD' special parameter , but If I when I use it, I have an issue:
ERROR: ERROR: Stack depth limit exceeded
HINT: Increase the configuration parameter "max_stack_depth" (the current value is 2048 KB), first making sure that the OS provides sufficient stack size.
Is it possible to do this by this query?
DELETE FROM product where product_name = 'Cheese';

There is no point in running DELETE in your BEFORE trigger – that happens anyway if you let the database have its course. So all you have to do is throw an error if you are unhappy with the proceedings:
CREATE OR REPLACE FUNCTION product_delete_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ((OLD.product_name IN ('Milk','Egg','Cheese')) IS NOT TRUE)
THEN
RAISE EXCEPTION
'Value out of list. Fix the product_delete_trigger() function!';
END IF;
/* proceed with the DELETE */
RETURN OLD;
END;
$$
LANGUAGE plpgsql;

Related

PostgreSQL trigger not executing after update [duplicate]

Here is my trigger function
CREATE OR REPLACE FUNCTION test_table_insert()
RETURNS TRIGGER
AS $$
BEGIN
IF NEW.id IS NULL THEN
RAISE EXCEPTION 'id is null';
END IF;
UPDATE e_sub_agreement SET ro_id = NEW.id WHERE id = NEW.id;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test_table_insert AFTER INSERT ON e_sub_agreement FOR EACH ROW EXECUTE PROCEDURE test_table_insert();
The problem is that it doesn't update table e_sub_agreement. I checked NEW value and everything is good. It returns with the new id. If I change where statement id = "some existing id in table", then it works. It changes ro_id to the new id. How is it possible? My guess is that data has not been inserted into table and trigger function can't find row with the given id. But it's not how trigger's after insert function works. What's the magic?
An AFTER trigger can not change anything. Running an additional UPDATE is also quite inefficient. Change this to a BEFORE trigger and assign the value you want:
CREATE OR REPLACE FUNCTION test_table_insert()
RETURNS TRIGGER
AS $$
BEGIN
IF NEW.id IS NULL THEN
RAISE EXCEPTION 'id is null';
END IF;
NEW.ro_id := NEW.id;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test_table_insert
BEFORE INSERT ON e_sub_agreement
FOR EACH ROW EXECUTE PROCEDURE test_table_insert();
Note that the NOT NULL check is better done by defining the column as NOT NULL.

how to fix this trigger error in PostgreSQL [duplicate]

This question already has answers here:
syntax Error in PostgreSQL when I try to create Trigger
(2 answers)
Closed last year.
I am getting a syntax error for my code which I can't understand why
am I missing something?
also, I read this I did not get my answer
syntax Error in PostgreSQL when I try to create Trigger
CREATE TRIGGER MyExampleName AFTER INSERT ON baskets
FOR EACH ROW BEGIN
UPDATE customers
SET customers.credit=customers.credit - NEW.amount
WHERE customers.id = NEW.customer_id;
END;
and tried it like this as well:
CREATE TRIGGER MyExampleName AFTER INSERT ON baskets
FOR EACH ROW AS $$ BEGIN
UPDATE customers
SET customers.credit=customers.credit - NEW.amount
WHERE customers.id = NEW.customer_id;
END;
$$ LANGUAGE plpgsql;
Error:
ERROR: syntax error at or near "BEGIN"
LINE 2: FOR EACH ROW BEGIN
^
SQL state: 42601
Character: 67
I'd say the first comment on your question pretty much covers it all. You cannot put the trigger code in the trigger body, you must first create a separate function and include the function call inside the trigger body.
This example comes directly from the Postgres docs:
-- 1. Create the function that does what you need
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when they must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
-- 2. Create the trigger with the 'EXECUTE FUNCTION function_name' part
-- replacing the actual function name from step 1.
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE FUNCTION emp_stamp();

encrypt password trigger

I've tried to encrypt a password in a PostgreSQL database by using a trigger but I can't get it to work, and I don't know where my mistake is.
Here is the code:
CREATE TABLE mms_user (
uid serial,
mail text NOT NULL,
passwd text NOT NULL,
usertype integer NOT NULL,
PRIMARY KEY (uid)
);
CREATE FUNCTION encrypt_password() RETURNS TRIGGER AS $$
BEGIN
NEW.passwd = digest(NEW.passwd, 'sha1');
RETURN NEW; END; $$LANGUAGE 'plpgsql';
CREATE TRIGGER encrypt_userdata AFTER INSERT ON mms_user EXECUTE PROCEDURE encrypt_password();
INSERT INTO mms_user values (default, 'who', 'me', 1);
It says this when executing:
ERROR: record "new" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
CONTEXT: PL/pgSQL function encrypt_password() line 3 at assignment
How do I access the record I'm inserting if it's not with NEW?
Security and cryptographic considerations aside (see comments), to address your actual PL/pgSQL question:
The trigger function is basically ok. Some cleanup:
CREATE FUNCTION encrypt_password()
RETURNS TRIGGER AS
$func$
BEGIN
NEW.passwd := digest(NEW.passwd, 'sha1'); -- or some other function?
RETURN NEW;
END
$func$ LANGUAGE plpgsql; -- don't quote the language name
But to make it work, you have to make the trigger BEFORE INSERT:
CREATE TRIGGER encrypt_userdata
BEFORE INSERT ON mms_user
EXECUTE PROCEDURE encrypt_password();
I suggest the manual here and here.

How can this approach to solving circular dependencies in triggers (PostgreSQL) be improved?

Let's say I have two tables, for the simplicity, let them be authors and pages. Listing only the interesting attributes like this:
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
active BOOLEAN NOT NULL DEFAULT true, -- set by the author himself
banned BOOLEAN NOT NULL DEFAULT false, -- set to true by the site administrator
visible BOOLEAN, -- set automatically in a trigger and used by the platform
-- when deciding whether to show this author or not.
-- Equals to (active and not banned).
visible_pages INT -- counter also updated automatically
);
CREATE TABLE pages (
id SERIAL PRIMARY KEY,
ref_author INT NOT NULL,
draft BOOLEAN DEFAULT true,
archived BOOLEAN DEFAULT false, -- not draft but not published anymore
visible BOOLEAN -- (authors.visible and not (draft or archived))
);
Now, when author publishes a page or archives it, the counter needs to be updated. When an author gets banned or suspends their account, the pages need to turn invisible. All that is done so that the frontend only cares for the visible flag and doesn't need to waste cycles to calculate it on the fly.
The straightforward (and somewhat naïve) way to make these triggers is as follows (the function names say it all, and trigger creation is omitted for brevity):
CREATE OR REPLACE FUNCTION before_update_authors() RETURNS TRIGGER AS $$
BEGIN
NEW.visible := (NEW.enabled AND NOT NEW.banned);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION after_update_authors() RETURNS TRIGGER AS $$
BEGIN
IF NEW.visible != OLD.visible THEN
UPDATE pages SET id = id WHERE ref_author = NEW.id; -- dummy call just to trigger the trigger
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION before_update_pages() RETURNS TRIGGER AS $$
DECLARE author_visible BOOLEAN;
BEGIN
SELECT visible INTO author_visible FROM authors WHERE id = NEW.ref_author;
NEW.visible := author_visible AND NOT (draft OR archived);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION after_update_pages() RETURNS TRIGGER AS $$
DECLARE counter INT;
BEGIN
IF OLD.visible <> NEW.visible THEN
IF NEW.visible THEN
counter := 1;
ELSE
counter := -1;
END IF;
UPDATE authors SET visible_pages = visible_pages + counter WHERE id = NEW.ref_author;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Looks straightforward, but in the best case it is slow, and in the worst case it would smash the stack and fail.
In MySQL we can get around this unfortunate situation by using #session_variables, so triggers can detect being called from triggers. PostgreSQL does not have such concept.
The best solution I have come up with so far is to use auxiliary columns and WHEN conditions. So my pages table grows an additional column, _updating_author of type BOOLEAN (default whatever but non-NULL).
Now the code for *_update_pages stays the same. The code to create trigger differs, though:
CREATE TRIGGER tg_before_update_pages BEFORE UPDATE ON pages WHEN (OLD._updating_author IS DISTINCT FROM NEW._updating_author) FOR EACH ROW EXECUTE PROCEDURE before_update_pages();
CREATE TRIGGER tg_after_update_pages AFTER UPDATE ON pages WHEN (OLD._updating_author IS DISTINCT FROM NEW._updating_author) FOR EACH ROW EXECUTE PROCEDURE after_update_pages();
And so is trigger function after_update_authors():
CREATE OR REPLACE FUNCTION after_update_authors() RETURNS TRIGGER AS $$
BEGIN
IF NEW.visible != OLD.visible THEN
UPDATE pages SET visible = (NEW.visible AND NOT (draft OR archived)), _updating_authors = NOT _updating_authors WHERE ref_author = NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
In this case, pages visibility is being set in batch and very fast, because no trigger will be called for individual rows in pages.
However, the need for auxiliary columns bothers me a bit in this approach, and WHEN conditions seem a bit clunky. So is the need to state the logic of what pages.visible is twice.
Can you please suggest an alternative, maybe more elegant, approach to solving this and similar problems?

SQL function, substract variable on another table when insert data

I have a relational database:
Examination (ExaminationID, DetectedDisease, Name)
Uses (ReagentID, ExaminationID, ReagentQuantity)
Reagent (ReagentID, Name, Stock, MinimumPermissibleStock)
Examined (InsuranceNumber, ExaminationID, ExaminationDate, Results, TakenResults?)
I want to create a function that when a new record of "examined" is added the quantity of the reagent that examination uses to be substracted from "reagent.stock". Also when reagent.minimumPermissibleStock is lower than stock to return a warning message.
For the second part of the problem i tried this, but it does not work:
create function warning() returns trigger AS $warning$
begin
if reagent.new.stock < reagent.minimumPermissibleStock then
raise exception 'Probably mistake';
end if;
return new;
end;
$warning$ LANGUAGE plpgsql;
Thanks in advance
EDIT
I tried that one but still it doesnt work:
CREATE FUNCTION log_examination2() RETURNS trigger AS $$
DECLARE
examID integer;
BEGIN
BEGIN
INSERT INTO examination (detecteddisease, ename) VALUES('disease1', 'name') RETURNING examinationID INTO examID;
INSERT INTO Uses VALUES(reagentID, examID, reagentQuantity);
UPDATE Reagent SET Stock = Stock - reagentQuantity WHERE ReagentID = reagentID;
RETURN new;
END;
END;
$$ LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER validate_quantity AFTER UPDATE OF Stock ON Reagent EXECUTE PROCEDURE log_examination2();
Triggers can only operate on the table/view to which they are attached--hence the error (I'm assuming you created the trigger as an AFTER UPDATE on Examined).
To actually solve the problem, what you want to do is create a transaction to represent the entire set of operations, and then attach the above function as a trigger AFTER UPDATE to Reagent.
For example:
CREATE FUNCTION log_examination(int reagentID, int reagentQuantity) AS $$
BEGIN
BEGIN;
examID := INSERT INTO Examination VALUE("disease1", "name") RETURNING ExaminationID;
INSERT INTO Uses VALUE(reagentID, examID, reagentQuantity);
UPDATE Reagent SET Stock = Stock - reagentQuantity WHERE ReagentID = reagentID;
COMMIT;
END;
$$ LANGUAGE plpgsql;
and
CREATE TRIGGER validate_quantity AFTER UPDATE OF Stock ON Reagent EXECUTE PROCEDURE warning();