Trigger not executing as expected - postgresql

I'm trying to implement this trigger that ensures an user can't comment on their own publications.
CREATE OR REPLACE FUNCTION not_comment() RETURNS TRIGGER AS
$BODY$
BEGIN
IF EXISTS (SELECT *
FROM
(SELECT publisherID FROM comment INNER JOIN post USING (postID) WHERE NEW.postID = post.postID) AS comment_userID,
(SELECT publisherID FROM article INNER JOIN post USING (postID) WHERE NEW.articleID = article.articleID) AS article_userID
WHERE comment_userID.publisherID = article_userID.publisherID) THEN
RAISE EXCEPTION 'A user cannot comment an article that publish';
END IF;
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER not_comment
BEFORE INSERT ON comment
FOR EACH ROW
EXECUTE PROCEDURE not_comment();
The inner query is working as expected for the test cases, but for some reason the condition isn't being activated.
The same syntax as other working triggers was used.
Any idea of what's going on?

The trigger is fired before the row is inserted, so you should assume in the condition that the row is already in the table. Thus the first subquery should look like
(SELECT publisherid FROM post WHERE new.postid = post.postid) AS comment_userID

Related

PostgreSQL update trigger: relation 'new' does not exist

I'm trying to get a better sense of triggers and not quite understanding why I'm unable to reference the new, incoming row in this trigger function definition:
CREATE OR REPLACE FUNCTION public.update_origin_country()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
NEW.origin_country = a.pk
from reference.admin_layers_0 a
inner join reference.world_port_index b on a.iso_a2 = b.country
inner join new c on b.id = c.origin_port;
RETURN NEW;
END;
$$;
CREATE TRIGGER "origin_country_update" BEFORE INSERT OR UPDATE OF "origin_port" ON "active"."events"
FOR EACH ROW
EXECUTE PROCEDURE "public"."update_origin_country"();
When I update the field origin_ports with the trigger applied, I get the error:
Relation "new" does not exist.
Not sure how to get around it. The goal is to evaluate the new row coming in, checking for the value in origin_ports and using that to update the value for origin_country from a query referencing a port table and a country name table. Any help appreciated.
I don't totally understand the desired logic but instead of joining to new (which is invalid since the new row is not a relation/table) you can just add the filter to a where clause, something like:
CREATE OR REPLACE FUNCTION public.update_origin_country()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
NEW.origin_country = a.pk
from reference.admin_layers_0 a
inner join reference.world_port_index b on a.iso_a2 = b.country
WHERE new.origin_port = b.id;
RETURN NEW;
END;
$$;
CREATE TRIGGER "origin_country_update" BEFORE INSERT OR UPDATE OF "origin_port" ON "active"."events"
FOR EACH ROW
EXECUTE PROCEDURE "public"."update_origin_country"();
Is that what you want it to do?

How to return id after insert from updated (created from trigger) table in postgres?

I am creating an insert query to a table (t1 -Which creates the returning id by increment) which adds the same row to another table (t2) by a trigger and creates a new id. How do I get this new ID?
I tried the following approach but it returns null (I think the row in table t2 is not available yet to perform join on).
In t2 I have an identifier 'type' which detects the type of row through trigger
Any help would be appreciated. Thanks in advance.
WITH inserted AS (
INSERT INTO t1 (entity_name, entity_type, entity_country)
VALUES
('sss', 'list', 'a',)
RETURNING id, entity_country
)
select id, entity_reference_id, entity_country, t2.entity_id from inserted
join t2 on t1.id = t2.entity_reference_id
where t2.type LIKE '%manual%'
This returns me null, However I want it to return me new t2.entity_id
CREATE OR REPLACE FUNCTION view_t2_id() RETURNS TRIGGER AS $BODY$
BEGIN
IF (NEW."type" LIKE '%manual%') THEN
RETURN NEW."entity_id";
ELSE
RAISE EXCEPTION 'Wrong "type"="%"', NEW."type";
END IF;
END;
$BODY$ LANGUAGE plpgsql;
CREATE TRIGGER view_t2_id_trigger
AFTER INSERT ON t2
FOR EACH ROW EXECUTE PROCEDURE view_t2_id();

How to create with condition and select?

I want to do something like this :
Someone creates a customer and if the ct already exist so we check if the cn is the same and if it's not the same we raise an error but it' doesn't work and take a lot of time.
CREATE OR REPLACE FUNCTION existingCT()
RETURNS trigger AS $$ BEGIN
IF ((SELECT COUNT(*) FROM customer WHERE ct= NEW.ct)!= 0) THEN 
IF( (SELECT COUNT(*) FROM customer WHERE ct= NEW.ct) != (SELECT count(*) FROM customer WHERE ct= NEW.ct AND cn= NEW.cn)) THEN
RAISE EXCEPTION 'This ct already exist for a cn';
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql ;
You can look for a customer with the same ct and a different cn in one query:
IF EXISTS (SELECT * FROM customer WHERE ct = NEW.ct AND cn <> NEW.cn) THEN
A thrown exception should abort the INSERT for any type of trigger. But given that it's a check, I'd create the trigger as BEFORE INSERT.

Fill a variable with "array_to_string" in a plpgsql trigger function

I'm working with PostgreSQL 9.5.
I'm creating a trigger in PL/pgSQL, that adds a record to a table (synthese_poly) when an INSERT is performed on a second table (operation_poly), with other tables data.
The trigger works well, except for some variables, that are not filled (especially the ones I try to fill with an array_to_string() function).
This is the code:
-- Function: bdtravaux.totablesynth_fn()
-- DROP FUNCTION bdtravaux.totablesynth_fn();
CREATE OR REPLACE FUNCTION bdtravaux.totablesynth_fn()
RETURNS trigger AS
$BODY$
DECLARE
varoperateur varchar;
varchantvol boolean;
BEGIN
IF (TG_OP = 'INSERT') THEN
varsortie_id := NEW.sortie;
varopeid := NEW.operation_id;
--The following « SELECT » queries take data in third-party tables and fill variables, which will be used in the final insertion query.
SELECT array_to_string(array_agg(DISTINCT oper.operateurs),'; ')
INTO varoperateur
FROM bdtravaux.join_operateurs oper INNER JOIN bdtravaux.operation_poly o ON (oper.id_joinop=o.id_oper)
WHERE o.operation_id = varopeid;
SELECT CASE WHEN o.ope_chvol = 0 THEN 'f' ELSE 't' END as opechvol INTO varchantvol
FROM bdtravaux.operation_poly o WHERE o.operation_id = varopeid;
-- «INSERT» query
INSERT INTO bdtravaux.synthese_poly (soperateur, schantvol) SELECT varoperateur, varchantvol;
RAISE NOTICE 'varoperateur value : (%)', varoperateur;
RAISE NOTICE 'varchantvol value : (%)', varchantvol;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION bdtravaux.totablesynth_fn()
OWNER TO postgres;
And this is the trigger :
-- Trigger: totablesynth on bdtravaux.operation_poly
-- DROP TRIGGER totablesynth ON bdtravaux.operation_poly;
CREATE TRIGGER totablesynth
AFTER INSERT
ON bdtravaux.operation_poly
FOR EACH ROW
WHEN ((new.chantfini = true))
EXECUTE PROCEDURE bdtravaux.totablesynth_fn();
The varchantvol variable is correctly filled, but varoperateur stays desperately empty (NULL value) (and so on for the corresponding field in the synthese_poly table).
Note:
The SELECT array_to_string(…) ... query itself (launched with pgAdmin, without INTO varoperateur and replacing varopeid with a value) works well, and returns a string.
I tried to change array_to_string() function and variables' data types (using ::varchar or ::text …), nothing works.
Do you see what can happen?
using array_agg
You can replace array_to_string(array_agg(DISTINCT oper.operateurs),'; ') with
string_agg(DISTINCT oper.operateurs,'; ')
And you can use order by to sort the text in the agregate
string_agg(DISTINCT oper.operateurs,'; ' ORDER BY oper.operateurs)
My educated guess: you have a trigger with BEFORE INSERT ON bdtravaux.operation_poly. And operation_id is its serial PK column.
In this case, the query with WHERE o.operation_id = varopeid
(where varopeid has been filled with NEW.operation_id) can never find any rows because the row is not in the table, yet.
array_agg() has no role in this.
Would work with a trigger AFTER INSERT ON bdtravaux.operation_poly. But if id_oper is from the same inserted row, you can just simplify to:
SELECT array_to_string(array_agg(DISTINCT oper.operateurs),'; ')
INTO varoperateur
FROM bdtravaux.join_operateurs oper
WHERE oper.id_joinop = NEW.id_oper;
And keep the BEFORE trigger.
The whole function might be simpler, can probably done with a single query.

PL/pgSQL: Inserted data not available when function returns

I have an interesting problem that me and my collegue troubles for some time now.
I have a PL/pgSQL function in a PostgreSQL-8.3 (sorry for that old version, I can't change that) that does the following four things:
Get a new serial (ID) from a sequence
Insert a couple of records into a table with that serial
Send a notify signal that an insertion took place
Return the ID to the caller.
Simplified function:
CREATE OR REPLACE FUNCTION add_entry(_user_name text, _visible_attr integer[])
RETURNS bigint AS
$BODY$
DECLARE
user_name text := '#' || $1;
user_id bigint;
BEGIN
-- get the ID from the sequence
SELECT nextval('my_sequence') INTO user_id;
-- insert the name (and some other data not shown here) 5x
FOR item IN 1..5
LOOP
INSERT INTO mytable
(id,index,username,visible)
VALUES (user_id,item,user_name,$2[item]);
END LOOP;
-- send notify that an insertion took place
notify my_notify;
RETURN user_id;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
So, my collegue called this function from his application. He gets the returned ID and uses another thread (DB pooling) in his application to call a function which shall return the data previously inserted with that ID. However, this doesn't work the first time. Only with the second request he is able to select the data. It seems as if that INSERT isn't finished while the function already returns?!
We checked mutiple times, the data will be inserted into the table correctly but somehow it is not available as fast as the return value (the ID from the sequence) is available! Why is that so?
Update: wrong assumption
I examined further and reduced the example to a simple query which really shows the problem:
select * from mytable where id = (select add_entry('MyTestUser'));
This query returns no rows. But if I do that in two seperate steps I can select the data which I inserted with the add_entry function.
I have no clue what I'm doing wrong or how I could speed up the insertion...
From the 8.3 manual
In effect, a SELECT query sees a snapshot of the database as of the instant that that query begins to run
Since the update is done in the select itself the inserted row will not be seen.
http://www.postgresql.org/docs/8.3/static/tutorial-transactions.html
Change the function to return setof mytable. It can be plain SQL. To change the return type the function must be dropped first
drop function add_entry(text);
create or replace function add_entry (_user_name text, _visible_attr integer[])
returns setof mytable as $body$
notify my_notify;
with ins as (
insert into mytable (id, index, username, visible)
select user_id, item, '#' || $1, $2[item]
from
generate_series(1, 5) g(item)
cross join
(values (nextval('my_sequence'))) s(user_id)
returning *
)
select * from ins;
$body$ language sql volatile;
The notification must happen before anything is returned from the function. It is not a problem as if the insert fails the transaction rolls back including the notification. Call it like
select * from add_entry('MyTestUser');
The select will not see the modified table but the returned mytable rows.
If it is necessary for the function to be plpgsql then use return query
create or replace function add_entry (_user_name text, _visible_attr integer[])
returns setof mytable as $body$
begin
notify my_notify;
return query
insert into mytable (id, index, username, visible)
select user_id, item, '#' || $1, $2[item]
from
generate_series(1, 5) g(item)
cross join
(values (nextval('my_sequence'))) s(user_id)
returning *
;
end;
$body$ language plpgsql volatile;