Postgres: checking value before conditionally running an update or delete - sql

I've got a fairly simple table which stores the records' authors in a text field as shown here:
CREATE TABLE "public"."test_tbl" (
"index" SERIAL,
"testdate" DATE,
"pfr_author" TEXT DEFAULT "current_user"(),
CONSTRAINT "test_tbl_pkey" PRIMARY KEY("index");
The user will never see the index or pfr_author fields, but I'd like them to be able to UPDATE the testdate field or DELETE whole records if they have permission and if they are the author. i.e. if test_tbl.pfr_author = CURRENT_USER THEN permit the UPDATE OR DELETE, but if not then raise an error message such as "Sorry, you do not have permission to edit this record.".
I have not gone down the route of using a trigger as I figure that even if it is executed before row update the user-requested update will still take place afterwards regardless.
I've tried doing this through a rule, but end up with infinite recursion as I put an update command inside the rule. Is there some way to do this using rules alone or a combination of a rule and trigger?
Thanks very much for any help!

Use a row level BEFORE trigger on UPDATE and DELETE to do this. Just have it return NULL when the operation is not permitted and the operation will be skipped.
http://www.postgresql.org/docs/9.0/interactive/trigger-definition.html

the trigger function have some problem,resulting recursive loop update.You should do like this:
CREATE OR REPLACE FUNCTION "public"."test_tbl_trig_func" () RETURNS trigger AS $body$
BEGIN
IF not (old.pfr_author = "current_user"() OR "current_user"() = 'postgres') THEN
NULL;
END IF;
RETURN new;
END;
$body$ LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER COST 100;
I have a test like this,it does well;
UPDATE test_tbl SET testdate = CURRENT_DATE WHERE test_tbl."index" = 2;

Related

syntax Error in PostgreSQL when I try to create Trigger

I want to create trigger in PostgreSQL.
Logic is very simple.
I need trigger, if published_at updated and written_at is null, set published_at to written_at.
I wrote this one, but it failed. Does anybody have an idea?
CREATE function setWrittenAt() RETURNS trigger;
AS
DECLARE old_id INTEGER;
BEGIN ;
old_id = OLD.id
IF NEW.published_at IS NOT and NEW.written_at IS null
THEN
UPDATE review SET NEW.written_at = NEW.published_at where id = old_id;
END IF ;
RETURN NEW;
END;
LANGUAGE plpgsql;
CREATE TRIGGER update_written_at
AFTER UPDATE OF published_at ON review
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
EXECUTE PROCEDURE setWrittenAt();
Error:
Syntax error: 7 ERROR: syntax error at or near "DECLARE"
LINE 3: DECLARE old_id INTEGER;
There are multiple errors in your code:
IS NOT is not a valid expression you need IS NOT NULL.
After BEGIN and the returns clause there must be no ;
you forgot to enclose the function body as a string (which is easier to write if you use dollar quoting
you also don't need an unnecessary (additional) UPDATE if you make it a before trigger
CREATE function setwrittenat()
RETURNS trigger
AS
$$
BEGIN
IF NEW.published_at IS NOT NULL and NEW.written_at IS null THEN
NEW.written_at := = NEW.published_at; --<< simply assign the value
END IF;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Then use a BEFORE trigger:
CREATE TRIGGER update_written_at
BEFORE UPDATE OF published_at ON review
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
FOR EACH ROW
EXECUTE PROCEDURE setWrittenAt();
this is based on a_horse_with_no_names answer, since it'll throw an error.
ERROR: statement trigger's WHEN condition cannot reference column values
You need to add FOR EACH ROW, else conditional triggers will not function.
If neither is specified, FOR EACH STATEMENT is the default.
Statement-level triggers can also have WHEN conditions, although the feature is not so useful for them since the condition cannot refer to any values in the table.
See here
CREATE TRIGGER update_written_at
BEFORE UPDATE OF published_at ON review
FOR EACH ROW
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
EXECUTE PROCEDURE setWrittenAt();
I can not comment yet, which is why I've posted this as an answer.

Check column value before delete trigger postgreSQL

Can someone tell me how can I check a specific column value before deleting from a table in postgreSQL?
My current code:
CREATE OR REPLACE FUNCTION f_tr_table_row_prohibit_delete() RETURNS TRIGGER AS
$$
BEGIN
IF (OLD.table_state_code=0) THEN
DELETE FROM mytable WHERE table_code=OLD.table_code;
ELSE
RAISE EXCEPTION 'The deletion of a row is allowed only when table state code is 0!';
END IF;
END;
$$
CREATE TRIGGER tr_table_row_prohibit_delete
BEFORE DELETE ON mytable
FOR EACH ROW
EXECUTE PROCEDURE f_tr_laua_rida_prohibit_delete();
LANGUAGE plpgsql SECURITY DEFINER STABLE
SET search_path = public, pg_temp;
I have a table named mytable with columns table_code and table_state_code.
Before deleteing from, mytable I want to check if the table_state_code is 0, if not then it should not allow delete if it is anything else then it should delete the row.
With my current code, it correctly raises the exception when table_state_code is not 0 but when it is then it says :
ERROR: DELETE is not allowed in a non-volatile function, instead of deleteing the row.
Thanks in advance.
In BEFORE DELETE trigger you can cancel the deletion or let it happen.
To cancel deletion you can return NULL or RAISE EXCEPTION.
To let the deletion to be continued return OLD.
The deletion has already started (as it is BEFORE DELETE trigger) so you don't have to execute DELETE FROM mytable WHERE table_code=OLD.table_code;.
See Overview of Trigger Behavior in the documentation.

Trigger to update a different table

Using Postgres 9.4, I have 2 tables streams and comment_replies. I am trying to do is update the streams.comments count each time a new comment_replies is inserted to keep track of the number of comments a particular stream has. I am not getting any errors but when I try to create a new comment it gets ignored.
This is how I am setting up my trigger. stream_id is a foreign key, so every stream_id will correspond to a streams.id which is the primary key of the streams table. I have been looking at this example: Postgres trigger function, but haven't been able to get it to work.
CREATE TABLE comment_replies (
id serial NOT NULL PRIMARY KEY,
created_on timestamp without time zone,
comments text,
profile_id integer,
stream_id integer
);
The trigger function:
CREATE OR REPLACE FUNCTION "Comment_Updates"()
RETURNS trigger AS
$BODY$BEGIN
update streams set streams.comments=streams.comments+1
where streams.id=comment_replies_streamid;
END$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
And the trigger:
CREATE TRIGGER comment_add
BEFORE INSERT OR UPDATE
ON comment_replies
FOR EACH ROW
EXECUTE PROCEDURE "Comment_Updates"();
How can I do this?
There are multiple errors. Try instead:
CREATE OR REPLACE FUNCTION comment_update()
RETURNS trigger AS
$func$
BEGIN
UPDATE streams s
SET streams.comments = s.comments + 1
-- SET comments = COALESCE(s.comments, 0) + 1 -- if the column can be NULL
WHERE s.id = NEW.streamid;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER comment_add
BEFORE INSERT OR UPDATE ON comment_replies -- on UPDATE, too? Really?
FOR EACH ROW EXECUTE PROCEDURE comment_update();
You need to consider DELETE as well if that is possible. Also if UPDATE can change stream_id. But why increase the count for every UPDATE? This looks like another error to me.
It's a syntax error to table-qualify the target column in the SET clause of UPDATE.
You need to return NEW in a BEFORE trigger unless you want to cancel the INSERT / UPDATE.
Or you make it an AFTER trigger, which would work for this, too.
You need to reference NEW for the stream_id of the current row (which is automatically visible inside the trigger function.
If streams.comments can be NULL, use COALESCE.
And rather use unquoted, legal, lower-case identifiers.

PostgreSQL Update trigger

I have a table:
CREATE TABLE annotations
(
gid serial NOT NULL,
annotation character varying(250),
the_geom geometry,
"rotationAngle" character varying(3) DEFAULT 0,
CONSTRAINT annotations_pkey PRIMARY KEY (gid),
CONSTRAINT enforce_dims_the_geom CHECK (st_ndims(the_geom) = 2),
CONSTRAINT enforce_srid_the_geom CHECK (st_srid(the_geom) = 4326)
)
And trigger:
CREATE TRIGGER set_angle
AFTER INSERT OR UPDATE
ON annotations
FOR EACH ROW
EXECUTE PROCEDURE setangle();
And function:
CREATE OR REPLACE FUNCTION setAngle() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE annotations SET "rotationAngle" = degrees( ST_Azimuth( ST_StartPoint(NEW.the_geom), ST_EndPoint(NEW.the_geom) ) )-90 WHERE gid = NEW.gid;
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE annotations SET "rotationAngle" = degrees( ST_Azimuth( ST_StartPoint(NEW.the_geom), ST_EndPoint(NEW.the_geom) ) )-90 WHERE gid = NEW.gid;
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
And when new row inserted in table or row edited i want to field rotationAngle setted with function result.
But when i inserting a new row in table function not work. I mean thath rotationAngle value not changed.
What can be wrong?
You are triggering an endless loop. Simplify the trigger function:
CREATE OR REPLACE FUNCTION set_angle()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
NEW."rotationAngle" := degrees(
ST_Azimuth(
ST_StartPoint(NEW.the_geom)
, ST_EndPoint(NEW.the_geom)
)
) - 90;
RETURN NEW;
END
$func$;
Assign to NEW directly. No WHERE in this case.
You must double-quote illegal column names. Better not to use such names to begin with.
Recent related answer.
Code for insert & upgrade is the same. I folded into one code path.
Use a BEFORE trigger. This way you can edit columns of the triggering row directly before they are saved:
CREATE TRIGGER set_angle
BEFORE INSERT OR UPDATE ON annotations
FOR EACH ROW EXECUTE PROCEDURE set_angle();
However
If you are just trying to persist a functionally dependent value in the table (and there are no other considerations): Don't. Use a view or a generated column instead:
Store common query as column?
Then you don't need any of this.
There are multiple things wrong here.
1) When you insert a row 'A' the function setAngle() is called. But in the function you are calling another update within the function which will trigger the function again, and again, and so on...To fix this don't issue a update! Just update the NEW records value independently and return it.

Postgresql trigger function not working

I'm very new to trigger function. Actually this is the first time I'm using it, and I can't pass over an issue. Here is my code:
CREATE OR REPLACE FUNCTION altitude()
RETURNS trigger AS $$
DECLARE
maxaltimeter DOUBLE PRECISION;
BEGIN
SELECT max(altitude) INTO maxaltimeter FROM waypoints WHERE flight_id = OLD.id;
RETURN NEW;
UPDATE flights SET max_altimeter = NEW.maxaltimeter WHERE id=OLD.id;
END;
$$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER set_altitude
AFTER UPDATE OF status
ON flights
FOR EACH ROW
EXECUTE PROCEDURE altitude();
When running UPDATE on the 'flights' table (status column) I don't get any results (but no errors too). Any ideas?
Thank you.
You got the statements in the wrong order. The RETURN NEW will terminate the trigger and thus the update will not be run.
It should be:
SELECT max(altitude) INTO maxaltimeter FROM waypoints WHERE flight_id = OLD.id;
UPDATE flights SET max_altimeter = NEW.maxaltimeter WHERE id=OLD.id;
RETURN NEW;
But I hope you are aware that this is not going to be a scalable solution.
Usually it's better to not store calculated/aggregated values in the database, but to calculate them while you retrieve the data. For convenience you might want to create a view that returns you the max() value for each flight.
The only reason why pre-calculating such a value would be if the retrieval is really expensive and you have to do it very often compared to the updates and the retrieval performance is more important than the update performance.