stack depth limit exceeded when fired a trigger - postgresql

I am trying to build a slow changing dimensional table, it track all the history of records. The schema of the table is like this:
CREATE TABLE test.dim
(id text,
column1 text,
column2 text,
begin_date timestamp without time zone,
is_current boolean,
end_date timestamp without time zone)
I defined a trigger function, and fire it before each insert action:
CREATE OR REPLACE FUNCTION test.slow_change_func()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
IF ( NOT EXISTS ( SELECT 1 FROM yang_test.dim
WHERE id= NEW.id
AND(column1 = NEW.column1 OR (column1 is null AND NEW.column1 is null))
AND (column2 = NEW.column2 OR (column2 is null AND NEW.column2 is null))
AND is_current
)
)
THEN UPDATE yang_test.dim
SET (end_date, is_current) = (now(), FALSE)
WHERE id = NEW.id
AND is_current;
INSERT INTO test.dim (id, column1, column2, begin_date, is_current, end_date)
VALUES ( NEW.id, NEW.column1, NEW.column2, now(), TRUE, 'infinity'::timestamp );
END IF;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER slow_change_trigger
BEFORE INSERT
ON test.dim
FOR EACH ROW
EXECUTE PROCEDURE test.slow_change_func();
When I try to test it,
INSERT INTO test.dim (id, column1, column2, begin_date, is_current, end_date)
VALUES ( 1, 'hello', 'world', now(), TRUE, 'infinity'::timestamp )
it will throw an error: stack depth limit exceeded. it looks like the function is running a loop. any suggestion s?

I think I have figure this out, this will match my requirement:
CREATE OR REPLACE FUNCTION yang_test.slow_change_func()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
IF ( NOT EXISTS ( SELECT 1 FROM yang_test.dim
WHERE id= NEW.id
AND(column1 = NEW.column1 OR (column1 is null AND NEW.column1 is null))
AND (column2 = NEW.column2 OR (column2 is null AND NEW.column2 is null))
AND is_current
)
)
THEN UPDATE yang_test.dim
SET (end_date, is_current) = (now(), FALSE)
WHERE id = NEW.id
AND is_current;
ELSE RETURN null;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

Related

How insert count of a table in another table attribute by trigger postgresql

I have created two tables "post" and "node" and I want to assign the sum of the entities of the "node" table in the attribute "nb_noeud" of the "post" table by trigger. But, the code below does not work and I think I missed something.
My code is as follows:
CREATE TABLE noeud
(
id_noeud serial NOT NULL,
code_noeud varchar(10) NULL,
type_noeud t_noeud NULL,
phase t_phase NULL
x_32632 bigint NULL,
y_32632 bigint NULL,
geom_noeud geometry(point) NULL,
obs text NULL
)
;
CREATE TABLE poste
(
id_pt serial NOT NULL,
code_pt varchar(8) NULL,
nom_pt varchar(50) NULL,
nb_noeud smallint NULL,
geom_pt geometry(polygon) NULL,
surf_pt numeric(15,2) NULL,
obs text NULL
)
;
CREATE OR REPLACE FUNCTION recap_noeud() RETURNS TRIGGER
language plpgsql AS
$$
DECLARE
som_noeud smallint;
BEGIN
IF (TG_OP = 'INSERT') THEN
SELECT COUNT(*) INTO som_noeud FROM noeud;
UPDATE poste set NEW.nb_noeud = som_noeud;
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
SELECT COUNT(*) INTO som_noeud FROM noeud;
UPDATE poste set NEW.nb_noeud = som_noeud;
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
RETURN NULL;
ELSE
RAISE WARNING 'Other action occurred: %, at %', TG_OP, now();
RETURN NULL;
END IF;
END;
$$
;
DROP TRIGGER IF EXISTS trig_recap_noeud ON noeud;
CREATE TRIGGER trig_recap_noeud AFTER INSERT OR UPDATE OR DELETE ON noeud FOR EACH ROW EXECUTE PROCEDURE recap_noeud();
Replace DELETE and INSERT clauses with
IF TG_OP = 'INSERT' OR TG_OP = 'DELETE' THEN
SELECT COUNT(*) INTO som_noeud FROM noeud;
UPDATE poste set nb_noeud = som_noeud;
RETURN NULL;
Best regards, Bjarni

trigger in postgresql old value and new value

how to write trigger in postgresql which maintain old value, new value and table name.
I have 5 tables and each tables is different data structure i want to maintain audit details in single table with old value new value and table name.old value and new value contain multiple columns in json format with column name and value.
example
audit_details
----------------------------------------------------------------
date_time|table_name|old_data|new_data|user|primary_key_of_table
----------------------------------------------------------------
I wrote sample trigger function for you. You can make additional changes yourself.
CREATE TABLE test.log_table_data (
id serial not null,
schema_name varchar(100) NOT NULL,
table_name varchar(100) NOT NULL,
action_date timestamp NOT NULL DEFAULT now(),
action_type varchar(10) NOT NULL,
table_id int4 NOT NULL,
old_data jsonb NULL,
new_data jsonb NULL,
CONSTRAINT log_table_data_pk PRIMARY KEY (id)
);
create or replace function test.register_as_log()
returns trigger
language plpgsql
security definer
as $function$
declare
v_old_data json;
v_new_data json;
shemaname varchar;
tablename varchar;
begin
shemaname = tg_table_schema;
tablename = tg_table_name;
/* json_strip_nulls - removes null values */
if (tg_op = 'UPDATE') then
v_old_data = (select json_strip_nulls(row_to_json(old.*)));
v_new_data = (select json_strip_nulls(row_to_json(new.*)));
insert into test.log_table_data (schema_name, table_name, action_type, table_id, old_data, new_data)
values (shemaname, tablename, 'update', old.id, v_old_data, v_new_data);
return new;
end if;
if (tg_op = 'DELETE') then
v_old_data = (select json_strip_nulls(row_to_json(old.*)));
insert into test.log_table_data (schema_name, table_name, action_type, table_id, old_data, new_data)
values (shemaname, tablename, 'delete', old.id, v_old_data, null);
return old;
end if;
end;
$function$
;

column reference "col_1" is ambiguous plpgsql

I'm writing postgresql function which inserts data and returns successfully inserted data rows.
The code is below.
CREATE OR REPLACE FUNCTION public.fn_insert_test(json_data jsonb)
returns table(col_1 varchar(255),
col_2 varchar(255),
col_3 timestamp)
LANGUAGE plpgsql
AS $function$
declare
--
begin
with my_table as (
with my_table1(my_json) as (
values(
json_data
)
)
insert into "test"(col_1, col_2, col_3)
select
elem->>'val1', elem->>'val2', now()
from
my_table1 t, jsonb_array_elements(my_json->'_data') elem
on conflict(col_1) do nothing
returning *
)
select * from my_table;
end
$function$
;
select fn_insert_test('{"_data": [{"val1":"1", "val2":"1"}, {"val1":"2", "val2":"2"}]}');
It occurs error below.
SQL Error [42702]: ERROR: column reference "col_1" is ambiguous
Detail: It could refer to either a PL/pgSQL variable or a table column.
Where: PL/pgSQL function fn_insert_test(jsonb) line 5 at SQL statement
[edit]
This is not the real problem here. There is a lot of problems in your code :
-> If you want to return a table, you have to use 'return query' : http://www.postgresqltutorial.com/plpgsql-function-returns-a-table/
-> I do not think you can use the WITH like you do: https://www.postgresql.org/docs/current/queries-with.html
Here is a functoinnal version of your code. But I am not quite sure of what you want :
CREATE OR REPLACE FUNCTION public.fn_insert_test(json_data jsonb)
returns table(col_1 varchar(255),
col_2 varchar(255),
col_3 timestamp)
LANGUAGE plpgsql AS
$$
declare
--json_data jsonb := '{"_data": [{"val1":"1", "val2":"1"}, {"val1":"2", "val2":"2"}]}';
begin
create temp table res (col_1 varchar (255), col_2 varchar (255), col_3 timestamp) on commit drop;
with my_table1(my_json) as
(
values(
json_data
)
)
, inserted as
(
insert into test(col_1, col_2, col_3)
select
elem->>'val1', elem->>'val2', now()
from
my_table1 t
, jsonb_array_elements(json_data->'_data') elem
on conflict do nothing
returning *
)
insert into res
select *
from inserted;
--raise notice '%', res_v;
return query select * from res;
end
$$
;
select fn_insert_test('{"_data": [{"val1":"1", "val2":"1"}, {"val1":"2", "val2":"2"}]}');

PostgreSQL log trigger optimalization

I spent a lot of time trying to optimize our pgsql log trigger which started to be a problem. I did huge progress (from 18min to 2.5min by inserting 3M rows) but I would like to know if some pgSql masters will be able to do it even better.
CREATE OR REPLACE FUNCTION table_log_trig()
RETURNS trigger AS
$BODY$
DECLARE
col TEXT; -- Single column name to save
newVal TEXT; -- New value for column
oldVal TEXT; -- Old value for column
colLimit TEXT[]; -- Columns that should be logged
BEGIN
IF TG_ARGV[0] IS NOT NULL THEN
-- Trigger specifies columns to log
SELECT array_agg(unnest)
FROM unnest(string_to_array(TG_ARGV[0], ','))
INTO colLimit;
ELSE
-- Trigger with no params. Log all columns
SELECT array_agg(json_object_keys)
FROM json_object_keys(row_to_json(NEW))
WHERE json_object_keys NOT IN ('id', 'created_at', 'updated_at') -- Exceptions
INTO colLimit;
END IF;
-- Loop over columns that should be saved in log
FOREACH col IN ARRAY colLimit
LOOP
-- INSERT & UPDATE
EXECUTE 'SELECT ($1).' || col || '::text' INTO newVal USING NEW;
-- UPDATE
IF TG_OP = 'UPDATE' THEN
EXECUTE 'SELECT ($1).' || col || '::text' INTO oldVal USING OLD;
END iF;
-- Add only new or changed data
IF
newVal != oldVal OR
(oldVal IS NULL AND newVal IS NOT NULL) OR
(oldVal IS NOT NULL AND newVal IS NULL)
THEN
INSERT INTO tab_logs (record_id, field_name, old_value, new_value, created_at, created_by, action)
VALUES (NEW.id, col, oldVal, newVal, NOW(), 999, 'O');
END IF;
END LOOP;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
row_to_json() returns both column names and values; you may as well make use of these values, rather than extracting them later via dynamic SQL.
I haven't thoroughly tested this, let alone benchmarked it, but here's the gist of it:
CREATE OR REPLACE FUNCTION table_log_trig() RETURNS trigger AS
$$
DECLARE
OldJson JSONB = NULL;
BEGIN
IF TG_OP <> 'INSERT' THEN
OldJson := to_jsonb(old);
END IF;
INSERT INTO tab_logs (record_id, field_name, old_value, new_value, created_at, created_by, action)
SELECT new.id, key, OldValues.value, NewValues.value, now(), 999, 'O'
FROM jsonb_each(to_jsonb(new)) NewValues
LEFT JOIN jsonb_each(OldJson) OldValues USING (key)
WHERE
(
(TG_ARGV[0] IS NULL AND key NOT IN ('id', 'created_at', 'updated_at')) OR
(TG_ARGV[0] IS NOT NULL AND key = ANY(string_to_array(TG_ARGV[0], ',')))
) AND
OldValues.value::text IS DISTINCT FROM NewValues.value::text;
RETURN NULL;
END
$$
LANGUAGE plpgsql VOLATILE;

How to avoid recursion in a trigger in PostgreSQL

I have a problem with recursion in a trigger.
I have the table:
CREATE TABLE employees(
task int4 NOT NULL,
first_name varchar(40) NOT NULL,
last_name varchar(40) NOT NULL,
PRIMARY KEY (task, cli_id_number)
);
When I insert the values:
(123, name, last)
i want to automatically insert these values as well:
(321, name, last)
I do this in the following way, but apparently the trigger is recursive and after the first recursion it tries to insert the previously inserted value.
CREATE OR REPLACE FUNCTION insert_task() RETURNS trigger AS $BODY$
BEGIN
IF new.task = '123' AND (
SELECT cant FROM (
SELECT name, task, count(*) AS cant
FROM client_task
WHERE name = NEW.name AND task = NEW.task
GOUP BY 1,2
HAVING count(*) <= 1
) t) = 1 THEN
INSERT INTO client_task(task, name, last_name)
VALUES('321', NEW.task, NEW.name, NEW.last_name);
RETURN NEW;
ELSE
IF NEW.task = '321' AND (
SELECT cant FROM (
SELECT name, task, count(*) AS cant
FROM client_task
WHERE name = NEW.name AND task = NEW.task
GROUP BY 1, 2
HAVING count(*) <=1
) t) = 1 THEN
INSERT INTO client_task(task, name, last_name)
VALUES('123', NEW.task, NEW.name, NEW.last_name);
RETURN NEW;
END IF;
END IF;
RETURN NULL;
END; $BODY$ LANGUAGE 'plpgsql';
Any help appreciated.
Use the function pg_trigger_depth(). According to the documentation it returns:
current nesting level of PostgreSQL triggers (0 if not called,
directly or indirectly, from inside a trigger)
CREATE TRIGGER insert_task
AFTER INSERT ON employees
FOR EACH ROW
WHEN (pg_trigger_depth() = 0)
EXECUTE PROCEDURE insert_task()