I am working on my first DB trigger and having an issue. The trigger involves a case statement and I am getting the error #1054 - Unknown column 'ORDERTYPECODE' in 'field list'.
The ORDERTYPECODE is a column on the table from which the trigger is called. Do I need to define which table the column belongs too?
Here is my code:
CREATE TRIGGER `StartNewIncOrderProcessing` AFTER INSERT ON `T_ORDERS`
FOR EACH ROW CASE WHEN ORDERTYPECODE = 'INC' THEN
INSERT INTO T_INC_DATA (ORDERID) VALUES ((SELECT MAX(ORDERID) FROM T_ORDERS));
END CASE;
Figured it out.
CREATE TRIGGER `StartNewIncOrderProcessing` AFTER INSERT ON `T_ORDERS`
FOR EACH ROW BEGIN
IF NEW.ORDERTYPECODE = 'INC' THEN
INSERT INTO T_INC_DATA (ORDERID)
VALUES ((SELECT MAX(ORDERID) FROM T_ORDERS ));
END IF;
END
Related
A trigger works on the first part of a function but not the second.
I'm trying to set up a trigger that does two things:
Update a field - geom - whenever the fields lat or lon are updated, using those two fields.
Update a field - country - from the geom field by referencing another table.
I've tried different syntaxes of using NEW, OLD, BEFORE and AFTER conditions, but whatever I do, I can only get the first part to work.
Here's the code:
CREATE OR REPLACE FUNCTION update_geometries()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
update schema.table a set geom = st_setsrid(st_point(a.lon, a.lat), 4326);
update schema.table a set country = b.name
from reference.admin_layers_0 b where st_intersects(a.geom,b.geom)
and a.pk = new.pk;
RETURN NEW;
END;
$$;
CREATE TRIGGER
geom_update
AFTER INSERT OR UPDATE of lat,lon on
schema.table
FOR EACH STATEMENT EXECUTE PROCEDURE update_geometries();
There is no new on a statement level trigger. (well, there is, but it is always Null)
You can either keep the statement level and update the entire a table, i.e. remove the and a.pk = new.pk, or, if only part of the rows are updated, change the trigger for a row-level trigger and only update the affected rows
CREATE OR REPLACE FUNCTION update_geometries()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
NEW.geom = st_setsrid(st_point(NEW.lon, NEW.lat), 4326);
SELECT b.name
INTO NEW.country
FROM reference.admin_layers_0 b
WHERE st_intersects(NEW.geom,b.geom);
RETURN NEW;
END;
$$;
CREATE TRIGGER
geom_update
BEFORE INSERT OR UPDATE of lat,lon on
schema.table
FOR EACH ROW EXECUTE PROCEDURE update_geometries();
I have a trigger AFTER INSERT ON mytable that calls a function
CREATE OR REPLACE FUNCTION myfunction() RETURNS trigger AS
$BODY$
DECLARE
index TEXT;
BEGIN
index := 'myIndex_' || NEW.id2::text;
IF to_regclass(index::cstring) IS NULL THEN
EXECUTE 'CREATE INDEX ' || index || ' ON mytable(id) WITH (FILLFACTOR=100) WHERE id2=' || NEW.id2|| ';';
RAISE NOTICE 'Created new index %',index;
END IF;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
SECURITY DEFINER
COST 100;
ALTER FUNCTION myfunction()
OWNER TO theadmin;
This works wonderfully. For each distinct id2 I create an index. Speeds up relevant queries by a lot.
As mentioned above I trigger this AFTER INSERT ON. Before doing that however I had the trigger set to BEFORE INSERT ON. And the function did some strange things. (Yes, I had changed the RETURN NULL to RETURN NEW)
insert of a new row insert into mytable VALUES(1391, 868, 0.5, 0.5);
creates the corresponding index myIndex_868
the inserted row does not appear in mytable when doing a select :(
trying to insert the same row results in ERROR: duplicate key value violates unique constraint "mytable_pkey" because of course DETAIL: Key (id, id2)=(1391, 868) already exists.
inserting other rows for the same id2 works as expected :)
DELETE FROM mytable WHERE id = 1391 and id2 = 868 does nothing
DROP INDEX myIndex_868; drops the index. And suddenly the initial row that never appeared in the table is suddenly there!
Why does BEFORE INSERT ON behave so differently? Is this a bug in postgres 9.4 or did I overlook something?
Just for completeness' sake:
CREATE TRIGGER mytrigger
AFTER INSERT ON mytable
FOR EACH ROW EXECUTE PROCEDURE myfunction();
vs.
CREATE TRIGGER mytrigger
BEFORE INSERT ON mytable
FOR EACH ROW EXECUTE PROCEDURE myfunction();
I'd argue that this is a bug in PostgreSQL. I could reproduce it with 9.6.
It is clear that the row is not contained in the index as it is created in the BEFORE trigger, but the fact that the index is not updated when the row is inserted is a bug in my opinion.
I have written to pgsql-hackers to ask for an opinion.
But apart from that, I don't see the point of the whole exercise.
Better than creating a gazillion indexes would be to create a single one:
CREATE INDEX ON mytable(id2, id);
I have the following code in postgresql:
CREATE OR REPLACE FUNCTION update_category() RETURNS trigger AS $newProduct$
BEGIN
UPDATE category SET product_count = product_count + 1 WHERE cat_id = NEW.cat_id;
INSERT INTO notification (content, type, p_id) VALUES('new product', 1, NEW.p_id);
RETURN NEW;
END;
$newProduct$ LANGUAGE plpgsql;
CREATE TRIGGER update_cat AFTER INSERT ON product
EXECUTE PROCEDURE update_category();
And after inserting a record into product I get the error:
[2017-03-20 16:05:05] [55000] ERROR: record "new" is not assigned yet
[2017-03-20 16:05:05] Detail: The tuple structure of a not-yet-assigned record is indeterminate.
[2017-03-20 16:05:05] Where: SQL statement "UPDATE category SET product_count = product_count + 1 WHERE cat_id = NEW.cat_id"
[2017-03-20 16:05:05] PL/pgSQL function update_category() line 3 at SQL statement
I've looked around for a solution, but I only find cases where the error is because
FOR EACH STATEMENT
is being used instead of
FOR EACH ROW
Seeing as I'm simply executing the procedure once, the solution doesn't apply in my case.
Thanks for your help!
The solution was indeed to add FOR EACH ROW:
CREATE TRIGGER update_cat AFTER INSERT ON product
FOR EACH ROW EXECUTE PROCEDURE update_category();
I assumed FOR EACH ROW meant calling the procedure once for each row in product, not for each row inserted.
I'm using postgres 9.4; I have a table with a unique index. I would like to mutate the name by adding a suffix to ensure the name is unique.
I have created a "before" trigger which computes a suffix. It works well in autocommit mode. However, if two items with the same name are inserted in the same transaction, they both get the same unique suffix.
What is the best way to accomplish my task? Is there a way to handle it with a trigger, or should I ... hmm... wrap the insert or update in a savepoint and then handle the error?
UPDATE (re comment from #Haleemur Ali ):
I don't think my question depends on the details. The salient point is that I query the subset of the collection over which I want to enforce uniqueness, and
choose a new name... however, it would seem that when the queries are run on two objects identically named in the same transaction, one doesn't see the others' modification to the new value.
But ... just in case... my trigger contains ("type" is fixed parameter to the trigger function):
select find_unique(coalesce(new.name, capitalize(type)),
'vis_operation', 'name', format(
'sheet_id = %s', new.sheet_id )) into new.name;
Where "find_unique" contains:
create or replace function find_unique(
stem text, table_name text, column_name text, where_expr text = null)
returns text language plpgsql as $$
declare
table_nt text = quote_ident(table_name);
column_nt text = quote_ident(column_name);
bstem text = replace(btrim(stem),'''', '''''');
find_re text = quote_literal(format('^%s(( \d+$)|$)', bstem));
xtct_re text = quote_literal(format('^(%s ?)', bstem));
where_ext text = case when where_expr is null then '' else 'and ' || where_expr end;
query_exists text = format(
$Q$ select 1 from %1$s where btrim(%2$s) = %3$s %4$s $Q$,
table_nt, column_nt, quote_literal(bstem), where_ext );
query_max text = format($q$
select max(coalesce(nullif(regexp_replace(%1$s, %4$s, '', 'i'), ''), '0')::int)
from %2$s where %1$s ~* %3$s %5$s
$q$,
column_nt, table_nt, find_re, xtct_re, where_ext );
last int;
i int;
begin
-- if no exact match, use exact
execute query_exists;
get diagnostics i = row_count;
if i = 0 then
return coalesce(bstem, capitalize(right(table_nt,4)));
end if;
-- find stem w/ number, use max plus one.
execute query_max into last;
if last is null then
return coalesce(bstem, capitalize(right(table_nt,4)));
end if;
return format('%s %s', bstem, last + 1);
end;
$$;
A BEFORE trigger sees rows modified by the statement that is currently running. So this should work. See demo below.
However, your design will not work in the presence of concurrency. You have to LOCK TABLE ... IN EXCLUSIVE MODE the table you're updating, otherwise concurrent transactions could get the same suffix. Or, with a UNIQUE constraint present, all but one will error out.
Personally I suggest:
Create a side table with the base names and a counter
When you create an entry, lock the side table in EXCLUSIVE mode. This will serialize all sessions that create entries, which is necessary so that you can:
UPDATE side_table SET counter = counter + 1 WHERE name = $1 RETURNING counter to get the next free ID. If you get zero rows, then instead:
Create a new entry in the side table if the base name being created and the counter set to zero.
Demo showing that BEFORE triggers can see rows inserted in the same statement, though not the row that fired the trigger:
craig=> CREATE TABLE demo(id integer);
CREATE TABLE
craig=> \e
CREATE FUNCTION
craig=> CREATE OR REPLACE FUNCTION demo_tg() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE
row record;
BEGIN
FOR row IN SELECT * FROM demo
LOOP
RAISE NOTICE 'Row is %',row;
END LOOP;
IF tg_op = 'DELETE' THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;
$$;
CREATE FUNCTION
craig=> CREATE TRIGGER demo_tg BEFORE INSERT OR UPDATE OR DELETE ON demo FOR EACH ROW EXECUTE PROCEDURE demo_tg();
CREATE TRIGGER
craig=> INSERT INTO demo(id) VALUES (1),(2);
NOTICE: Row is (1)
INSERT 0 2
craig=> INSERT INTO demo(id) VALUES (3),(4);
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (3)
INSERT 0 2
craig=> UPDATE demo SET id = id + 100;
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (2)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (102)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (102)
NOTICE: Row is (103)
UPDATE 4
craig=>
I have to write a trigger to ensure unique entries in column account of table accounts:
create table accounts (id serial, account int4 default 0);
I tried to write my trigger like this:
create function x_6 () returns trigger as '
begin
IF row(new.account) is distinct from row(OLD.account) THEN
return NEW;
ELSE
raise notice '' Entries are not unique! '';
END IF;
end;
'
language 'plpgsql';
Or:
create function x_6 () returns trigger as '
begin
IF (new.account <> OLD.account) THEN
return NEW;
ELSE
raise notice '' Entries are not unique ! '';
END IF;
end;
'
language 'plpgsql';
And then
create trigger x_6t before insert on accounts for each row execute procedure x_6();
When I try to insert something:
insert into accounts(account) values (20);
I get an error in either case:
ERROR: record "old" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
CONTEXT: PL/pgSQL function "x_6" line 3 at if
How can I fix it?
This is absolutely wrong way. You should not to use triggers for this purpose, you should to use unique indexes.
CREATE TABLE foo(a int PRIMARY KEY, b int);
-- column b has only unique values
CREATE UNIQUE INDEX ON foo(b);
Your code has more than one issue:
bad identifiers - konto instead account
it is table trigger - you has no any access to data there - PostgreSQL triggers are different than MSSQL
If you use row trigger, where there is possible access to record data, then OLD has different meaning than you expect. It is value of record before change - and this value is defined only for UPDATE or DELETE operations - and it is undefined for INSERT, because there previous value of record doesn't exist.