I am working in PostgreSQL 9.5 where I have these two tables:
Table incident:
oid integer
description integer,
geom geometry(Point,4326),
And table grid:
oid integer,
number integer,
geom geometry(Polygon,4326)
Everytime a point is added to table incident I want the field description to be filled with the grid number that the point is intersecting. To achieve this I created this trigger:
CREATE OR REPLACE FUNCTION get_ortofoto_incidentes()
RETURNS TRIGGER AS
$BODY$
BEGIN
SELECT g.number INTO NEW.description
FROM incident AS i, grid AS g
WHERE st_intersects(new.geom, g.geom) ; --
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsq;
And added it to the table incident:
CREATE TRIGGER get_ortofoto_incidentes
BEFORE INSERT OR UPDATE ON incidentestest
FOR each row
EXECUTE PROCEDURE get_ortofoto_incidentes();
The problem is that when the trigger function is called the attribute description remains null for the first row, but after the trigger works as expected. This is what I am getting:
oid | description
1 | null
2 | 236541
3 | 695489
4 | 325687
... | ....
Any idea why the trigger is not updating for the first row of the table?
Thank you
I'd say that it is a coincidence that this happens only for the first inserted row. It is completely ok for description to be set to NULL if the first matching row in grid has NULL in number.
I also want to warn you that the query in your trigger function builds a Cartesian product between the matching rows from grid and the table incident, since there is no WHERE condition restricting the latter. Remove the incident AS i from the query.
Related
I am trying to write a trigger with reference to Postgres DOC. But its not even allowing to create a trigger base on truncate, tried different approaches but didn't work.
CREATE TRIGGER delete_after_test
AFTER truncate
ON tableA
FOR EACH ROW
EXECUTE PROCEDURE delete_after_test3();
Function:
CREATE OR REPLACE FUNCTION econnect.delete_after_test3()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
query text;
begin
insert into econnect.delete_after_test_2 (
"name",
age1,
log_time
)
values
(
old."name",
old.age1,
CURRENT_TIMESTAMP
)
;
return old;
END;
$function$
;
Reference: https://www.postgresql.org/docs/current/sql-createtrigger.html
"TRUNCATE will not fire any ON DELETE triggers that might exist for the tables. But it will fire ON TRUNCATE triggers. If ON TRUNCATE triggers are defined for any of the tables, then all BEFORE TRUNCATE triggers are fired before any truncation happens, and all AFTER TRUNCATE triggers are fired after the last truncation is performed and any sequences are reset. The triggers will fire in the order that the tables are to be processed (first those listed in the command, and then any that were added due to cascading)"
A solution using ON DELETE:
create table delete_test(id integer, fld1 varchar, fld2 boolean);
create table delete_test_save(id integer, fld1 varchar, fld2 boolean);
insert into delete_test values (1, 'test', 't'), (2, 'dog', 'f'), (3, 'cat', 't')
CREATE OR REPLACE FUNCTION public.delete_save()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
INSERT INTO delete_test_save SELECT * FROM del_recs;
RETURN OLD;
END;
$function$
CREATE TRIGGER trg_del_save
AFTER DELETE ON delete_test referencing OLD TABLE AS del_recs FOR EACH statement
EXECUTE FUNCTION delete_save ();
delete from delete_test;
DELETE 3
select * from delete_test;
id | fld1 | fld2
----+------+------
(0 rows)
select * from delete_test_save;
id | fld1 | fld2
----+------+------
1 | test | t
2 | dog | f
3 | cat | t
The example uses a transition relation (referencing OLD TABLE AS del_recs) to collect all the deleted records for use in the function. Then it is possible to do the INSERT INTO delete_test_save SELECT * FROM del_recs; to transfer the records to the other table. No, they will not work with a TRUNCATE trigger.
Transition relations are explained here Create Trigger:
The REFERENCING option enables collection of transition relations, which are row sets that include all of the rows inserted, deleted, or modified by the current SQL statement. This feature lets the trigger see a global view of what the statement did, not just one row at a time. This option is only allowed for an AFTER trigger that is not a constraint trigger; also, if the trigger is an UPDATE trigger, it must not specify a column_name list. OLD TABLE may only be specified once, and only for a trigger that can fire on UPDATE or DELETE; it creates a transition relation containing the before-images of all rows updated or deleted by the statement. Similarly, NEW TABLE may only be specified once, and only for a trigger that can fire on UPDATE or INSERT; it creates a transition relation containing the after-images of all rows updated or inserted by the statement.
Description of the problem:
Hi everyone, i have this table:
CREATE TABLE COMPORDINE (
CodProdotto CHAR(5) NOT NULL CHECK(CodProdotto ~* '^[0-9]+$'),
CodOrdine CHAR(5) CHECK(CodOrdine ~* '^[0-9]+$'),
Prezzo REAL NOT NULL, CHECK(Prezzo >= 0.0)
CONSTRAINT OrdineFK FOREIGN KEY(CodOrdine) REFERENCES ORDINE(CodOrdine)
CONSTRAINT ProdottoFK FOREIGN KEY(CodProdotto) REFERENCES PRODOTTO(CodProdotto)
);
on which I declared a TRIGGER:
CREATE TRIGGER updatePrezzoOrdine
AFTER INSERT ON COMPORDINE
FOR EACH ROW
EXECUTE PROCEDURE updatePrezzoOrdine();
and a TRIGGER FUNCTION:
CREATE OR REPLACE FUNCTION updatePrezzoOrdine()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
Totale REAL;
BEGIN
SELECT SUM(Prezzo) INTO Totale
FROM COMPORDINE AS CO
WHERE CO.CodOrdine = NEW.CodOrdine
GROUP BY CodOrdine;
UPDATE ORDINE SET PrezzoTotale = PrezzoTotale + Totale
WHERE CodOrdine = NEW.CodOrdine;
RETURN NEW;
END
$$
Everything works and the trigger just fires fine, but no UPDATE is done on table ORDINE (on line 12).
What I experience (and tried):
I've tried to put some print instructions to see if the query was working:
RAISE NOTICE 'NEW.CodOrdine = %', NEW.CodOrdine;
RAISE NOTICE 'NEW.CodProdotto = %', NEW.CodProdotto;
RAISE NOTICE 'NEW.Prezzo = %', NEW.Prezzo;
but the output returns that new row inserted:
INSERT INTO COMPORDINE VALUES ('12345', '11111', 1.80)
has null values:
NOTICE: NEW.CodOrdine = <NULL>
NOTICE: NEW.CodProdotto = <NULL>
NOTICE: NEW.Prezzo = <NULL>
So the UPDATE "fails" because no row in ORDINE matches NULL values:
CodOrdine
PrezzoTotale
DataAcquisto
CodCliente
CodDipendente
12345
default (0.0)
2020-12-12
00000
99999
Apparently the problem could be related to the inserted row, may it?
What I expect:
After INSERT I would the ORDINE.PrezzoTotale column to be updated with the correct total price of every item in COMPORDINE that matches COMPORDINE.CodOrdine = ORDINE.CodOrdine in this way:
INSERT INTO COMPORDINE VALUES ('12345', '11111', 1.80)
CodOrdine
PrezzoTotale
DataAcquisto
CodCliente
CodDipendente
12345
1.80
2020-12-12
00000
99999
Big thanks to anyone in advance
[EDIT #1]:
As asked, here's the output of \d compordine:
**"public.compordine" table:**
| Colonna | Tipo | Ordinamento | Pu‗ essere null | Default|
|------------|--------------|-------------|-----------------|--------|
|codprodotto | character(5) | | not null | |
|codordine | character(5) | | not null | |
|prezzo | real | | not null | |
**Indexes**:
"compordine_codprodotto_key" UNIQUE CONSTRAINT, btree (codprodotto)
**Check constraints**:
"compordine_codordine_check" CHECK (codordine ~* '^[0-9]+$'::text)
"compordine_codprodotto_check" CHECK (codprodotto ~* '^[0-9]+$'::text)
"compordine_prezzo_check" CHECK (prezzo > 0::double precision)
**Referential integrity constraints**:
"ordinefk" FOREIGN KEY (codordine) REFERENCES ordine(codordine) ON UPDATE CASCADE
"prodottofk" FOREIGN KEY (codprodotto) REFERENCES prodotto(codprodotto) ON UPDATE CASCADE
**Triggers**:
updateprezzoordine AFTER INSERT ON compordine FOR EACH STATEMENT EXECUTE FUNCTION updateprezzoordine()
**Disabled triggers**:
updatescorte BEFORE INSERT ON compordine FOR EACH ROW EXECUTE FUNCTION updatescorte()
[EDIT #2]:
So, I did several other attempts to find a solution (SPOILER ALERT: no solution found); what I did, was to create another function and trigger that simply prints the NEW.<column> value after an INSERT statement on a different table called CLIENTE (my suspect was that all this issue was releated only to COMPORDINE table but I was wrong), here's the code:
CREATE TABLE CLIENTE (
CodCliente CHAR(5) PRIMARY KEY, CHECK(CodCliente ~* '^[0-9]+$'),
Nome VARCHAR(255) NOT NULL, CHECK(Nome ~* '^[A-Za-z ]+$'),
Cognome VARCHAR(255) NOT NULL, CHECK(Cognome ~* '^[A-Za-z ]+$'),
CodiceFiscale CHAR(16) NOT NULL, CHECK(CodiceFiscale ~* '^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$'),
Indirizzo VARCHAR(300) NOT NULL,
Email VARCHAR(255) NOT NULL, CHECK(Email ~* '^[A-Za-z0-9._%-]+#[A-Za-z0-9.-]+[.][A-za-z]+$'),
UNIQUE(CodiceFiscale, Email)
);
CREATE TRIGGER print
BEFORE INSERT ON Cliente
EXECUTE PROCEDURE print();
CREATE FUNCTION print()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
RAISE NOTICE 'CodCliente = %', NEW.CodCliente;
RETURN NEW;
END
$$
INSERT INTO CLIENTE VALUES ('34567', 'Gianfranco', 'Rana', 'GNFRNA87F24E123A', 'Via Delle Rane 23', 'franco.rane#ravioli.it')
Output returns:
NEW.CodCliente = <NULL>
While it should be:
NEW.CodCliente = <34567>
I simply can't understand why, shouldn't NEW in an AFTER INSERT TRIGGER returns the value this way? :
Event
OLD
NEW
INSERT
NULL
New Record
DELETE
Old Record
NULL
UPDATE
Original Record
Updated Record
Had the same issue. For me the issue was the way I created the trigger:
create trigger pending_balance_add after insert on pending_balance
execute function update_pending_balance();
I forgot to tell that "for each row" and perhaps "procedure" instead of function:
create trigger pending_balance_add before insert on pending_balance
for each row
execute procedure update_pending_balance();
Now inside of update_pending_balance I have correct new.amount, etc.
Regarding your issue, it shows that
Triggers:
updateprezzoordine AFTER INSERT ON compordine FOR EACH STATEMENT EXECUTE FUNCTION updateprezzoordine()
Disabled triggers:
updatescorte BEFORE INSERT ON compordine FOR EACH ROW EXECUTE FUNCTION updatescorte()
When trigger is executed for statement it has NEW.FIELD values as NULL
PostgreSQL Can't change values in AFTER trigger. Change the trigger to BEFORE.
Does not matter if you change the variable record NEW, the data was already changed in the table record. AFTER triggers ignore NEW changes. You can read and write but only read matters.
I have a table that uses a sequence as a default value in one of the columns. Whenever there is an insert into this table, I would like to insert the value of the sequence into another table. However when doing this, I get null values for the values generated by the sequence. Here is example code:
create sequence usq_test
as bigint
increment by 1
start 1
minvalue 1
maxvalue 9223372036854775807
no cycle
owned by none;
create table test1(
id bigint default nextval('usq_test') primary key,
name text not null);
insert into test1(name) values ('test_name');
insert into test1(name) values ('test_name');
insert into test1(name) values ('test_name');
select * from test1;
| id | name |
|----|-----------|
| 1 | test_name |
| 2 | test_name |
| 3 | test_name |
Now when I add the second table, and define the trigger function:
create table test2(id bigint primary key);
create or replace function ufn_insert_trg() returns trigger as
$$
begin
insert into test2(id)
values (NEW.id);
end;
$$ language plpgsql
;
create trigger utr_test1_insert
after insert
on test1
execute function ufn_insert_trg();
insert into test1(name) values ('test_name');
I get:
[2019-04-04 14:00:45] [23502] ERROR: null value in column "id" violates not-null constraint
[2019-04-04 14:00:45] Detail: Failing row contains (null).
[2019-04-04 14:00:45] Where: SQL statement "insert into test2(id)
[2019-04-04 14:00:45] values (NEW.id)"
[2019-04-04 14:00:45] PL/pgSQL function ufn_insert_trg() line 3 at SQL statement
What am I doing wrong?
The problem is in your trigger:
create trigger utr_test1_insert
after insert
on test1
execute function ufn_insert_trg();
Because you didn't explicitly state whether the trigger is row-level or statement-level, by default it becomes a statement-level trigger.
From the documentation:
FOR EACH ROW
FOR EACH STATEMENT
This specifies whether the trigger
function should be fired once for every row affected by the trigger
event, or just once per SQL statement. If neither is specified, FOR
EACH STATEMENT is the default.
And also as per the documentation, the NEW and OLD variables in a statement-level trigger are NULL, which makes sense because any number of rows could have been affected in a statement, so it wouldn't really make sense for NEW or OLD to refer to any specific one - you're dealing with a statement rather than specific rows.
That's why NEW.id is NULL. By changing the trigger to a row-level trigger, it will be fired for every row affected, and the NEW/OLD variables will be set as expected.
So:
create trigger utr_test1_insert
after insert
on test1
for each row
execute function ufn_insert_trg();
I am trying to create a trigger function in PostgreSQL that should check records with the same id (i.e. comparison by id with existing records) before inserting or updating the records. If the function finds records that have the same id, then that entry is set to be the time_dead. Let me explain with this example:
INSERT INTO persons (id, time_create, time_dead, name)
VALUES (1, 'now();', ' ', 'james');
I want to have a table like this:
id time_create time-dead name
1 06:12 henry
2 07:12 muka
id 1 had a time_create 06.12 but the time_dead was NULL. This is the same as id 2 but next time I try to run the insert query with same id but different names I should get a table like this:
id time_create time-dead name
1 06:12 14:35 henry
2 07:12 muka
1 14:35 waks
henry and waks share the same id 1. After running an insert query henry's time_dead is equal to waks' time_create. If another entry was to made with id 1, lets say for james, the time entry for james will be equal to the time_dead for waks. And so on.
So far my function looks like this. But it's not working:
CREATE FUNCTION tr_function() RETURNS trigger AS '
BEGIN
IF tg_op = ''UPDATE'' THEN
UPDATE persons
SET time_dead = NEW.time_create
Where
id = NEW.id
AND time_dead IS NULL
;
END IF;
RETURN new;
END
' LANGUAGE plpgsql;
CREATE TRIGGER sofgr BEFORE INSERT OR UPDATE
ON persons FOR each ROW
EXECUTE PROCEDURE tr_function();
When I run this its say time_dead is not supposed to be null. Is there a way I can write a trigger function that will automatically enter the time upon inserting or updating but give me results like the above tables when I run a select query?
What am I doing wrong?
My two tables:
CREATE TABLE temporary_object
(
id integer NOT NULL,
time_create timestamp without time zone NOT NULL,
time_dead timestamp without time zone,
PRIMARY KEY (id, time_create)
);
CREATE TABLE persons
(
name text
)
INHERITS (temporary_object);
Trigger function
CREATE FUNCTION tr_function()
RETURNS trigger AS
$func$
BEGIN
UPDATE persons p
SET time_dead = NEW.time_create
WHERE p.id = NEW.id
AND p.time_dead IS NULL
AND p.name <> NEW.name;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
You were missing the INSERT case in your trigger function (IF tg_op = ''UPDATE''). But there is no need for checking TG_OP to begin with, since the trigger only fires on INSERT OR UPDATE - assuming you don't use the same function in other triggers. So I removed the cruft.
Note that you don't have to escape single quotes inside a dollar-quoted string.
Also added:
AND p.name <> NEW.name
... to prevent INSERT's from terminating themselves instantly (and causing an infinite recursion). This assumes that a row can never succeed another row with the same name.
Aside: The setup is still not bullet-proof. UPDATEs could mess with your system. I could keep updating the id or a row, thereby terminating other rows but not leaving a successor. Consider disallowing updates on id. Of course, that would make the trigger ON UPDATE pointless. I doubt you need that to begin with.
now() as DEFAULT
If you want to use now() as default for time_create just make it so. Read the manual about setting a column DEFAULT. Then skip time_create in INSERTs and it is filled automatically.
If you want to force it (prevent everyone from entering a different value) create a trigger ON INSERT or add the following at the top of your trigger:
IF TG_OP = 'INSERT' THEN
NEW.time_create := now(); -- type timestamp or timestamptz!
RETURN NEW;
END IF;
Assuming your missleadingly named column "time_create" is actually a timestamp type.
That would force the current timestamp for new rows.
I am currently using postgres 8.3. I have created a table that acts as a dirty flag table for members that exist in another table. I have applied triggers after insert or update on the members table that will insert/update a record on the modifications table with a value of true. The trigger seems to work, however I am noticing that something is flipping the boolean is_modified value. I have no idea how to go about trying to isolate what could be flipping it.
Trigger function:
BEGIN;
CREATE OR REPLACE FUNCTION set_member_as_modified() RETURNS TRIGGER AS $set_member_as_modified$
BEGIN
LOOP
-- first try to update the key
UPDATE member_modification SET is_modified = TRUE, updated = current_timestamp WHERE "memberID" = NEW."memberID";
IF FOUND THEN
RETURN NEW;
END IF;
--member doesn't exist in modification table, so insert them
-- if someone else inserts the same key conncurrently, raise a unique-key failure
BEGIN
INSERT INTO member_modification("memberID",is_modified,updated) VALUES(NEW."memberID", TRUE,current_timestamp);
RETURN NEW;
EXCEPTION WHEN unique_violation THEN
-- do nothing, and loop to try the update again
END;
END LOOP;
END;
$set_member_as_modified$ LANGUAGE plpgsql;
COMMIT;
CREATE TRIGGER set_member_as_modified AFTER INSERT OR UPDATE ON members FOR EACH ROW EXECUTE PROCEDURE set_member_as_modified();
Here is the sql I run and the results:
$CREATE TRIGGER set_member_as_modified AFTER INSERT OR UPDATE ON members FOR EACH ROW EXECUTE PROCEDURE set_member_as_modified();
Results:
UPDATE 1
bluesky=# select * from member_modification;
-[ RECORD 1 ]---+---------------------------
modification_id | 14
is_modified | t
updated | 2011-05-26 09:49:47.992241
memberID | 182346
bluesky=# select * from member_modification;
-[ RECORD 1 ]---+---------------------------
modification_id | 14
is_modified | f
updated | 2011-05-26 09:49:47.992241
memberID | 182346
As you can see something flipped the is_modified value. Is there anything in postgres I can use to determine what queries/processes are acting on this table?
Are you sure you've posted everything needed? The two queries on member_modification suggest that a separate query is being run in between, which sets is_modified back to false.
You could add an text[] field to member_modification, e.g. query_trace text[] not null default '{}', then and a before insert/update trigger on each row on that table which goes something like:
NEW.query_trace := NEW.query_trace || current_query();
If current_query() is not available in 8.3, see this:
http://www.postgresql.org/docs/8.3/static/monitoring-stats.html
SELECT pg_stat_get_backend_pid(s.backendid) AS procpid,
pg_stat_get_backend_activity(s.backendid) AS current_query
FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS s;
You could then get the list of subsequent queries that affected it:
select query_trace[i] from generate_series(1, array_length(query_trace, 1)) as i