postgresql CONCAT function error when use in a trigger - postgresql

This might be a stupid question but pardon me, I'm trying to convert one of my MariaDB database into a PostgreSQL database. Here I'm getting an error while executing this function.
I cannot find what's wrong here,
create function tg_prodcut_insert()
returns trigger as '
BEGIN
SET NEW.id = CONCAT(1, LPAD(INSERT INTO product_seq VALUES (NULL) returning id, 6, 0));
END;
' LANGUAGE 'plpgsql';
Error is pointing to the 1 in CONCAT method, The type of id I'm trying to SET is char(7)
EDIT
I also tried this, this won't work either,
create function tg_orders_insert()
returns trigger as '
BEGIN
INSERT INTO order_seq VALUES (NULL);
SET NEW.id = CONCAT('1', LPAD(LAST_INSERT_ID(), 6, 0));
END;
' LANGUAGE 'plpgsql';
Thanks in advance.

It seems you are trying to simulate some kind of sequence with that code by inserting into a table and then getting the auto_increment value from that.
This can be done much more efficiently using a sequence in Postgres.
The error you get also isn't caused by the concat() function but because you are using the wrong syntax.
Value assignment is done using := in PL/pgSQL.
And there is also no last_insert_id() function in Postgres. To get the next value from a sequence use nextval(), to get the most recently generated value, you can use lastval() but that's not necessary here.
create sequence product_id_seq;
create function tg_product_insert()
returns trigger as
$$
BEGIN
NEW.id := concat('ORD', to_char(nextval('product_id_seq'), 'FM00000000'));
return new;
END;
$$
LANGUAGE plpgsql;
you will need to create a before trigger for that to work:
create trigger product_seq_trigger
before insert on product
for each row
execute procedure tg_product_insert();
Online example
But it would be a lot more efficient to switch to a proper identity column instead and get rid of the trigger.

Related

Generic string trimming trigger for Postgresql

Problem:
One of the owners of the company that I work for has direct database access. He uses Navicat on a windows notebook. Apparently, it has a feature that he likes where he can import data from Excel. The problem is that text fields often (or maybe always) end up with a \r\n at the end of them. Which can lead to display, reporting and filtering issues. I've been asked to clean this up and to stop him from doing it.
I know I can just add a trigger to each table that will do something like:
NEW.customer_name := regexp_replace(NEW.customer_name, '\r\n', '', 'g');
However, I would prefer to not write a separate trigger function for each table that he has access to (there are over 100). My idea was to just write a generic function and then pass in an array of column names I want corrected via the TG_ARGV[] argument.
Is there a way to update a triggers NEW record dynamically based on the TG_ARGV array?
Details:
I'm using PostgreSQL 9.6.6 on x86_64-pc-linux-gnu
There is no native means to dynamically access the columns of the new record in a plpgsql trigger function. The only way I know is to convert the record to jsonb, modify it and convert it back to record using jsonb_populate_record():
create or replace function a_trigger()
returns trigger language plpgsql as $$
declare
j jsonb = to_jsonb(new);
arg text;
begin
foreach arg in array tg_argv loop
if j->>arg is not null then
j = j || jsonb_build_object(arg, regexp_replace(j->>arg, e'\r\n', '', 'g'));
end if;
end loop;
new = jsonb_populate_record(new, j);
return new;
end;
$$;
The case is much simpler if you can use plpython:
create or replace function a_trigger()
returns trigger language plpython3u as $$
import re
new = TD["new"]
for col in TD["args"]:
new[col] = re.sub(r"\r\n", "", new[col])
return "MODIFY"
$$;

Calling a function for each updated row in postgresql

I have a sql UPDATE statement in a plpgsql function. I now want to call the pg_notify function for each updated row and am uncertain if my solution is the best possibility.
I am not aware of any position in the UPDATE statement itself where I could apply the function. I don't think it is possible in the SET part and if I would apply the function in the WHERE part, it would be applied to each row as it is checked and not only the updated rows, correct?
I therefore thought I could use the RETURNING part for my purposes and designed the function like this:
CREATE OR REPLACE FUNCTION function_name() RETURNS VOID AS $BODY$
BEGIN
UPDATE table1
SET a = TRUE
FROM table2
WHERE table1.b = table2.c
AND <more conditions>
RETURNING pg_notify('notification_name', table1.pk);
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE;
Unfortunately this gave me an error saying that I am not using or storing the return value of the query anywhere. I therefore tried putting PERFORM in front of the query but this seemed to be syntactically incorrect.
After trying different combinations with PERFORM my ultimate solution is this:
CREATE OR REPLACE FUNCTION function_name() RETURNS VOID AS $BODY$
DECLARE
dev_null INTEGER;
BEGIN
WITH updated AS (
UPDATE table1
SET a = TRUE
FROM table2
WHERE table1.b = table2.c
AND <more conditions>
RETURNING pg_notify('notification_name', table1.pk)
)
SELECT 1 INTO dev_null;
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE;
This works as it is supposed to, but I feel like there should be a better solution which does not temporarily store a useless result and does not use a useless variable.
Thank you for your help.
** EDIT 1 **
As can be seen in #pnorton 's answer, a trigger would do the trick in most cases. For me, however, it is not applicable as the receiver of the notifications also sometimes updates the table and I do not want to generate notifications in such a case
"I have a sql UPDATE statement in a plpgsql function. I now want to
call the pg_notify function for each updated row "
Ok I might be tempted to use a trigger Eg
CREATE TABLE foobar (id serial primary key, name varchar);
CREATE OR REPLACE FUNCTION notify_trigger() RETURNS trigger AS $$
DECLARE
BEGIN
PERFORM pg_notify('watch_tb_update', TG_TABLE_NAME || ',id,' || NEW.id );
RETURN new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER foobar_trigger AFTER INSERT ON foobar
FOR EACH ROW EXECUTE PROCEDURE notify_trigger();
LISTEN watch_tb_update;
INSERT into foobar(id, name) values(1,'test_name');
I've tested this and it works fine

postgres 9.3 database trigger creation

I am trying to create a trigger that on update of one table, runs a query and updates another table with the results.
Where I am getting stuck, is assigning the result of the query to a correctly typed variable.
The current error is that the array must start with "{" or other dimensional information however as I make tweaks I get other errors
Please see my current code below and let me know the best approach
Your help is very appreciated as I have spent a huge amount of time consulting google.
CREATE TYPE compfoo AS (ownership character varying (50), count INT);
CREATE OR REPLACE FUNCTION test1_update() RETURNS trigger AS
$$
DECLARE
largest_owner character varying (50);
temp_result compfoo[];
BEGIN
SELECT ownership, count(*) INTO temp_result
FROM austpoly2
WHERE ownership IS NOT NULL
group by ownership
ORDER BY count DESC
LIMIT 1;
largest_owner = temp_result[0].ownership;
UPDATE public.states
SET ownership= largest_owner
WHERE statecode='1';
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test1_update_trigger
BEFORE UPDATE ON austpoly2
FOR EACH ROW EXECUTE PROCEDURE test1_update();
Thankyou a_horse with_no_name
Your response combined with
temp_result compfoo%ROWTYPE;
solved this problem

using a cursor in a trigger

I am trying to write a trigger function that executes when an insert is performed. The condition is, if the id already exists then the time_create should now be the time_dead.
CREATE function archive_temp() returns trigger as '
begin
insert into temporary_archive
values
(
OLD.id,
OLD.time_create,
OLD.time_dead,
OLD.fname,
current_user,
now(),
now(),
TG_OP
);
return null;
end;
' LANGUAGE 'plpgsql';
-----------------------------
CREATE TRIGGER archive_temps
AFTER DELETE OR UPDATE
on temporary_object
FOR EACH ROW
DECLARE
temporary_archive temporary_object.id%type;
begin
if inserting then
select id into temporary_archive
from temporary_object
where id = :old.id;
if temporary_archive is not null then
EXECUTE PROCEDURE archive_temps();
end if;
end if;ins
It looks to me from your example like you are trying to do things the Oracle way. PostgreSQL is different. I also do not see any uses of cursors anywhere in your code so the question may be misleading.
In PostgreSQL, triggers can only call procedures and the procedures must be largely self-contained. In essence you are going to have to refactor your code a bit. The only accepted syntax after FOR EACH ROW is EXECUTE PROCEDURE and so your other logic will have to be moved into user defined functions. You could, if you want, apply the function in a WHEN condition by calling the function (if your function returns bool). Or you could build it into your trigger logic.

PSQL : Silencing a function call's output, or calling it without SELECT

In Postgresql, I have an UPDATE rule on a table which only needs to call a dctUpdate function without doing a whole SQL statement, since the SQL statement is actually done in the function. The only way I know of calling the function is through SELECT dctUpdate(windowId):
create or replace function infoUpdate(windowId in numeric) returns void as $$
begin
if windowId is null then
update info_timestamp set timestamp = now();
else
update info_timestamp set timestamp = now() where window_id = windowId;
end if;
end;
$$ LANGUAGE plpgsql;
create or replace rule info_update_rule as on update to some_table do also select infoUpdate(NEW.window_id);
However, on the command line, when that rule gets triggered because I updated a row in some_table, I get useless output from the SELECT clause that calls the function :
db=# update some_table set name = 'foobar' where window_id = 1;
infoupdate
-----------
(1 row)
UPDATE 1
Is there a way to have info_update_rule call the infoUpdate function without it displaying dummy output?
I've found no options to implement this using rules, but there is an alternative way of implementing this usign triggers.
So, you define trigger function as following:
CREATE OR REPLACE FUNCTION ur_wrapper_trg()
RETURNS trigger AS
$BODY$
begin
perform infoUpdate(NEW.window_id);
RETURN NEW;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION ur_wrapper_trg() OWNER TO postgres;
Note PERFORM syntax is used. This syntax is identical to SELECT syntax except it supresses all output.
Than you define a trigger
CREATE TRIGGER some_table_utrg
BEFORE UPDATE
ON some_table
FOR EACH ROW
EXECUTE PROCEDURE ur_wrapper_trg();
In the end, you remve your rule.
Haven't tested with null, but with actual windos_ids works as expected, without any unwanted output.
Consult with Triggers and Rules vs triggers for detailed description.
The closes solution to which I came is to call \t \a before select function() and right after it. The only remaining thing is a new line for each call.