nested cursor loop in postgresql - postgresql

I am new to postgresql, and get a problem about nested loop.Here is my code:
CREATE TABLE q_39442172
(
id character varying,
event_id character varying,
createdat character varying
);
insert into q_39442172 values('id1', 'event_1', '20160789');
insert into q_39442172 values('id2', 'event_2', '20160689');
insert into q_39442172 values('id3', 'event_3', '20160679');
insert into q_39442172 values('id4', 'event_4', '20160579');
insert into q_39442172 values('id3', 'event_3', '20160579');
insert into q_39442172 values('id2', 'event_5', '20160379');
insert into q_39442172 values('id1', 'event_6', '20160339');
create or replace function query_event_sequence() returns table( r_id character varying, r_events text ) as
$$
declare
vc_id character varying;
vc_event_id character varying;
begin
for ref_User in execute 'select distinct id from q_39442172 order by id' loop
vc_id := ref_User.id;
r_id := ref_User.id;
for ref_Event in execute 'select event_id from q_39442172 where id = ' || vc_id loop
vc_event_id := ref_Event.event_id;
r_events := concat_ws( ',', r_events, vc_event_id );
end loop;
raise notice '%: %', r_id, r_events;
return next;
end loop;
end;
$$
language plpgsql;
The exception i get:
NOTICE: id1: event_6,event_1
ERROR: cursor "<unnamed portal 2>" already in use
CONTEXT: PL/pgSQL function query_event_sequence() line 13 at OPEN
********** Error **********
ERROR: cursor "<unnamed portal 2>" already in use
SQL state: 42P03
Actually, using array_agg can do what i want to do, but i am just confused about why nested cursor loop in my code won't work.

You don't need a function or a cursor for this. A single SQL statement will do:
select string_agg(concat_ws(',', event_id, id), ',' order by id)
from q_39442172
where id in (select id from q_39442172)

Related

Count for each columns in table (not null) PostgreSQL PL/pgSQL

I am trying to count the number of rows that do not contain null for each column in the table
There is a simple table actor_new
The first 2 columns (actor_id, first_name) contain 203 rows not null
Other 2 columns (last_name, last_update) contain 200 rows not null
This is a simple test that outputs the same value for all columns, but if you perform select separately, then everything works correctly, please help me understand the LOOP block
create or replace function new_cnt_test_ho(in_table text, out out_table text, out cnt_rows int) returns setof record AS $$
DECLARE i text;
BEGIN
FOR i IN
select column_name
from information_schema."columns"
where table_schema = 'public'
and table_name = in_table
LOOP
execute '
select $1, count($1)
from '|| quote_ident(in_table) ||'
where $1 is not null '
INTO out_table, cnt_rows
using i, quote_literal(i), quote_ident(in_table), quote_literal(in_table) ;
return next;
END LOOP;
END;
$$LANGUAGE plpgsql
Result:
select * from new_cnt_test_ho('actor_new')
out_table |cnt_rows|
-----------+--------+
actor_id | 203|
first_name | 203|
last_name | 203|
last_update| 203|
There are 4 parameters specified in using, because I assumed that the error was in quotes, I took turns playing with arguments from 1 to 4
The correct result should be like this
out_table |cnt_rows|
-----------+--------+
actor_id | 203|
first_name | 203|
last_name | 200|
last_update| 200|
based on your title: input is a table name, output is a table one column is column name, another column is return of count(column)
first check the table exists or not.
then for loop get each column name, after that for each column name run a query.
a sample query is select 'cola',count(cola) from count_nulls. first occurrence is literal 'cola', so we need quote_literal(cols.column_name),
second is the column name, so we need use quote_ident(cols.column_name)
select 'cola',count(cola) from count_nulls will count column cola all not null value. if a column all value is null then return 0.
The following function will return the expected result. Can be simplified, since i use a lot of raise notice.
CREATE OR REPLACE FUNCTION get_all_nulls (_table text)
RETURNS TABLE (
column_name_ text,
numberofnull bigint
)
AS $body$
DECLARE
cols RECORD;
_sql text;
_table_exists boolean;
_table_reg regclass;
BEGIN
_table_reg := _table::regclass;
_table_exists := (
SELECT
EXISTS (
SELECT
FROM
pg_tables
WHERE
schemaname = 'public'
AND tablename = _table));
FOR cols IN
SELECT
column_name
FROM
information_schema.columns
WHERE
table_name = _table
AND table_schema = 'public' LOOP
_sql := 'select ' || quote_literal(cols.column_name) || ',count(' || quote_ident(cols.column_name) || ') from ' || quote_ident(_table::text);
RAISE NOTICE '_sql:%', _sql;
RETURN query EXECUTE _sql;
END LOOP;
END;
$body$ STRICT
LANGUAGE plpgsql;
setup.
begin;
create table count_nulls(cola int, colb int, colc int);
INSERT into count_nulls values(null,null,null);
INSERT into count_nulls values(1,null,null);
INSERT into count_nulls values(2,3,null);
commit;

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"}]}');

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;

PL/pgSQL: How to use IF NEW.<variable_column_name> <> OLD.<variable_column_name>

I am pretty new to PL/pgSQL programming. I have a requirement of audit logging updated columns in my table
Table
create table sample_table(name varchar(15),city varchar(15),age int,mail varchar(20) primary key);
Audit table
create table sample_table__audits_dynamicols(mail varchar(20), columnchanged varchar(10), oldvalue varchar(10), changed_on timestamp(6) NOT NULL)
Trigger Function
CREATE FUNCTION public.log_sample_table_allchanges() RETURNS trigger AS $BODY$DECLARE
_colname text;
_tablename varchar(15) := 'sample_table';
_schema varchar(15) := 'public';
_changed_on time := now();
BEGIN
FOR _colname IN SELECT column_name FROM information_schema.Columns WHERE table_schema = _schema AND table_name = _tablename LOOP
IF NEW._colname <> OLD._colname THEN
INSERT INTO sample_table__audits_dynamicols(mail,columnchanged, oldvalue ,changed_on)
VALUES(OLD.mail,_colname,OLD.:_colname,_changed_on);
END IF;
END LOOP;
RETURN NEW;
END$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
Trigger
create TRIGGER log_sample_table_allchanges
BEFORE UPDATE
ON SAMPLE_TABLE
FOR EACH ROW
EXECUTE PROCEDURE log_sample_table_allchanges();
Requirement: Whenever a column value is changed i want to log it as
(mail, columnname, columnvalue, date)
E.g:
insert into sample_table (name, mail, city, age) values('kanta','mk#foo.com','hyd',23);
insert into sample_table (name, mail, city, age) values('kmk','mk#gmail.com','hyd',23);
So when i update like the following
update sample_table set age=24 where mail='mk#foo.com';
update sample_table set city='bza' where mail='mk#gmail.com'
I want audit table to record like
(mk#foo.com,age,23, timestamp)
(mk#gmail.com, city, hyd, timestamp)
Right now I am facing issue with column comparison in my Trigger function. Please help me rectifying my Trigger function to meet my requirement.
You may use EXECUTE to get the values of columns dynamically and do the comparison.
CREATE OR REPLACE FUNCTION public.log_sample_table_allchanges() RETURNS trigger AS
$BODY$
DECLARE
_colname text;
_tablename varchar(15) := 'sample_table';
_schema varchar(15) := 'public';
_changed_on timestamp := now();
_old_val text;
_new_val text;
BEGIN
FOR _colname IN SELECT column_name FROM information_schema.Columns WHERE table_schema = _schema AND table_name = _tablename
LOOP
EXECUTE 'SELECT $1.' || _colname || ', $2.' || _colname
USING OLD,NEW
INTO _old_val, _new_val; --get the old and new values for the column.
IF _new_val <> _old_val THEN
INSERT INTO sample_table__audits_dynamicols(mail,columnchanged, oldvalue ,changed_on)
VALUES(OLD.mail,_colname,_old_val,_changed_on);
END IF;
END LOOP;
RETURN NEW;
END$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
I'm not sure why you have defined mail as a PRIMARY KEY in the audits table, it will cause unique constraint violation if the same mail gets updated twice.

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;