How to know the date of modification of an SP? - postgresql

Is it possible to know the date of modification and / or creation of an SP in PostgreSQL 9.4?
I need to identify them to upload them next Deploy.-

PostgreSQL has not this functionality. You can create own table and update it from event triggers.
create table updates(proc regprocedure primary key, t timestamp);
create or replace function event_trigger_for_ddl_command_end()
returns event_trigger as $$
declare obj record;
begin
for obj in select * from pg_event_trigger_ddl_commands()
loop
if obj.classid = 'pg_proc'::regclass then
insert into updates values(obj.objid, current_timestamp)
on conflict (proc) do update set t = current_timestamp
where updates.proc = excluded.proc;
end if;
end loop;
end;
$$ language plpgsql;
create event trigger trigger_for_ddl_command_end
on ddl_command_end
execute procedure event_trigger_for_ddl_command_end();
create or replace function fx(a int) returns int as $$ select 1 $$ language sql;
postgres=# select * from updates ;
+-------------+----------------------------+
| proc | t |
+-------------+----------------------------+
| fx(integer) | 2017-11-22 14:21:11.367036 |
+-------------+----------------------------+
(1 row)
-- alternative code without INSERT ON CONFLICT
create or replace function event_trigger_for_ddl_command_end()
returns event_trigger as $$
declare obj record;
begin
for obj in select * from pg_event_trigger_ddl_commands()
loop
if obj.classid = 'pg_proc'::regclass then
begin
update updates set t = current_timestamp
where proc = obj.objid;
if not found then
begin
insert into updates values(obj.objid, current_timestamp);
exception when unique_violation then
update updates set t = current_timestamp
where proc = obj.objid;
end;
end if;
end if;
end loop;
end;
$$ language plpgsql;

Related

Cursor not found

i have created procedure, inside used cursor to update the some data, while calling the procedure it's getting the error.
create or REPLACE PROCEDURE bal_upd(p_id int) as
$$
DECLARE rc record;
----- cursor
bal_upd1 CURSOR (p_id int)
for
select * from tbal where custid = p_id;
begin
open bal_upd1 (p_id);
loop
FETCH bal_upd1 into rc;
exit when not found;
update t_trans set balance = balance + rc.trans;
COMMIT;
end loop;
close bal_upd1;
end;
$$ LANGUAGE plpgsql;
call bal_upd(1)
ERROR: cursor "bal_upd1" does not exist
CONTEXT: PL/pgSQL function bal_upd(integer) line 12 at FETCH
SQL state: 34000
create or REPLACE PROCEDURE bal_upd(p_id int) as
$$
DECLARE rc record;
----- cursor
bal_upd1 CURSOR (p_id int)
for
select * from tbal where custid = p_id;
begin
open bal_upd1 (p_id);
loop
FETCH bal_upd1 into rc;
exit when not found;
update t_trans set balance = balance + rc.trans;
COMMIT;
end loop;
close bal_upd1;
end;
$$ LANGUAGE plpgsql;
call bal_upd(1)
ERROR: cursor "bal_upd1" does not exist
CONTEXT: PL/pgSQL function bal_upd(integer) line 12 at FETCH
SQL state: 34000
You don't need a function or a loop for that:
UPDATE t_trans
SET balance = t_trans.balance + t.trans
FROM (SELECT sum(trans) AS trans
FROM tbal
GROUP BY custid) AS t
WHERE t_trans.custid = t.custid;
I tried, failed. I just found just use for loop (implicit cursor) is far more simple.
BEGIN;
CREATE temp TABLE tbal (
custid bigint
, trans numeric
);
INSERT INTO tbal VALUES (1 , 1);
INSERT INTO tbal VALUES (1 , 2);
CREATE temp TABLE t_trans (
custid bigint
, balance numeric
);
INSERT INTO t_trans VALUES (1 , 10);
COMMIT;
CREATE OR REPLACE PROCEDURE bal_upd (bigint)
AS $func$
DECLARE
rc record;
BEGIN
FOR rc IN
SELECT
*
FROM
tbal
WHERE
custid = $1 LOOP
RAISE NOTICE 'custid: %, trans: % ' , rc.custid , rc.trans;
UPDATE
t_trans ta
SET
balance = balance + (rc.trans)
WHERE
ta.custid = (rc.custid);
END LOOP;
END;
$func$
LANGUAGE plpgsql;
Then call it. CALL bal_upd(1);

Postgres update trigger

I've problem with a trigger function in postgresql.
Here my simple code.
CREATE TABLE specie
(specie_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_comune TEXT UNIQUE,
nome_scientifico TEXT UNIQUE);
CREATE TABLE rilevatore
(rilevatore_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_cognome TEXT);
CREATE TABLE evento_investimento
(evento_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data DATE,
ora TIME WITHOUT TIME ZONE,
rilevatore_id INT REFERENCES rilevatore (rilevatore_id),
specie_id INT REFERENCES specie(specie_id));
CREATE VIEW inserimento_dati_vista AS
SELECT row_number() OVER ()::integer AS gid,
evento_investimento.ora,
evento_investimento.data,
rilevatore.nome_cognome,
specie.nome_comune,
specie.nome_scientifico
FROM evento_investimento
JOIN specie ON evento_investimento.specie_id = specie.specie_id
JOIN rilevatore ON evento_investimento.rilevatore_id = rilevatore.rilevatore_id;
CREATE OR REPLACE FUNCTION inserimento_dati_fun_2() RETURNS trigger AS $$
BEGIN
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome)
VALUES (NEW.nome_cognome);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
INSERT INTO evento_investimento (data, ora, rilevatore_id, specie_id)
VALUES (NEW.data,NEW.ora,
(SELECT rilevatore_id FROM rilevatore WHERE rilevatore.nome_cognome = NEW.nome_cognome),
(SELECT specie_id FROM specie WHERE specie.nome_comune = NEW.nome_comune));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger inserimento_dati_fun_trg
instead of insert on inserimento_dati_vista for each row EXECUTE procedure inserimento_dati_fun_2();
Now, I want to add a function that allow to update all the tables by using the view inserimento_dati_vista.
I've tried with a simple code to update only the data column
CREATE OR REPLACE FUNCTION update_dati_fun_2() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF old.data is distinct from new.data then
UPDATE evento_investimento
SET data = new.data;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger update_dati_fun_2_trg
instead of update on inserimento_dati_vista for each row EXECUTE procedure update_dati_fun_2();
However when I perfomr the query in order to update only a row, the trigger update all the rows in the table. Here some code to fill data.
INSERT INTO inserimento_dati_vista
(data, ora, nome_cognome, nome_comune, nome_scientifico)
VALUES
('2020-01-01', '16:54:00','mario', 'lupo', 'Canis lupus'),
('2020-01-02', '13:54:00','luca', 'lontra', 'Lutra lutra');
UPDATE inserimento_dati_vista
SET data = '2021-01-02' where nome_cognome = 'luca'
Update function is:
CREATE OR REPLACE FUNCTION update_dati_fun_2() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF old.data is distinct from new.data then
UPDATE evento_investimento e
SET data = new.data
FROM rilevatore r
WHERE nome_cognome = new.nome_cognome AND r.rilevatore_id = e.rilevatore_id;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Postgresql: UPDATE before INSERT function

I have problem when create function for trigger. I want to UPDATE inserted value BEFORE INSERT data to DB.
My code look like this:
CREATE OR REPLACE FUNCTION test_func()
RETURNS TRIGGER AS
$$
DECLARE cnt INTEGER;
BEGIN
cnt := COUNT(*) FROM sample_tbl WHERE id = NEW.id AND created_date = NEW.created_date;
NEW.current_order := cnt + 1; // I want to set value of sample_tbl.current_order automatically
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_trigger
BEFORE INSERT
ON test_tbl
FOR EACH ROW
EXECUTE PROCEDURE test_func();
I inserted data then IDE said:
control reached end of trigger procedure without RETURN
Where: PL/pgSQL function test_func()
The error says that you must return something from the Trigger ( either NEW or NULL )
There's no Trigger needed for this. A simple View using this select query will give you the required result
--create or replace view sample_view as
select t.id, t.created_date,
row_number() OVER ( partition by id,created_date order by id ) as current_order
FROM sample_tbl t;
This will exactly match the records if updated using a Trigger
CREATE OR REPLACE FUNCTION test_func()
RETURNS TRIGGER AS
$$
DECLARE cnt INTEGER;
BEGIN
select COUNT(*) INTO cnt FROM sample_tbl WHERE id = NEW.id
AND created_date = NEW.created_date;
NEW.current_order := cnt + 1;
RETURN NEW; --required
END
$$ LANGUAGE plpgsql;
Demo
Your trigger function is just missing RETURN NEW; statement:
CREATE OR REPLACE FUNCTION test_func()
RETURNS TRIGGER AS
$$
DECLARE cnt INTEGER;
BEGIN
cnt := COUNT(*) FROM sample_tbl WHERE id = NEW.id AND created_date = NEW.created_date;
NEW.current_order := cnt + 1;
RETURN NEW;
END
$$ LANGUAGE plpgsql;

Update row in trigger without recursion

I have two tables:
CREATE TABLE first (
id text primary key,
updated_at timestamp,
data text
);
CREATE TABLE second (
id text REFERENCES first (id),
book_error text,
);
and I need to update updated_at field in first table always, when any of these tables has updated. I wrote this:
CREATE FUNCTION update_timestamp() RETURNS trigger AS $$
BEGIN
UPDATE first
SET updated_at = current_timestamp
WHERE id = NEW.id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DO $$
DECLARE
t text;
BEGIN
FOR t IN
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
LOOP
EXECUTE format('CREATE TRIGGER update_timestamp
BEFORE INSERT OR UPDATE ON %I
FOR EACH ROW EXECUTE PROCEDURE update_timestamp()',
t);
END LOOP;
END;
$$ LANGUAGE plpgsql;
But it's not working because update statement inside my trigger causes call of this trigger again before executing.
How can I do update inside trigger without firing it trigger again?
Per the documentation:
TG_TABLE_NAME
Data type name; the name of the table that caused the trigger invocation.
Use the variable in the trigger function:
CREATE OR REPLACE FUNCTION update_timestamp() RETURNS trigger AS $$
BEGIN
IF TG_TABLE_NAME = 'first' THEN
NEW.updated_at = current_timestamp;
ELSE
UPDATE first
SET updated_at = current_timestamp
WHERE id = NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
From Postgresql documentation,
pg_trigger_depth() - current nesting level of PostgreSQL triggers (0
if not called, directly or indirectly, from inside a trigger)
You can use this inside your trigger function to check if it is called from inside trigger
DO $$
DECLARE
t text;
BEGIN
FOR t IN
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
LOOP
EXECUTE format('CREATE TRIGGER update_timestamp
BEFORE INSERT OR UPDATE ON %I
FOR EACH ROW
WHEN (pg_trigger_depth() = 0)
EXECUTE PROCEDURE update_timestamp()',
t);
END LOOP;
END;
$$ LANGUAGE plpgsql;
test=# Insert into first select 1,now(), 'test';
INSERT 0 1
test=# select * from first;
id | updated_at | data
----+----------------------------+------
1 | 2018-10-29 20:18:25.227281 | test
(1 row)
test=# Insert into second select 1, 'test_error';
INSERT 0 1
test=# select * from first;
id | updated_at | data
----+----------------------------+------
1 | 2018-10-29 20:19:07.456737 | test

not start trigger on view from pg_stat_activity

In postgres not real create trigger on pg_stat_activity, becouse i create my view based on pg_stat_activity and create trigger.
DROP FUNCTION IF EXISTS get_sa() CASCADE;
DROP FUNCTION IF EXISTS f_call_count_conn();
DROP FUNCTION IF EXISTS f_update_count_conn();
CREATE OR REPLACE FUNCTION get_sa() RETURNS SETOF pg_stat_activity AS
$$ SELECT * FROM pg_catalog.pg_stat_activity; $$
LANGUAGE sql
VOLATILE
SECURITY DEFINER;
CREATE OR REPLACE VIEW pg_stat_activity_allusers AS SELECT * FROM get_sa();
GRANT SELECT ON pg_stat_activity_allusers TO public;
CREATE OR REPLACE FUNCTION f_call_count_conn()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF TG_OP = 'INSERT' THEN
COPY (SELECT time_change, count FROM count_conn) TO '/tmp/query.csv' (format csv, delimiter ';');
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
COPY (SELECT time_change, count FROM count_conn) TO '/tmp/query.csv' (format csv, delimiter ';');
RETURN OLD;
END IF;
-- PERFORM f_update_count_conn();
-- RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER t_check_activity_conn
INSTEAD OF INSERT OR DELETE ON pg_stat_activity_allusers
FOR EACH ROW
EXECUTE PROCEDURE f_call_count_conn();
CREATE FUNCTION f_update_count_conn()
RETURNS VOID
AS
$BODY$
BEGIN
insert into count_conn (time_change, count)
values (NOW(), (select count(*)
from pg_stat_activity_allusers));
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
This code is not working, but when i replace my view (pg_stat_activity_allusers) on real table and change this table, my trigger work. Why? Thank you!
Your code worked for me on Postgres 9.5, except I manually called f_update_count_conn() to populate count_conn, because you're not (or no longer) calling it anywhere.
# select f_update_count_conn();
f_update_count_conn
---------------------
(1 row)
mw=# select * from count_conn;
time_change | count
----------------------------+-------
2017-02-03 17:22:34.846179 | 1
(1 row)
mw=# insert into pg_stat_activity_allusers(datid) values(123456::oid);
INSERT 0 1
mw=#
[1]+ Stopped '/Applications/Postgres.app/Contents/Versions/9.5/bin'/psql -p5432
$ cat /tmp/query.csv
2017-02-03 17:22:34.846179;1