Getting error in creating trigger function using PostgreSQL? - postgresql

I am creating a trigger function to delete records automatically , if time limit of that data exceeds.
Here is the code-
CREATE FUNCTION delete_old_rows() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
row_count int;
BEGIN
DELETE FROM tablename1 WHERE timestamp < NOW() - INTERVAL '1 day';
IF found THEN
GET DIAGNOSTICS row_count = ROW_COUNT;
RAISE NOTICE 'DELETEd % row(s) FROM tablename1', row_count;
END IF;
RETURN NULL;
END;
$$;
I am getting following error-
ERROR: syntax error at or near "CREATE"
LINE 5: AS $BODY$CREATE FUNCTION delete_old_rows() RETURNS trigger.

While the trigger function itself looks you might want to table a good look at
Return NULL ;
As you did not post the actual trigger. From the Docs
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). If a nonnull value is returned then the
operation proceeds with that row value. Returning a row value
different from the original value of NEW alters the row that will be
inserted or updated. Thus, if the trigger function wants the
triggering action to succeed normally without altering the row value,
NEW (or a value equal thereto) has to be returned. To alter the row to
be stored, it is possible to replace single values directly in NEW and
return the modified NEW, or to build a complete new record/row to
return. In the case of a before-trigger on DELETE, the returned value
has no direct effect, but it has to be nonnull to allow the trigger
action to proceed. Note that NEW is null in DELETE triggers, so
returning that is usually not sensible. The usual idiom in DELETE
triggers is to return OLD.

Related

How to make a PostgreSQL constraint only apply to a new value

I'm new to PostgreSQL and really loving how constraints work with row level security, but I'm confused how to make them do what I want them to.
I have a column and I want add a constraint that creates a minimum length for a text column, this check works for that:
(length((column_name):: text) > 6)
BUT, it also then prevents users updating any rows where column_name is already under 6 characters.
I want to make it so they can't change that value TO that, but can still update a row where that is already happening, so they can change it as needed according to my new policy.
Is this possible?
BUT, it also then prevents users updating any rows where column_name is already under 6 characters.
Well, no. When you try to add that CHECK constraint, all existing rows are checked, and an exception is raised if any violation is found.
You would have to make it NOT VALID. Then yes.
You really need a trigger on INSERT or UPDATE that checks new values. Not as cheap and not as bullet-rpoof, but still pretty solid. Like:
CREATE OR REPLACE FUNCTION trg_col_min_len6()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF TG_OP = 'UPDATE'
AND OLD.column_name IS NOT DISTINCT FROM NEW.column_name THEN
-- do nothing
ELSE
RAISE EXCEPTION 'New value for column "note" must have at least 6 characters.';
END IF;
RETURN NEW;
END
$func$;
-- trigger
CREATE TRIGGER tbl1_column_name_min_len6
BEFORE INSERT OR UPDATE ON tbl
FOR EACH ROW
WHEN (length(NEW.column_name) < 7)
EXECUTE FUNCTION trg_col_min_len6();
db<>fiddle here
It should be most efficient to check in a WHEN condition to the trigger directly. Then the trigger function is only ever called for short values and can be super simple.
See:
Trigger with multiple WHEN conditions
Fire trigger on update of columnA or ColumnB or ColumnC
You can create separate triggers for Insert and Update letting each completely define when it should fired. If completely different logic is required for the DML action this technique allows writing dedicated trigger functions. In this case that is not required the trigger function reduces to raise exception .... See Demo
-- Single trigger function for both Insert and Delete
create or replace function trg_col_min_len6()
returns trigger
language plpgsql
as $$
begin
raise exception 'Cannot % val = ''%''. Must have at least 6 characters.'
, tg_op, new.val;
return null;
end;
$$;
-- trigger before insert
create trigger tbl_val_min_len6_bir
before insert
on tbl
for each row
when (length(new.val) < 6)
execute function trg_col_min_len6();
-- trugger before update
create trigger tbl_val_min_len6_bur
before update
on tbl
for each row
when ( length(new.val) < 6
and new.val is distinct from old.val
)
execute function trg_col_min_len6();

PostreSQL trigger disallow an action without exceptions

Trying to solve a problem for a university assignment. Each row in the table has a date field, and I need to disallow deleting rows that have this field less than 5 years old. Using a trigger is required and it must not raise exceptions. How can I do it? I tried something like this but it doesn't work:
create or replace function no_change()
returns trigger as $no_change$
begin
if current_timestamp - old.date <= interval '5y' then
new = old;
end if;
return new;
end;
$no_change$ language plpgsql;
create trigger no_change after delete on wiz
for each row execute procedure no_change();
Not tested, but should work.
create or replace function no_change()
returns trigger as $no_change$
begin
if current_timestamp - old.date <= interval '5y' then
RETURN NULL;
ELSE
RETURN OLD;
end if;
end;
$no_change$ language plpgsql;
create trigger no_change BEFORE delete on wiz
for each row execute procedure no_change();
In the PostgreSQL docs:
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).
<...>
In the case of a before-trigger on DELETE, the returned value has no direct effect, but it has to be nonnull to allow the trigger action to proceed. Note that NEW is null in DELETE triggers, so returning that is usually not sensible. The usual idiom in DELETE triggers is to return OLD.
So just return NULL in cases the date is less then 5 days old.

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;

How to update column value based on other column change within same table without any primary key column in table

I have a table called 'custom_manual_edit' with columns 'name', 'builder' and 'flag' in which there is no column with primary key.I have written a trigger when user update any change in builder column and that trigger will invoke a function that should update a flag column value to 10 for record for which builder value is changed
below is my trigger
CREATE TRIGGER builder_update_trigger_manual_custom_edits
AFTER UPDATE
ON edmonton.custom_manual_edit
FOR EACH ROW
WHEN (((old.builder)::text IS DISTINCT FROM (new.builder)::text))
EXECUTE PROCEDURE
edmonton.automated_builder_update_trigger_manual_custom_edits();
and my function
CREATE OR REPLACE FUNCTION
edmonton.automated_builder_update_trigger_manual_custom_edits()
RETURNS trigger AS
$BODY$
DECLARE
e record;
BEGIN
IF NEW.builder <> OLD.builder THEN
EXECUTE FORMAT('UPDATE edmonton.custom_manual_edit set builder_edit_flag = 10;
END IF;
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
I know this will update entire table flag column to 10 but how to update flag value for records for which builder value is changed.
Please check the documentation: 36.1. Overview of Trigger Behavior
Trigger functions invoked by per-statement triggers should always
return NULL. Trigger functions invoked by per-row triggers can return
a table row (a value of type HeapTuple) to the calling executor, if
they choose. A row-level trigger fired before an operation has the
following choices:
It can return NULL to skip the operation for the current row. This
instructs the executor to not perform the row-level operation that
invoked the trigger (the insertion, modification, or deletion of a
particular table row).
For row-level INSERT and UPDATE triggers only, the returned row
becomes the row that will be inserted or will replace the row being
updated. This allows the trigger function to modify the row being
inserted or updated.
A row-level BEFORE trigger that does not intend to cause either of
these behaviors must be careful to return as its result the same row
that was passed in (that is, the NEW row for INSERT and UPDATE
triggers, the OLD row for DELETE triggers).
According to the above you must:
declare the trigger as BEFORE UPDATE, not AFTER UPDATE
changebuilder_edit_flag column value directly in NEW row instead of firing UPDATE statement
CREATE TRIGGER builder_update_trigger_manual_custom_edits
BEFORE UPDATE
ON edmonton.custom_manual_edit
FOR EACH ROW
.....
.....
CREATE OR REPLACE FUNCTION
edmonton.automated_builder_update_trigger_manual_custom_edits()
.....
.....
BEGIN
IF NEW.builder <> OLD.builder THEN
NEW.builder_edit_flag = 10;
END IF;
RETURN NEW;
.....
.....