after update the table insert data like arrays - postgresql

I have two tables
Table A
col1 col2 col3
Table B
table_name column_name new_value old_value
if any update happened on table A it will insert the data on table B
the output of table B is==>
table_name column_name new_value old_value
---------------- ------------------ -------------- -----------
A {col1}
A {col1,col2} {col1.new_value, {col1.old_value,
col2.new_value} col2.old_value},
so anyone can tell me how to capture the column_names and it store data target table like arrays

Try this
Use Trigger Function
CREATE OR REPLACE FUNCTION update_history()
RETURNS trigger AS
$BODY$
DECLARE col_name VARCHAR[];
DECLARE od_value VARCHAR[];
DECLARE ne_value VARCHAR[];
DECLARE each_column RECORD;
DECLARE each_entity RECORD;
DECLARE column_name VARCHAR;
DECLARE old_value VARCHAR;
DECLARE new_value VARCHAR;
FOR each_column IN
select c.column_name --- Get the all column names in affected table
from information_schema.columns c
where(table_name = tg_relname And c.TABLE_SCHEMA = TG_TABLE_SCHEMA)
LOOP
FOR each_entity IN --- Its used to get old and new columns value
EXECUTE 'SELECT text((' || quote_literal(OLD.*) || '::"' || tg_table_schema || '"."' || tg_relname || '")."' || each_column.column_name || '") as old_val,
text((' || quote_literal(NEW.*) || '::"' || tg_table_schema || '"."' || tg_relname || '")."' || each_column.column_name || '")
AS new_val
FROM "' || tg_table_schema || '"."' || tg_relname || '";'
LOOP
old_value = each_entity.old_val;
new_value = each_entity.new_val;
IF old_value != new_value THEN
i=i+1;
col_name[i]=each_column.column_name;
od_value[i]=old_value;
ne_value[i]=new_value;
END IF;
END LOOP;
END LOOP;
INSERT INTO B
(
tablename,
columnnames,
oldvalues,
newvalues
)
VALUES
(
tg_relname,
col_name,
od_value,
ne_value
);
End if;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE

I thinks it's a good case to use hstore extension of PostgreSQL:
create or replace function history_trigger_func()
returns trigger AS
$$
begin
insert into TableB
select
tg_relname,
case when tg_op in ('UPDATE', 'INSERT') then hstore(new) end,
case when tg_op in ('UPDATE', 'DELETE') then hstore(old) end;
return null;
end;
$$
language plpgsql;
create trigger tr_history_trigger after insert or update or delete on TableA
for each row execute procedure history_trigger_func();
sql fiddle demo
You can extend this further by removing columns which are not changing, or, if you're using PostgreSQL 9.3, you can use JSON instead of hstore.

Related

Function for changing a column value in all tables in one schema

I would appreciate it if you could let me know why my code doesn't do anything ? I use PostgreSQL 12.
I want to create a function to change an specific column's value among all tables in a schema.
CREATE OR REPLACE FUNCTION update_cols(_sch text,_col text, _old int, _new int)
RETURNS text AS
$func$
DECLARE
_tbl text;-- table_name
BEGIN
-- Loop over tables
FOR _tbl IN
SELECT quote_ident(table_name)
FROM information_schema.columns
WHERE table_schema = _sch -- name of schema
AND column_name = _col -- name of column
LOOP
RAISE NOTICE '%',
EXECUTE
'UPDATE ' || _tbl || ' SET _col = new_id WHERE _col = old_id';
END LOOP;
RETURN _tbl;
END
$func$ LANGUAGE plpgsql;
-- Call:
SELECT update_cols('','column_name', 10, 13);
It's working now, thanks to the help of #Adrian Klaver's comment.
CREATE OR REPLACE FUNCTION update_cols(_sch text, _old int, _new int)
RETURNS text AS
$func$
DECLARE
_tbl text;-- table_name
BEGIN
-- Loop over tables
FOR _tbl IN
SELECT quote_ident(table_name)
FROM information_schema.columns
WHERE column_name = 'col_name' and TABLE_Schema = _sch
-- name of column
LOOP
EXECUTE format('UPDATE ' ||_tbl|| ' SET col_name = $1 WHERE col_name= $2') USING _new, _old;
END LOOP;
RETURN _tbl;
END
$func$ LANGUAGE plpgsql;

How to use a Function Parameter in a Cursor that's incorporated with Dynamic SQL in Postgres Functions?

Created this Postgres Function which is working fine, but the actual requirement is to pass the input parameter in the function to the Cursor which uses the dynamic SQL as follows,
The below is the Function
CREATE OR REPLACE FUNCTION ssp2_pcat.find_shift_dates (date_to_find date)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
C1 CURSOR FOR
SELECT TABLE_NAME, 'SELECT COUNT(*) FROM ' || TABLE_NAME || ' WHERE ' ||
COLUMN_NAME || ' = '||
'CASE WHEN ' || COLUMN_NAME || ' LIKE ' || '''%START%'''||' THEN
date_to_find ELSE date_to_find-1 END;' SQL_TEXT
FROM (
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME IN (SELECT TABLE_NAME FROM RESET_DATES WHERE RESET_IT =
'Y') AND
UPPER(DATA_TYPE) = 'DATE'
AND (COLUMN_NAME LIKE '%START%' OR COLUMN_NAME LIKE '%END%')
AND (COLUMN_NAME NOT LIKE '%TEST%'
AND COLUMN_NAME NOT LIKE '%PCAT%'
AND COLUMN_NAME NOT LIKE '%ORDER%'
AND COLUMN_NAME NOT LIKE '%SEASON%'
AND COLUMN_NAME NOT LIKE '%_AT')
ORDER BY 1, 2) A;
END_COUNT INTEGER := 0;
START_COUNT INTEGER := 0;
TABLENAME VARCHAR(32) := 'ALFU';
l_start TIMESTAMP;
l_end TIMESTAMP;
Time_Taken VARCHAR(20);
BEGIN
l_start := clock_timestamp();
DELETE FROM SHIFT_DATES_COUNT;
FOR I IN C1 LOOP
IF I.TABLE_NAME <> TABLENAME THEN
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT,
END_COUNT, current_timestamp::timestamp(0));
TABLENAME := I.TABLE_NAME;
END_COUNT := 0;
START_COUNT := 0;
END IF;
IF STRPOS(I.SQL_TEXT, 'END') > 0 THEN
EXECUTE I.SQL_TEXT INTO END_COUNT;
RAISE NOTICE '% ', ('END: ' || I.SQL_TEXT);
ELSE
EXECUTE I.SQL_TEXT INTO START_COUNT;
RAISE NOTICE '% ', ('START: ' || I.SQL_TEXT);
END IF;
END LOOP;
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT, END_COUNT,
current_timestamp::timestamp(0));
RAISE NOTICE '% ', ('INSERT INTO SHIFT_DATES_COUNT Done...');
l_end := clock_timestamp();
Time_Taken := (l_end-l_start);
RAISE NOTICE '% ', ('FIND_SHIFT_DATES Took: ' || Time_Taken );
END;
$BODY$;
Please let me know how can I use the date_to_find input parameter in the Dynamic SQL in the Cursor in the above Function.
You can use unbound cursor, clause fetch to get data from cursor, and exit when not found to finish, like:
CREATE OR REPLACE FUNCTION example (p_name text) RETURNS void LANGUAGE 'plpgsql' AS $$
DECLARE
C1 refcursor;
res record;
BEGIN
OPEN c1 FOR EXECUTE 'SELECT * FROM pg_database WHERE datname like ''%'||p_name||'%''';
LOOP
FETCH c1 INTO res;
EXIT WHEN not found;
raise notice 'value datname: %',res.datname;
END LOOP;
CLOSE c1;
RETURN;
END; $$;
--in my case
select example ('test')
NOTICE: value datname: test
NOTICE: value datname: test_msmov
NOTICE: value datname: test_resources
NOTICE: value datname: test_load_table
NOTICE: value datname: test_resources2
Total query runtime: 63 msec
1 row retrieved.
You can use EXECUTE clause for open cursor, see the documentation of PostgreSQL
https://www.postgresql.org/docs/10/plpgsql-cursors.html#PLPGSQL-CURSOR-OPENING
Example:
OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;

Inserting into dynamic table partition in postgresql with return

I created a table partition that will create a table if it is not yet existing the table names are on a monthly basis. I need this function to return the inserted ID but I'm getting this error of column "partition" does not exist it seems that my schema(partition) is considered column in this code
CREATE OR REPLACE FUNCTION partition.itinerary_partition_function()
RETURNS TRIGGER AS
$BODY$
DECLARE
reflowId bigint;
_tablename text;
_startyear text;
_startmonth text;
_fulltablename text;
BEGIN
--Takes the current inbound "time" value and determines when midnight is for the given date
_startyear := to_char(now(), 'YYYY');
_startmonth := to_char(now(), 'MM');
_tablename := 'itinerary_'||_startyear || '_' || _startmonth;
_fulltablename := 'partition.' || _tablename;
-- Check if the partition needed for the current record exists
PERFORM 1
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND c.relname = _tablename
AND n.nspname = 'partition';
-- If the partition needed does not yet exist, then we create it:
-- Note that || is string concatenation (joining two strings to make one)
IF NOT FOUND THEN
EXECUTE 'CREATE TABLE partition.' || quote_ident(_tablename) || '()INHERITS (partition.itinerary)';
-- Table permissions are not inherited from the parent.
-- If permissions change on the master be sure to change them on the child also.
EXECUTE 'ALTER TABLE partition.' || quote_ident(_tablename) || ' OWNER TO postgres';
-- Indexes are defined per child, so we assign a default index that uses the partition columns
EXECUTE 'CREATE INDEX ' || quote_ident(_tablename||'_indx1') || ' ON partition.' || quote_ident(_tablename) || ' (id)';
END IF;
BEGIN
EXECUTE format('INSERT INTO %I SELECT $1.*', "partition." || _tablename)
USING NEW;
RETURN NEW;
END;
END;
$BODY$
LANGUAGE plpgsql;
After this code I am calling it in another insert function
CREATE OR REPLACE FUNCTION partition.insert_data(username text,jsonData jsonb) RETURNS bigint AS
$$
DECLARE reflowId bigint;
BEGIN
INSERT INTO reflow_partition.itinerary(username, data)
VALUES (username, jsonData) RETURNING id;
END;
$$
LANGUAGE plpgsql;
Try changing this:
EXECUTE format('INSERT INTO %I SELECT $1.*', "partition." || _tablename)
to this:
EXECUTE format('INSERT INTO %1$I.%2$I SELECT $1.*', 'partition', _tablename)

PostgreSQL: compare NULL and normal value

I have a trigger in PostgreSQL
CREATE OR REPLACE FUNCTION table_update_func_pk1() RETURNS trigger AS $$
DECLARE
ri RECORD;
old_value TEXT;
new_value TEXT;
BEGIN
FOR ri IN
SELECT column_name FROM information_schema.columns
WHERE
table_schema = quote_ident('public')
AND table_name = quote_ident(TG_TABLE_NAME)
ORDER BY ordinal_position
LOOP
EXECUTE 'SELECT ($1).' || ri.column_name || '::text' INTO new_value USING NEW;
EXECUTE 'SELECT ($1).' || ri.column_name || '::text' INTO old_value USING OLD;
IF new_value <> old_value AND ri.column_name != 'update_by' THEN
INSERT INTO protokoll(datetime, operation, tabelle, field, pk1, old_value, new_value, update_by)
VALUES(now(), TG_OP, TG_TABLE_NAME, ri.column_name, NEW.cfg, old_value, new_value, NEW.update_by);
END IF;
END LOOP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Sometimes if old_value is changed from NULL to normal value (or from normal value to NULL), the condition "new_value <> old_value" is NOT true but unknown. I would like ask, there is a method that I can get true in the case. Thanks.
Use is distinct from:
if new_value IS DISTINCT FROM old_value and ri.column_name <> 'update_by' then
...
end if;

Postgresql function return multiple select statements

Can any one of you tell me how to approach this:
CREATE OR REPLACE FUNCTION name()
RETURNS ????? AS
$func$
BEGIN
SELECT * FROM tbl_a a;
SELECT * FROM tbl_b b;
END
$func$ LANGUAGE plpgsql;
Both tables have different structures.
You can use cursors but I can hardly imagine why you need such a function.
CREATE OR REPLACE FUNCTION my_multiselect(refcursor, refcursor) RETURNS VOID AS
$func$
BEGIN
OPEN $1 FOR SELECT * FROM information_schema.routines;
OPEN $2 FOR SELECT * FROM information_schema.sequences;
END
$func$ LANGUAGE plpgsql;
BEGIN;
SELECT my_multiselect('first_cursor_to_routines', 'second_cursor_to_sequences');
FETCH ALL IN first_cursor_to_routines;
FETCH ALL IN second_cursor_to_sequences;
COMMIT;
I'm not really sure what you're doing with this, but it sounds like you just want to return a union of these distinct result sets. You can do this with a dynamic query. I'm using Postgres 9.4.
CREATE OR REPLACE FUNCTION make_query(IN p_tables text[])
RETURNS void AS
$BODY$
DECLARE
v_qry text;
v_cols text;
v_types text;
v_as text;
BEGIN
EXECUTE format('
WITH sub AS (
SELECT
table_name,
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = ANY(%L)
ORDER BY
table_name,
ordinal_position)
,sub2 AS(
SELECT
DISTINCT ON (column_name, data_type)
column_name || '' '' || data_type AS def
FROM
sub
)
SELECT
string_agg(def, '','')
FROM
sub2;
',
p_tables
) INTO v_types;
v_qry := '
CREATE OR REPLACE FUNCTION name()
RETURNS TABLE(' || v_types || ') AS
$func$';
FOR i IN 1..array_upper(p_tables, 1)
LOOP
v_as := 'tbl' || i;
EXECUTE format('
WITH sub AS (
SELECT
table_name,
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = ANY(%L)
ORDER BY
table_name,
ordinal_position)
,sub2 AS(
SELECT
DISTINCT ON (column_name, data_type)
CASE WHEN table_name = ''%I''
THEN %L || ''.'' || column_name
ELSE ''NULL::'' || data_type
END AS cols
FROM
sub
)
SELECT
string_agg(cols, '','')
FROM
sub2;
',
p_tables,
p_tables[i],
v_as
) INTO v_cols;
IF i > 1 THEN
v_qry := v_qry || '
UNION ALL';
END IF;
v_qry := v_qry || '
SELECT ' || v_cols || ' FROM ' || p_tables[i] || ' AS ' || v_as;
IF i = array_upper(p_tables, 1) THEN
v_qry := v_qry || ';';
END IF;
END LOOP;
v_qry := v_qry || '
$func$ LANGUAGE sql;
';
EXECUTE v_qry;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Sorry it looks ugly here, but this formatting helps the final product look nicer. If you're shy about executing a dynamic query like this off the bat, just replace EXECUTE v_qry; with RAISE INFO 'v_qry: %', v_qry; and it will simply print the dynamic query out in a message without executing it, so you can review what it will do once executed.
Then execute make_query() with a list of tables you want to display like this:
SELECT make_query(ARRAY['tbl_a', 'tbl_b']);
The result is that you will now have a function called name() which you can call in order to see the results of both tables at the same time, with all the union details already sorted out:
SELECT * FROM name();