JSONB to record update using for loop - postgres - postgresql

I am trying to update multiple fields from JSONB but getting error like cannot call_populate composite on an array.
I have written below code:-
do $$
<<myjsonb>>
declare
spec jsonb:=('[
{"schema_name":"public",
"table_name":"temp",
"nw_schema":public,
"nw_table": "temp",
"nw_col":"id"},
{"schema_name":"public",
"table_name":"temp",
"nw_schema":public,
"nw_table": "temp",
"nw_col":"name"}
]');
i record;
BEGIN
for i in SELECT * from jsonb_to_record(spec) as (schema_name text, table_name text, nw_schema text, nw_table text, nw_col text)
LOOP
update my_table set schema_name=i->>schema_name, table_name=i->>table_name where nw_schema=i->>nw_schema and nw_table=i->>nw_table and nw_col=i->>nw_col;
end loop;
end myjsonb $$;

There are three things to touch.
Your JSON syntax is invalid, "nw_schema":public must be quoted;
jsonb_to_record shall become jsonb_to_recordset;
Expressions like i->>schema_name shall become i.schema_name.
So here it is corrected:
do $$
declare
spec jsonb:='[
{
"schema_name":"public",
"table_name":"temp",
"nw_schema":"public",
"nw_table": "temp",
"nw_col":"id"
},
{
"schema_name":"public",
"table_name":"temp",
"nw_schema":"public",
"nw_table": "temp",
"nw_col":"name"
}
]';
i record;
begin
for i in select * from jsonb_to_recordset(spec) as (schema_name text, table_name text, nw_schema text, nw_table text, nw_col text)
loop
update my_table
set schema_name = i.schema_name, table_name = i.table_name
where nw_schema = i.nw_schema and nw_table = i.nw_table and nw_col = i.nw_col;
end loop;
end $$;

Related

postgresql for loop script in text form can not be executed

I am trying to write function in postgresql, that creates temp_table with columns table_name text, table_rec jsonb and fill it through for loop with table names from my table containing names of tables and records in json. I have the for loop in string and I want to execute it. But it doesnt work.
I have variable rec record, sql_query text and tab_name text and I want to do this:
CREATE OR REPLACE FUNCTION public.test51(
)
RETURNS TABLE(tabel_name text, record_json jsonb)
LANGUAGE 'plpgsql'
COST 100
VOLATILE
ROWS 1000
AS $BODY$
declare
rec record;
tabel_name text;
tabel_names text[];
counter integer := 1;
sql_query text;
limit_for_sending integer;
rec_count integer;
begin
select into tabel_names array(select "TABLE_NAME" from public."TABLES");
create temp table temp_tab(tab_nam text, recik jsonb);
while array_length(tabel_names, 1) >= counter loop
tabel_name := '"' || tabel_names[counter] || '"';
select into limit_for_sending "TABLE_LIMIT_FOR_SENDING_DATA" from public."TABLES" where "TABLE_NAME" = tabel_name;
sql_query := 'select count(*) from public.' || tabel_name;
execute sql_query into rec_count;
if (rec_count >= limit_for_sending and limit_for_sending is not null) then
sql_query := 'for rec in select * from public.' || tabel_name || '
loop
insert into temp_tab
select ' || tabel_name || ', to_jsonb(rec);
end loop';
execute sql_query;
end if;
counter := counter + 1;
end loop;
return query
select * from temp_tabik;
drop table temp_tabik;
end;
$BODY$;
Thank you for response.
It seems you have some table that contains the information for which tables you want to return all rows as JSONB. And that meta-table also contains a column that sets a threshold under which the rows should not be returned.
You don't need the temp table or an array to store the table names. You can iterate through the query on the TABLES table and run the dynamic SQL directly in that loop.
return query in PL/pgSQL doesn't terminate the function, it just appends the result of the query to the result of the function.
Dynamic SQL is best created using the format() function because it is easier to read and using the %I placeholder will properly deal with quoted identifiers (which is really important as you are using those dreaded upper case table names)
As far as I can tell, your function can be simplified to:
CREATE OR REPLACE FUNCTION public.test51()
RETURNS TABLE(tabel_name text, record_json jsonb)
LANGUAGE plpgsql
AS
$BODY$
declare
rec record;
sql_query text;
rec_count bigint;
begin
for rec in
select "TABLE_NAME" as table_name, "TABLE_LIMIT_FOR_SENDING_DATA" as rec_limit
from public."TABLES"
loop
if rec.rec_limit is not null then
execute format('select count(*) from %I', rec.table_name)
into rec_count;
end if;
if (rec.rec_limit is not null and rec_count >= rec.rec_limit) then
sql_query := format('select %L, to_jsonb(t) from %I as t', rec.table_name, rec.table_name);
return query execute sql_query;
end if;
end loop;
end;
$BODY$;
Some notes
the language name is an identifier and should not be enclosed in single quotes. This syntax is deprecated and might be removed in a future version so don't get used to it.
you should really avoid those dreaded quoted identifiers. They are much more trouble than they are worth it. See the Postgres wiki for details.

How to PREPARE & EXECUTE a query from a string stored in a table

This stored function returns a query:
DROP FUNCTION IF EXISTS get_query (
ctl text, scm text, tbl text, seq text);
CREATE OR REPLACE FUNCTION get_query (
ctl text, scm text, tbl text, seq text)
RETURNS text
AS
$$
select concat('insert into ',$2,'.',$1, ' select nextval("',$4,'") as id, ',
string_agg(concat('NEW.', column_name), ', '), ', current_timestamp as audited_at;')
from information_schema.columns
where table_catalog = $1
and table_schema = $2
and table_name = $3
$$
LANGUAGE sql;
How do I PREPARE the query that this function returns.
I want insert a record in a table when a trigger is fired but I don't want to specify the list of columns to be inserted. The schema might keep changing. Hence, trying to use prepared statements.
This sample code illustrates how I mean the query string to be executed:
DROP FUNCTION IF EXISTS fn_name (store_temporary_query text);
CREATE OR REPLACE FUNCTION fn_name (store_temporary_query text)
RETURNS table (query text)
LANGUAGE plpgsql
AS
$$
begin
select 'select 1 as ID' into store_temporary_query;
return query (select store_temporary_query);
end;
$$
select fn_name('');
The above query gives the following output
fn_name
select 1 as ID
The desired result is the query
ID
1
EDIT #2
DROP FUNCTION IF EXISTS fn_name (store_temporary_query text);
CREATE OR REPLACE FUNCTION fn_name (store_temporary_query text)
RETURNS table (query text)
LANGUAGE plpgsql
AS
$$
begin
select 'select 1 as ID;' into store_temporary_query;
return query execute store_temporary_query;
end;
$$
select fn_name('');
This gets us here,
Error executing SQL statement. ERROR: syntax error at or near "select"
Position: 254 - Connection: Aurora Legacy: 794ms
You need EXECUTE to execute a query stored in a string:
RETURN QUERY EXECUTE store_temporary_query;

"INSERT INTO ... FETCH ALL FROM ..." can't be compiled

I have some function on PostgreSQL 9.6 returning a cursor (refcursor):
CREATE OR REPLACE FUNCTION public.test_returning_cursor()
RETURNS refcursor
IMMUTABLE
LANGUAGE plpgsql
AS $$
DECLARE
_ref refcursor = 'test_returning_cursor_ref1';
BEGIN
OPEN _ref FOR
SELECT 'a' :: text AS col1
UNION
SELECT 'b'
UNION
SELECT 'c';
RETURN _ref;
END
$$;
I need to write another function in which a temp table is created and all data from this refcursor are inserted to it. But INSERT INTO ... FETCH ALL FROM ... seems to be impossible. Such function can't be compiled:
CREATE OR REPLACE FUNCTION public.test_insert_from_cursor()
RETURNS table(col1 text)
IMMUTABLE
LANGUAGE plpgsql
AS $$
BEGIN
CREATE TEMP TABLE _temptable (
col1 text
) ON COMMIT DROP;
INSERT INTO _temptable (col1)
FETCH ALL FROM "test_returning_cursor_ref1";
RETURN QUERY
SELECT col1
FROM _temptable;
END
$$;
I know that I can use:
FOR _rec IN
FETCH ALL FROM "test_returning_cursor_ref1"
LOOP
INSERT INTO ...
END LOOP;
But is there better way?
Unfortunately, INSERT and SELECT don't have access to cursors as a whole.
To avoid expensive single-row INSERT, you could have intermediary functions with RETURNS TABLE and return the cursor as table with RETURN QUERY. See:
Return a query from a function?
CREATE OR REPLACE FUNCTION f_cursor1_to_tbl()
RETURNS TABLE (col1 text) AS
$func$
BEGIN
-- MOVE BACKWARD ALL FROM test_returning_cursor_ref1; -- optional, see below
RETURN QUERY
FETCH ALL FROM test_returning_cursor_ref1;
END
$func$ LANGUAGE plpgsql; -- not IMMUTABLE
Then create the temporary table(s) directly like:
CREATE TEMP TABLE t1 ON COMMIT DROP
AS SELECT * FROM f_cursor1_to_tbl();
See:
Creating temporary tables in SQL
Still not very elegant, but much faster than single-row INSERT.
Note: Since the source is a cursor only the first call succeeds. Executing the function a second time would return an empty set. You would need a cursor with the SCROLL option and move to the start for repeated calls.
This function does INSERT INTO from refcursor. It is universal for all the tables. The only requirement is that all columns of table corresponds to columns of refcursor by types and order (not necessary by names).
to_json() does the trick to convert any primitive data types to string with double-quotes "", which are later replaced with ''.
CREATE OR REPLACE FUNCTION public.insert_into_from_refcursor(_table_name text, _ref refcursor)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
_sql text;
_sql_val text = '';
_row record;
_hasvalues boolean = FALSE;
BEGIN
LOOP --for each row
FETCH _ref INTO _row;
EXIT WHEN NOT found; --there are no rows more
_hasvalues = TRUE;
SELECT _sql_val || '
(' ||
STRING_AGG(val.value :: text, ',') ||
'),'
INTO _sql_val
FROM JSON_EACH(TO_JSON(_row)) val;
END LOOP;
_sql_val = REPLACE(_sql_val, '"', '''');
_sql_val = TRIM(TRAILING ',' FROM _sql_val);
_sql = '
INSERT INTO ' || _table_name || '
VALUES ' || _sql_val;
--RAISE NOTICE 'insert_into_from_refcursor(): SQL is: %', _sql;
IF _hasvalues THEN --to avoid error when trying to insert 0 values
EXECUTE (_sql);
END IF;
END;
$$;
Usage:
CREATE TABLE public.table1 (...);
PERFORM my_func_opening_refcursor();
PERFORM public.insert_into_from_refcursor('public.table1', 'name_of_refcursor_portal'::refcursor);
where my_func_opening_refcursor() contains
DECLARE
_ref refcursor = 'name_of_refcursor_portal';
OPEN _ref FOR
SELECT ...;

Issue with dynamic sql

Hi every one I have multiple spatial tables That I want to control, so that I created a table where I will store the name of the operation applied on my layers tables(insert,update or delete), operation time and the team who did it, number of spatial tables created.
My script table
CREATE TABLE public.monitoring_table
(
operation character(1) COLLATE pg_catalog."default",
operat_ime timestamp without time zone,
userid text COLLATE pg_catalog."default",
dc_team text COLLATE pg_catalog."default",
number_pts_created integer,
id integer NOT NULL DEFAULT nextval('monitoring_table_id_seq'::regclass),
CONSTRAINT monitoring_table_pkey PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.monitoring_table
OWNER to postgres;
after that I stored all the teams that I have on my table :
insert into monitoring_table (dc_team) values ('abdoulhassan');
insert into monitoring_table (dc_team) values ('abdoulei');
insert into monitoring_table (dc_team) values ('danis');
insert into monitoring_table (dc_team) values ('david');
insert into monitoring_table (dc_team) values ('joseph');
To calculate the number of spatial tables created, I executed this function :
My counting function :
DROP FUNCTION get_dc_team_counting();
CREATE OR REPLACE FUNCTION get_dc_team_counting()
RETURNS bigint AS
$func$
DECLARE
dc_team text;
_tbl_pattern text;
_schema text = 'sige';
_tb_name information_schema.tables.table_name%TYPE;
_tc bigint;
BEGIN
FOR _tb_name IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = _schema
AND table_name ~ _tbl_pattern
LOOP
EXECUTE format('SELECT count(*) FROM %I.%I where id= 583', _schema, _tb_name)
INTO _tc;
return _tc;
END LOOP;
END
$func$ LANGUAGE plpgsql
To get the team I executed this function :
CREATE OR REPLACE FUNCTION get_team()
RETURNS text AS -- or whatever you want to return
$func$
DECLARE
dc_team text;
_tbl_pattern text;
_schema text = 'public';
_tb_name information_schema.tables.table_name%TYPE; -- currently varchar
_tc text;
BEGIN
FOR _tb_name IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = _schema
AND table_name ~ _tbl_pattern -- see below!
LOOP
EXECUTE format('SELECT dc_team FROM %I.%I where id = 26', _schema, _tb_name)
INTO _tc;
return _tc;
END LOOP;
END
$func$ LANGUAGE plpgsql;
In my where clause I want to get dynamically the value of the ID. I don't want to give it manually in the function. I don't see how to do it.
Now I created a trigger function to be able to update my table if a row was inserted, updated or deleted. I did it this way :
CREATE OR REPLACE FUNCTION process_monitoring() RETURNS TRIGGER AS $monitoring$
BEGIN
IF (TG_OP = 'DELETE') THEN
update monitoring_table set operation = 'D', operat_ime = now(), userid = user ,dc_team = OLD.dc_team, number_pts_created = get_dc_team_counting() where dc_team = get_team();
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
update monitoring_table set operation = 'U',operat_ime = now(),userid = user , dc_team = NEW.dc_team, number_pts_created = get_dc_team_counting() where dc_team = get_team();
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
update monitoring_table set operation = 'I', operat_ime = now(), userid = user, dc_team = NEW.dc_team, number_pts_created = get_dc_team_counting() where dc_team = get_team();
RETURN NEW;
END IF;
RETURN NULL;
END;
$monitoring$ LANGUAGE plpgsql;
CREATE TRIGGER monitoring
AFTER INSERT OR UPDATE OR DELETE ON sige.valve
FOR EACH ROW EXECUTE PROCEDURE process_monitoring();
In my previous functions I used known values of ids in the where clauses, but when I try to insert or update a value on the concerned table I get this error :
the control attempted it's end without return
CONTEXT: fonction PL/pgsql get_team()
instruction SQL « update monitoring_table set operation = 'U',operat_ime = now(),userid = user , dc_team = NEW.dc_team, number_pts_created = get_dc_team_counting() where dc_team = get_team() »
fonction PL/pgsql process_monitoring(), ligne 10 à instruction SQL
If you have any idea about the origin of the error and how to get dynamically a value of a field and put it in the where clause tell me please.
I'm a newbie and I'm struggling, any assistance would be warmly appreciated.
I'm using PostgreSQL.

inserting record from one table to another using record in postgres for loop

I'm trying to insert data from one table to another in postgres using for...loop. The approach is given below.
DO LANGUAGE PLPGSQL $$
DECLARE
data record;
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop values data;<br>
END LOOP;
END;
$$
I've used record for the row iteration but couldn't find out how to insert that 'data' into 'for_loop' table. When I run this code it gives me the following error:
ERROR: syntax error at or near "data"
LINE 9: INSERT INTO for_loop values data;
^
Here are my two tables.
create table forall_data(
nid numeric(15,0)not null,
name varchar(15) not null,
city varchar(10) not null,
contact numeric(11,0) not null
);
create table for_loop(
nid numeric(15,0)not null,
name varchar(15) not null,
city varchar(10) not null,
contact numeric(11,0) not null
);
What should I try here to insert that 'data' record into 'for_loop' table? Thanks in advance.
'data' is untyped record, so I have to mention the column name to retrieve the value of this record.
DO LANGUAGE PLPGSQL $$
DECLARE
data record;
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop values (data.nid,data.name,data.city,data.contact);
END LOOP;
END;
$$
But using %rowtype or table type is more flexible and no need to mention the column names to retrieve column value from the variable
DO LANGUAGE PLPGSQL $$
DECLARE
data forall_data; --- or data forall_data%rowtype
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop select (data).*;
END LOOP;
END;
$$
cheers :)
use this code:
DO LANGUAGE PLPGSQL $$
DECLARE
rec record;
BEGIN
FOR rec IN SELECT * FROM budzet.forall_data
LOOP
INSERT INTO budzet.for_loop(nid, name , city , contact)
VALUES (rec.nid, rec.name , rec.city , rec.contact);
END LOOP;
END;
$$
You can try Loop with some exit condition.
DO LANGUAGE PLPGSQL $$
DECLARE
rec CURSOR FOR SELECT * FROM forall_data;
V_nid numeric;
V_name varchar(15);
V_city varchar(10);
V_contact numeric;
BEGIN
OPEN rec;
LOOP
FETCH rec INTO V_nid ,V_name ,V_city,V_contact;
EXIT WHEN(rec IS NULL);
INSERT INTO for_loop(nid, name , city , contact)
VALUES (V_nid , V_name , V_city , V_contact);
END LOOP;
CLOSE rec;
END;
$$
Hope it work for you.
EDIT: Alternately you can try this without using loop insert statement from one table and select statement from another table.
INSERT INTO for_loop(nid, name , city , contact)
select nid, name , city , contact FROM forall_data;