PostgreSQL increment serial column on update - postgresql

I am trying to increment a serial column after an update. I have written a trigger function to help.
CREATE FUNCTION "public"."update_transaction_id" () RETURNS trigger AS
$BODY$
DECLARE
curr_id integer;
BEGIN
curr_id = nextval(pg_get_serial_sequence('current_table', 'transaction_id'));
NEW.transaction_id = curr_id;
PERFORM SETVAL((SELECT pg_get_serial_sequence('current_table', 'transaction_id')), curr_id + 1, false);
RETURN NEW;
END;
$BODY$ LANGUAGE 'plpgsql' IMMUTABLE CALLED ON NULL INPUT SECURITY INVOKER;
CREATE TRIGGER "trg_update_transaction_id" AFTER UPDATE
ON "current_table" FOR EACH ROW
EXECUTE PROCEDURE "public"."update_transaction_id"();
So the column is transaction_id in the table current_table. The query executes fine and the my update function does run through this trigger. However, the transaction_id column remains the same value. Is there something wrong with this procedure?

There are three errors in your code. First, you use the equality operator = instead of the assignment operator :=. Second, you should not SETVAL the sequence; it knows perfectly well how to maintain itself. Third, this function should be called by a BEFORE UPDATE trigger or the assignment to transaction_id will not persist.
So why not simply:
CREATE FUNCTION update_transaction_id() RETURNS trigger AS $BODY$
BEGIN
NEW.transaction_id := nextval('seq_name');
RETURN NEW;
END;
$BODY$ LANGUAGE 'plpgsql';
You can trivially retrieve the immutable name of the sequence from the table definition, so plug that in straightaway.

Related

postgresql - trigger function with condition

I'm trying to create a trigger with a condition. Based on the geom length I want the attribute "nom" (= name) to be written in upper case or lower case.
here's what I have:
CREATE OR REPLACE FUNCTION public.test_upper_lower()
RETURNS trigger AS
$BODY$
BEGIN
NEW.dummy:= (ST_Length(new.geom));
if (SELECT dummy FROM ligne_ligne)>100
then NEW.nom:= LOWER(nom) FROM ligne_ligne;
else NEW.nom:= UPPER(nom) FROM ligne_ligne;
end if;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
DROP trigger IF EXISTS test_upper_lower on public.ligne_ligne;
CREATE trigger test_upper_lower BEFORE INSERT OR UPDATE on public.ligne_ligne
FOR EACH ROW
EXECUTE PROCEDURE public.test_upper_lower();
With this I have a "more than one row returned by a subquery" error
Based on other questions on this forum I tried it using case instead of if and using when in the trigger itself not the function but neither are working
Any ideas ?
Thanks
You don't need (or can actually) use SELECT statements to access data from the inserted row.
The part SELECT dummy FROM ligne_ligne returns all rows from that table - not just from the one relevant to the trigger.
As you just want to check the value you just calculated, simply use new.dummy at that point:
CREATE OR REPLACE FUNCTION public.test_upper_lower()
RETURNS trigger AS
$BODY$
BEGIN
NEW.dummy:= ST_Length(new.geom);
if new.dummy > 100 then --<< no SELECT necessary
NEW.nom:= LOWER(new.nom); --<< no "FROM", just access the value
else
NEW.nom:= UPPER(new.nom);
end if;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;

How to use json string element in a trigger function without declaring it as a variable

I'm learning about Postgresql - I don't have a specific application in mind, but the general idea is to update one column with json and not worry about the rest.
I have a table with a few columns (id, name, amount, json). I would like to update only the json column and have a trigger update the other columns from the json.
For example, if json was set to {"name" = "fred", "amount" = 100}, the trigger would populate the name and amount columns
so far, this works:
create table one(id serial primary key, name text, amount int, data jsonb);
CREATE OR REPLACE FUNCTION test()
RETURNS trigger AS
$$
DECLARE
json_name text = (data -> 'name') from one;
json_amount int = (data -> 'amount') from one;
BEGIN
IF json_name IS DISTINCT FROM OLD.name THEN
UPDATE one SET name = json_name;
END IF;
IF json_amount IS DISTINCT FROM OLD.amount THEN
UPDATE one SET amount = json_amount;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER test_trigger
AFTER INSERT OR UPDATE
OF data ON one
FOR EACH ROW
EXECUTE PROCEDURE test();
I'm trying to do this without declaring any variables. for example, I've tried these:
IF one.data->'name' IS DISTINCT FROM OLD.name THEN
or
IF one.data->'name'::text IS DISTINCT FROM OLD.name THEN
or
IF ((data->'name') from one) IS DISTINCT FROM OLD.name THEN
but none work.
Making such a trigger may be a horrible idea, and that would be nice to know, but I'm mainly interested figuring out this json stuff :)
I've made an example here:
https://dbfiddle.uk/?rdbms=postgres_11&fiddle=92f2e6dd3630c76178ca4cfe4dc30b10
Make it a before trigger and instead of an UPDATE manipulate new.
CREATE OR REPLACE FUNCTION test()
RETURNS trigger
AS
$$
BEGIN
new.name := new.data->'name';
new.amount := new.data->'amount';
RETURN new;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test_trigger
BEFORE INSERT
OR UPDATE
OF data
ON one
FOR EACH ROW
EXECUTE PROCEDURE test();
Although the answer provided by #sticky bit goes into the right direction, it doesn't handle type conversion well.
Here's a version that is better at dealing with empty JSON strings:
CREATE OR REPLACE FUNCTION test()
RETURNS trigger AS
$$
BEGIN
NEW.name := (NEW.data->>'name');
NEW.amount := round((NEW.data->>'amount')::numeric)::int;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER test_trigger
BEFORE INSERT OR UPDATE
OF data ON one
FOR EACH ROW
EXECUTE PROCEDURE test();
https://dbfiddle.uk/?rdbms=postgres_11&fiddle=15a232d4092e23c7bc0425b849b31976
Please remember that executing multiple UPDATEs inside a trigger function after an original INSERT or UPDATE is going to kill performance.

Alias column name in Postgres notify

I am using trigger in Postgres database to call function and send newly inserted row to NodeJs application
CREATE OR REPLACE FUNCTION triggerFunction() RETURNS trigger AS $$
DECLARE
BEGIN
PERFORM pg_notify('tableName', row_to_json(NEW)::text );
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
This returns the whole row in json format. However I need to change one of the column name while this row is returned.
Unfortunately AS keywork doesnt work in the row to json with NEW.COLUMN_NAME AS NEW_COLUMN. How can we achieve the solution for this?
CREATE OR REPLACE FUNCTION triggerFunction() RETURNS trigger AS $$
DECLARE
ret json;
BEGIN
select row_to_json(x) into ret from
(select NEW.abc as def, NEW.jkl, NEW.col3) x;
PERFORM pg_notify('tableName', ret::text );
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

To ignore result in BEFORE TRIGGER of PostgreSQL?

This thread is a part challenge of this thread to which I am searching a better solution for one part by BEFORE TRIGGER.
I just want to launch a trigger to convert to correct brackets.
I am thinking whether I should return from the trigger NULL or something else in before trigger.
Code
CREATE OR REPLACE FUNCTION insbef_events_function()
RETURNS TRIGGER AS
$func$
DECLARE
m int[];
BEGIN
FOREACH m SLICE 1 IN ARRAY TG_ARGV[0]::int[]
LOOP
INSERT INTO events (measurement_id, event_index_start, event_index_end)
SELECT NEW.measurement_id, m[1], m[2]; -- Postgres array starts with 1 !
END LOOP;
-- do something with _result ...
RETURN NULL; -- result ignored since this is an BEFORE trigger TODO right?
END
$func$ LANGUAGE plpgsql;
which I use the by the function
CREATE OR REPLACE FUNCTION f_create_my_trigger_events(_arg1 int, _arg2 text, _arg3 text)
RETURNS void AS
$func$
BEGIN
EXECUTE format($$
DROP TRIGGER IF EXISTS insbef_ids ON events
CREATE TRIGGER insbef_ids
BEFORE INSERT ON events
FOR EACH ROW EXECUTE PROCEDURE insbef_events_function(%1$L)$$
, translate(_arg2, '[]', '{}'), translate(_arg3, '[]', '{}')
);
END
$func$ LANGUAGE plpgsql;
I am unsure about this line: RETURN NULL; -- result ignored since this is anBEFOREtrigger TODO right?, since I think this is the case in AFTER trigger but not in before trigger.
I just want to launch a trigger to convert correct brackets.
Test command is sudo -u postgres psql detector -c "SELECT f_create_my_trigger_events(1,'[112]','[113]');" getting the following error because of misunderstanding of the returning -thing, I think.
LINE 3: CREATE TRIGGER insbef_ids
^
QUERY:
DROP TRIGGER IF EXISTS insbef_ids ON events
CREATE TRIGGER insbef_ids
BEFORE INSERT ON events
FOR EACH ROW EXECUTE PROCEDURE insbef_events_function('{112}')
CONTEXT: PL/pgSQL function f_create_my_trigger_events(integer,text,text) line 4 at EXECUTE statement
How can you manage BEFORE triggers in PostgreSQL 9.4?
First of all, you need to pass the row variable in a BEFORE trigger. Passing NULL cancels the operation for the row:
CREATE OR REPLACE FUNCTION insbef_events_function()
RETURNS TRIGGER AS
$func$
DECLARE
m int[];
BEGIN
FOREACH m SLICE 1 IN ARRAY TG_ARGV[0]::int[]
LOOP
INSERT INTO events (measurement_id, event_index_start, event_index_end)
SELECT NEW.measurement_id, m[1], m[2]; -- Postgres array subscripts start with 1
END LOOP;
-- do something with _result ...
RETURN NEW; -- NULL would cancel operation in BEFORE trigger!
END
$func$ LANGUAGE plpgsql;
I demonstrated the use of RETRUN NULL in an AFTER trigger in my previous answer. You can't do the same for a BEFORE trigger. 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). If a nonnull value is returned then the
operation proceeds with that row value.
There is more. Read the manual.
But since you are passing two 1-dimensional arrays instead of one 2-dimensional array now, you need to adapt your trigger logic:
CREATE OR REPLACE FUNCTION insbef_events_function()
LANGUAGE plpgsql RETURNS TRIGGER AS
$func$
DECLARE
a1 int[] := TG_ARGV[1]::int[];
a2 int[] := TG_ARGV[2]::int[];
BEGIN
FOR i in array_lower(a1, 1) .. array_upper(a1, 1)
LOOP
INSERT INTO events (measurement_id, event_index_start, event_index_end)
SELECT NEW.measurement_id -- or TG_ARGV[0]::int instead?
, a1[i], a2[i];
END LOOP;
RETURN NEW; -- NULL would cancel operation in BEFORE trigger!
END
$func$;
It's your responsibility that both arrays have the same number of elements.
The function changing the trigger could look like this now:
CREATE OR REPLACE FUNCTION f_create_my_trigger_events(_arg1 int, _arg2 text, _arg3 text)
LANGUAGE plpgsql RETURNS void AS
$func$
BEGIN
EXECUTE format(
$$DROP TRIGGER IF EXISTS insbef_ids ON measurements; -- on measurements ..
CREATE TRIGGER insbef_ids
BEFORE INSERT ON measurements -- .. according to previous posts!!
FOR EACH ROW EXECUTE PROCEDURE insbef_events_function(%s, %L, %L)$$
, _arg1
, translate(_arg2, '[]', '{}')
, translate(_arg3, '[]', '{}')
);
END
$func$;
You need to understand basics of SQL, PL/pgSQL, trigger functions and array handling before using this advanced automated design.

Return ID of last inserted row in PostgreSQL with RETURNING clause

I've got a pretty simple function defined like so:
CREATE OR REPLACE FUNCTION create_new_order(....) RETURNS integer AS
$BODY$
BEGIN
PERFORM add_points_to_usage(client_id_p, date_in_p, total_points_p);
INSERT INTO orders (...) VALUES (...)
RETURNING ident;
END;
$BODY$ LANGUAGE plpgsql;
Where I'm struggling is how to actually RETURN the value stored in the ident field back via the RETURNING clause. I've tried setting the value to a variable but that either doesn't work or I'm just messing up the syntax.
You're missing the variable declaration, the INTO clause and the final RETURN:
CREATE OR REPLACE FUNCTION create_new_order(....) RETURNS integer AS
$BODY$
DECLARE
var_ident int;
BEGIN
PERFORM add_points_to_usage(client_id_p, date_in_p, total_points_p);
INSERT INTO orders (...) VALUES (...)
RETURNING ident INTO var_ident;
RETURN var_ident;
END;
$BODY$ LANGUAGE plpgsql;