Eliminating the value of a field on a Postgres Trigger - postgresql

I want to create a Postgres trigger that will eliminate a whole field from NEW like in (because this column will be filled automatically by the database and I do not want to change the assigned value by the database):
CREATE OR REPLACE FUNCTION befo_insert_for_auto_inc_numeric_ids()
RETURNS trigger AS
$$
BEGIN
NEW.Id = NULL; <---- Want to eliminate Id field from NEW
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
Is this possible?

Assigning NULL to the column will achieve the opposite of what you want: if will manually set the column's value to NULL which will fail if the column is defined as NOT NULL
If you want to make sure that the ID is generated always regardless of what was provided in the in the actual INSERT statement, you can use nextval() inside the trigger:
CREATE OR REPLACE FUNCTION before_insert_for_auto_inc_numeric_ids()
RETURNS trigger AS
$$
BEGIN
NEW.id := nextval('id_column_seq');
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Replace id_column_seq with the name of the sequence attached to that column.
Note that this will most probably cause confusion at some point because values passed in the INSERT statement might not be the value stored in the table.

Related

Alter PostreSQL column into a GENERATED ALWAYS column

I have an already made table:
cotizacion(idCot(PK), unit_price,unit_price_taxes)
I need to convert unit_price_taxes into a generated column that is equal to unit_price*1.16. The issue is I can't find the alter table statement which will give me this. Dropping table and creating it again is not an option as this table is already deeply linked with the rest of the database and reinserting all records is not an option at this point.
I tried the following:
ALTER TABLE cotizacion
alter column unit_price_taxes set
GENERATED ALWAYS AS (unit_price*1.16) STORED;
But it's not working. Does anybody know how to get this done or if it's even possible? I would like to avoid creating a new column.
Thanks!
**EDIT:
I also tried the following trigger implementation:
CREATE OR REPLACE FUNCTION calculate_price_taxes()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare pu money;
begin
select unit_price from cotizacion into pu
where idCot = new."idCot";
update cotizacion
set unit_price_taxes = pu * (1.16)
where idCot = new."idCot";
return new;
end;
$function$
;
And the trigger delcaration:
Create or replace trigger price_taxes
after update on cotizacion
for each row
execute procedure
calculate_price_taxes()
The most probable reason for your trigger to go into an infinite recursion is that you are running an UPDATE statement inside the trigger - which is the wrong thing to do. Create a before trigger and assign the calculated value to the new record:
create trigger update_tax()
returns trigger
as
$$
begin
new.unit_price_taxes := unit_price * 1.16;
return new;
end;
$$
language plpgsql;
create trigger update_tax_trigger()
before update or insert on cotizacion
for each row execute procedure update_tax();
The only way to "convert" that column to a generated one, is to drop it and add it again:
alter table cotizacion
drop unit_price_taxes;
alter table cotizacion
add unit_price_taxes numeric generated always as (unit_price*1.16) stored;
Note that this will rewrite the entire table which will block access to it. Adding the trigger will be less invasive.

Trigger | how to delete row instead of update based on cell value

Postgresql 10/11.
I need to delete row instead of update in case if target cell value is null.
So I created this trigger function:
CREATE OR REPLACE FUNCTION delete_on_update_related_table() RETURNS trigger
AS $$
DECLARE
refColumnName text = TG_ARGV[0];
BEGIN
IF TG_NARGS <> 1 THEN
RAISE EXCEPTION 'Trigger function expects 1 parameters, but got %', TG_NARGS;
END IF;
EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE $1 = ''$2'''
USING refColumnName, OLD.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
And a BEFORE UPDATE trigger:
CREATE TRIGGER proper_delete
BEFORE UPDATE OF def_id
ON public.definition_products
FOR EACH ROW
WHEN (NEW.def_id IS NULL)
EXECUTE PROCEDURE delete_on_update_related_table('def_id');
Table is simple:
id uuid primary key
def_id uuid not null
Test:
UPDATE definition_products SET
def_id = NULL
WHERE id = 'f47415e8-6b00-4c65-aeb8-cadc15ca5890';
-- rows affected 0
Documentation says:
Row-level triggers fired BEFORE can return null to signal the trigger
manager to skip the rest of the operation for this row (i.e.,
subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does
not occur for this row).
Previously, I used a RULE instead of the trigger. But there is no way to use WHERE & RETURNING clause in same rule.
You need an unconditional ON UPDATE DO INSTEAD rule with a RETURNING clause
So, is there a way?
While Jeremy's answer is good, there is still room for improvement.
Problems
You need to be very accurate in the definition of the objective. Your statement:
I need to delete row instead of update in case if target cell value is null.
... does not imply that the column was changed to NULL in the UPDATE at hand. Might have been NULL before, like, before you implemented the trigger. So not:
BEFORE UPDATE OF def_id ON public.definition_products
But just:
BEFORE UPDATE ON public.definition_products
Of course, if the column is defined NOT NULL (as it probably should be), there is no effective difference - except for the noise and an additional point of failure. The manual:
A column-specific trigger (one defined using the UPDATE OFcolumn_name syntax) will fire when any of its columns are listed as targets in the UPDATE command's SET list. It is possible for a column's value to change even when the trigger is not fired, because changes made to the row's contents by BEFORE UPDATE triggers are not considered.
Also, nothing in your question indicates the need for dynamic SQL. (That would be the case if you wanted to reuse the same trigger function for multiple triggers on different tables. And even then it's often better to just create several distinct trigger functions for multiple reason: simpler, faster, less error-prone, easier to read & maintain, ...)
As for "error-prone": your original dynamic statement was just invalid:
EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE $1 = ''$2'''
USING refColumnName, OLD.id;
Can't pass a column name as value (refColumnName).
Can't put single quotes around $2, which is passed as value and hence needs no quoting.
An unqualified, unquoted TG_TABLE_NAME can go terribly wrong, which is especially critical for a heavy-weight function that deletes rows.
Jeremy's version fixes most, but still features the unqualified TG_TABLE_NAME.
This would be good:
EXECUTE format('DELETE FROM %s WHERE %I = $1', TG_RELID::regclass, refColumnName) -- refColumnName still unquoted
USING OLD.id;
Or:
EXECUTE format('DELETE FROM %I.%I WHERE %I = $1', TG_TABLE_SCHEMA, TG_TABLE_NAME, refColumnName)
USING OLD.id;
Related:
Why does a PostgreSQL SELECT query return different results when a schema name is specified?
Table name as a PostgreSQL function parameter
Solution
Simpler trigger function:
CREATE OR REPLACE FUNCTION delete_on_update_related_table()
RETURNS trigger AS
$func$
BEGIN
DELETE FROM public.definition_products WHERE id = OLD.id; -- def_id?
RETURN NULL;
END
$func$ LANGUAGE plpgsql;
Simpler trigger:
CREATE TRIGGER proper_delete
BEFORE UPDATE ON public.definition_products
FOR EACH ROW
WHEN (NEW.def_id IS NULL) -- that's the defining condition!
EXECUTE PROCEDURE delete_on_update_related_table(); -- no parameter
You probably want to use OLD.id, not OLD.def_id. (The row to delete is best defined by it's PK, not by the column changed to NULL.) But that's not entirely clear.
This works for me, with a few small changes:
CREATE OR REPLACE FUNCTION delete_on_update_related_table() RETURNS trigger
AS $$
DECLARE
refColumnName text = quote_ident(TG_ARGV[0]);
BEGIN
IF TG_NARGS <> 1 THEN RAISE EXCEPTION 'Trigger function expects 1 parameters, but got %', TG_NARGS; END IF;
EXECUTE format('DELETE FROM %s WHERE %s = %s', quote_ident(TG_TABLE_NAME), refColumnName, quote_literal(OLD.id));
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- create trigger
CREATE TRIGGER proper_delete
BEFORE UPDATE OF def_id
ON public.definition_products
FOR EACH ROW
WHEN (NEW.def_id IS NULL)
EXECUTE PROCEDURE delete_on_update_related_table('id'); --Note id, not def_id

Keep default behavior when new trigger is inserted SQL

Let's say I have contraints on a table. When I try to insert something there which doesn't have the correct data, I get an error in the console saying that this and that IC was violated.
ERROR: null value in column "column" violates not-null constraint
DETAIL: Failing row contains (0, null, null, null, null).
Now if I add a this:
CREATE OR REPLACE FUNCTION trigger_function()
RETURNS trigger AS $BODY$
BEGIN
raise notice 'test';
return null;
END;
$BODY$ LANGUAGE plpgsql;
create trigger test_trigger
before update or insert
on tablename
for each row
execute procedure trigger_function();
And execute the same thing as before I instead get this:
NOTICE: test
INSERT 0 0
I know that this trigger isn't really useful but I'm still learning how they work.
Is it possible to keep the previous error messages, and add whatever I did in the trigger_function after it? Why does it replace all the default messages?
Quote from the manual
Row-level triggers fired BEFORE can return null to signal the trigger manager to skip the rest of the operation for this row (i.e., subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does not occur for this row)
(emphasis mine)
Because your trigger returns NULL the insert is never actually done and thus no constraint can be violated.
The output in the psql console
INSERT 0 0
tells you that no rows have been inserted. The second value is the number of rows inserted.
If you want a trigger that "does nothing", you need to use return new
CREATE OR REPLACE FUNCTION trigger_function()
RETURNS trigger
AS $BODY$
BEGIN
raise notice 'test';
return new; --<< here
END;
$BODY$ LANGUAGE plpgsql;

PostgreSQL: Checking for NEW and OLD in a function for a trigger

I want to create a trigger which counts rows and updates a field in an other table. My current solution works for INSERT statements but failes when I DELETE a row.
My current function:
CREATE OR REPLACE FUNCTION update_table_count()
RETURNS trigger AS
$$
DECLARE updatecount INT;
BEGIN
Select count(*) into updatecount
From source_table
Where id = new.id;
Update dest_table set count=updatecount
Where id = new.id;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
The trigger is a pretty basic one, looking like.
CREATE TRIGGER count_trigger
AFTER INSERT OR DELETE
ON source_table
FOR EACH ROW
EXECUTE PROCEDURE update_table_count();
When I excute a DELETE statement the following error occurs:
ERROR: record "new" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
I know one solution could be to create just one set of trigger and function for the DELETE and one for the INSERT statement. But I want to do it a bit more elegant and want to know, if there is a solution to check if NEW or OLD is present in the current context and just implement an IF ELSE block. But I dont know how to check for this context sensitive items.
Thanks for your help
The usual approach to make a trigger function do different things depending on how the trigger was fired is to check the trigger operation through TG_OP
CREATE OR REPLACE FUNCTION update_table_count()
RETURNS trigger AS
$$
DECLARE
updatecount INT;
BEGIN
if tg_op = 'UPDATE' then
select count(*) into updatecount from source_table where id = new.id;
update dest_table set count=updatecount where id = new.id;
elsif tg_op = 'DELETE' then
... do something else
end if;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Unrelated, but: the language name is an identifier. Do not quote it using single quotes.
From PostgreSQL's documentation:
NEW
Data type RECORD; variable holding the new database row for INSERT/UPDATE operations in row-level triggers. This variable is null in statement-level triggers and for DELETE operations.
OLD
Data type RECORD; variable holding the old database row for UPDATE/DELETE operations in row-level triggers. This variable is null in statement-level triggers and for INSERT operations.
So, for example, if NEW is NULL, then the trigger was invoked on DELETE.

Calling a function for each updated row in postgresql

I have a sql UPDATE statement in a plpgsql function. I now want to call the pg_notify function for each updated row and am uncertain if my solution is the best possibility.
I am not aware of any position in the UPDATE statement itself where I could apply the function. I don't think it is possible in the SET part and if I would apply the function in the WHERE part, it would be applied to each row as it is checked and not only the updated rows, correct?
I therefore thought I could use the RETURNING part for my purposes and designed the function like this:
CREATE OR REPLACE FUNCTION function_name() RETURNS VOID AS $BODY$
BEGIN
UPDATE table1
SET a = TRUE
FROM table2
WHERE table1.b = table2.c
AND <more conditions>
RETURNING pg_notify('notification_name', table1.pk);
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE;
Unfortunately this gave me an error saying that I am not using or storing the return value of the query anywhere. I therefore tried putting PERFORM in front of the query but this seemed to be syntactically incorrect.
After trying different combinations with PERFORM my ultimate solution is this:
CREATE OR REPLACE FUNCTION function_name() RETURNS VOID AS $BODY$
DECLARE
dev_null INTEGER;
BEGIN
WITH updated AS (
UPDATE table1
SET a = TRUE
FROM table2
WHERE table1.b = table2.c
AND <more conditions>
RETURNING pg_notify('notification_name', table1.pk)
)
SELECT 1 INTO dev_null;
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE;
This works as it is supposed to, but I feel like there should be a better solution which does not temporarily store a useless result and does not use a useless variable.
Thank you for your help.
** EDIT 1 **
As can be seen in #pnorton 's answer, a trigger would do the trick in most cases. For me, however, it is not applicable as the receiver of the notifications also sometimes updates the table and I do not want to generate notifications in such a case
"I have a sql UPDATE statement in a plpgsql function. I now want to
call the pg_notify function for each updated row "
Ok I might be tempted to use a trigger Eg
CREATE TABLE foobar (id serial primary key, name varchar);
CREATE OR REPLACE FUNCTION notify_trigger() RETURNS trigger AS $$
DECLARE
BEGIN
PERFORM pg_notify('watch_tb_update', TG_TABLE_NAME || ',id,' || NEW.id );
RETURN new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER foobar_trigger AFTER INSERT ON foobar
FOR EACH ROW EXECUTE PROCEDURE notify_trigger();
LISTEN watch_tb_update;
INSERT into foobar(id, name) values(1,'test_name');
I've tested this and it works fine