nested CURSOR on information schema - postgresql

I am doing nested cursor using information schema to SAVE my time to insert on all tables.
I am getting error
[42P01] ERROR: relation "rec2" does not exist
Where: PL/pgSQL function test() line 22 at SQL statement
Please help me to resolve this issue.
Below is the code, any help is greatly appreciated.
Thanking in advance.
CREATE OR REPLACE FUNCTION test()
RETURNS VOID AS
$$
DECLARE
rec1 RECORD;
rec2 RECORD;
BEGIN
FOR rec1 IN
SELECT
C.table_name,
C.column_name
FROM "information_schema"."columns" AS C
WHERE C.table_schema = 'prod1'
AND C.table_name LIKE 'prd_tbl%'
AND C.column_name <> 'id'
ORDER BY C.table_name ASC
LOOP
FOR rec2 IN
SELECT table_name
FROM "information_schema"."tables"
WHERE table_schema = 'ec2'
ORDER BY table_name ASC
LOOP
SELECT rec2.*
FROM rec2
EXCEPT
SELECT rec1.column_name.*
FROM rec1;
END LOOP;
END LOOP;
END;
$$
LANGUAGE plpgsql STABLE;

Related

Looping through tables to retrieve max dates from a created date column in Postgres

I need to monitor the ongoing flow of data using the latest created date for a set of tables.
Basically, I need to batch run
SELECT MAX(z_date_creation)
FROM table_schema.table_name
on a set of tables I retrieve with
SELECT
c.table_schema,
c.table_name
FROM information_schema."columns" c
WHERE c.column_name LIKE '%z_date_creation'
AND c.table_schema = 'datawarehouse'
AND c.table_name NOT LIKE 'partition%'
and then feed it to a dedicated "ods.dates_derniere_maj" table I'll be pluging reports on.
I am using a cursor for like of a better idea to iterate through the tables in need to get the MAX(z_date_creation) from. I manage to feed the table_schema and table_name values into my ods.dates_derniere_maj table but cannot find a way to also get the MAX(z_date_creation) from these tables.
I'm stuck with the nested query part.
Here is what I've come up with so far :
DO $$
DECLARE
table_rec record ;
max_date TEXT DEFAULT NOW();
cursor1 CURSOR FOR
SELECT DISTINCT c.table_schema, c.table_name, c.column_name
FROM information_schema."columns" c
WHERE c.table_schema = 'datawarehouse'
AND c.table_name NOT LIKE 'partition%'
AND c.column_name LIKE '%creation%';
from_clause TEXT;
date_column TEXT;
BEGIN
FOR table_rec IN cursor1
LOOP
from_clause := CONCAT(table_rec.table_schema, '.', table_rec.table_name);
date_column := CONCAT(table_rec.table_schema, '.', table_rec.table_name,'.','z_date_creation');
Which code herebelow ?
PREPARE nom_req (text, text) AS
SELECT MAX($1) FROM $2 ; ---> not working, syntax error on $2
max_date := EXECUTE nom_req (date_column, from_clause) ; ---> not working
SELECT MAX(date_column) INTO max_date FROM CONCAT(from_clause) ; ----> not working
INSERT INTO ods.dates_derniere_maj (schema_name, table_name, z_date_creation_max)
VALUES (table_rec.table_schema, table_rec.table_name, max_date);
END LOOP;
END $$;`
I have tried passing the table_rec.table_schema and table_rec.table_name variables directly in the FROM clause but that didn't work so I tried to concatenate them beforehand.
Any help will be much appreciated !
Thanks a bunch !
Franck
Try something like this :
CREATE FUNCTION max_date() RETURNS date LANGUAGE plpgsql AS
$$
DECLARE
table_rec record ;
max_date date ;
result date ;
cursor1 CURSOR FOR
SELECT DISTINCT c.table_schema, c.table_name, c.column_name
FROM information_schema."columns" c
WHERE c.table_schema = 'datawarehouse'
AND c.table_name NOT LIKE 'partition%'
AND c.column_name LIKE '%creation%';
BEGIN
FOR table_rec IN cursor1
LOOP
EXECUTE FORMAT( 'SELECT max(%I) FROM %I.%I'
, table_rec.column_name
, table_rec.table_schema
, table_rec.table_name
)
INTO max_date ;
result = greatest(result, max_date) ;
END LOOP ;
RETURN result ;
END ;
$$ ;
see test result in dbfiddle
Here is what I came up with with Edouard's invaluable help !
DO $$
DECLARE
table_rec record ;
max_date TEXT DEFAULT NOW();
cursor1 CURSOR FOR SELECT DISTINCT c.table_schema, c.table_name, c.column_name
FROM information_schema."columns" c
WHERE c.table_schema = 'datawarehouse'
AND c.table_name NOT LIKE 'partition%'
AND c.column_name LIKE '%creation%';
BEGIN
FOR table_rec IN cursor1
LOOP
EXECUTE FORMAT( 'SELECT max(%I) FROM %I.%I'
, table_rec.column_name
, table_rec.table_schema
, table_rec.table_name
)
INTO max_date ;
INSERT INTO ods.dates_derniere_maj (schema_name, table_name, z_date_creation_max)
VALUES (table_rec.table_schema, table_rec.table_name, max_date);
END LOOP;
END $$;

Looping through all tables and columns in postgresql and applying a select query to each one

I came up with this idea to loop over all tables and column names to count the number of null values in postgres. However, I failed to do that by Error 42702: column reference "table_name" is ambiguous.
create or replace function count_nulls()
returns table ("table_name" text, "column_name" text, "nulls" int)
language plpgsql as
$func$
declare _record information_schema.columns %ROWTYPE;
begin
for _record in
SELECT "table_name", "column_name"
FROM information_schema.columns
where "table_schema" = 'public'
loop
select quote_literal(_record.table_name) as "table",
quote_literal(_record.column_name) as "column",
count(*) as "nulls"
from quote_literal(_record.table_name)
where quote_literal(_record.column_name) is null
group by "table", "column";
end loop;
return;
end;
$func$;
select * from count_nulls();
Any pointers to documentation or keywords for further search is appreciated.
Change this block :
for _record in
SELECT "table_name", "column_name"
FROM information_schema.columns
where "table_schema" = 'public'
loop
to this:
for _record in
SELECT t1."table_name", t1."column_name"
FROM information_schema.columns t1
where t1."table_schema" = 'public'
loop

Dynamic query splicing error,a fuction use falie

I want to use the cursor to get the count(data) in all tables under the schema I need.
But I am unfamiliar with SQL, and I still cannot pass the following code:
CREATE OR REPLACE FUNCTION check_tool.get_nae(v_dbName character varying)
RETURNS numeric
LANGUAGE edbspl
SECURITY DEFINER
AS $function$
numInsert numeric;
numCal numeric;
v_result numeric;
query TEXT DEFAULT '';
cursor c_pj is
select t.table_schema::text as tableSchema,
t.table_name::text as tableName
from information_schema.tables t
where t.table_catalog = v_dbName
and t.table_type = 'BASE TABLE'
and t.table_schema in (select schema_name from check_tool.img_schema where dbName = v_dbName);
BEGIN
v_result := -1;
numInsert := 0;
for r_pj in c_pj loop
query := 'select count(*) from '||tableSchema||'.'||tableName||';'; -- select count(*) from "item"."project";
execute query into numCal;
insert into check_tool.img_result(schema_name,table_name,num) values (r_pj.tableSchema,r_pj.tableName,numCal);
numInsert := numInsert + 1;
if numInsert > 1000 then
numInsert := 0;
commit;
end if;
end loop;
commit;
v_result := 0;
RETURN v_result;
EXCEPTION
WHEN others THEN
RETURN v_result;
END get_nae$function$
;
/
I also tried concat() and quote_ident(), but the result is not ideal.
Dynamic SQL should be constructed using format() to better handle identifiers. In Postgres you can't commit inside a function, only in a procedure. Committing in a loop rarely improves the performance to begin with, so I would just skip that. I also wouldn't hide the real error (by just returning -1 or 0) but simply let any exception reach the caller of the procedure. And language edbspl is nothing I know, but in PL/pgSQL I would write it like this:
CREATE OR REPLACE PROCEDURE check_tool.get_nae(v_dbName character varying)
LANGUAGE plpgsql
AS
$body$
declare --<< required in PL/pgSQL to declare variables
numcal numeric;
query TEXT DEFAULT '';
l_rec record;
BEGIN
for l_rec in select t.table_schema::text as tableschema, t.table_name::text as tablename
from information_schema.tables t
where t.table_catalog = v_dbName
and t.table_type = 'BASE TABLE'
and t.table_schema in (select schema_name from check_tool.img_schema where dbName = v_dbName)
loop
query := format('select count(*) from %I.%I', l_rec.tableschema, l_rec.tablename);
execute query into numcal;
insert into check_tool.img_result(schema_name,table_name,num) values (l_rec.tableschema, l_rec.tablename, numcal);
end loop;
commit;
END;
$body$
;
Note that the condition t.table_catalog = v_dbName is actually useless, because you can't query tables that are not in the current database anyway.
Note that you don't really need a stored procedure to do this. You can use query_to_xml() to do this in a single SQL query by adjusting this answer
insert into check_tool.img_result(schema_name,table_name,num)
select table_schema,
table_name,
(xpath('/row/cnt/text()', xml_count))[1]::text::int as row_count
from (
select table_name, table_schema,
query_to_xml(format('select count(*) as cnt from %I.%I', table_schema, table_name), false, true, '') as xml_count
from information_schema.tables
where t.table_type = 'BASE TABLE'
and t.table_schema in (select schema_name from check_tool.img_schema)
) t;

Loop through all user tables and insert row in each

for some reason I just can not figure this out. I have a seperate schema in PostgreSQL for notification related tables for each user connected to the server. My plan is to have each user create a TEMP table to receive extra notification info from since Xojo doesn't support PostgreSQL payloads.
I feel like I'm starting to get close so I'll just post my code that is in my trigger function.
DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'information_schema'
LOOP
INSERT INTO my_table.table_name (effected_row_id)
VALUES (NEW.effected_row_id);
END LOOP;
END;
Tell me if I'm wrong, but I believe my main problem is figuring out how to use the table name returned from the SELECT statement in the INSERT statement.
EDIT:
This is my current trigger function
-- Function: notification.my_insert_trigger_function()
-- DROP FUNCTION notification.my_insert_trigger_function();
CREATE OR REPLACE FUNCTION notification.my_insert_trigger_function()
RETURNS trigger AS
$BODY$DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'notification' AND table_name <> 'notification_global' AND table_name <> 'switcher'
LOOP
EXECUTE(FORMAT($f$
INSERT INTO %s (effected_row_username)
VALUES (%s);
$f$, 'notification.' || my_table.table_name, NEW.effected_row_username));
END LOOP;
RETURN new;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION notification.my_insert_trigger_function()
OWNER TO serveradmin;
You need to use dynamic commands in your trigger function.
The funcion format() is often very helpful.
DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'information_schema'
LOOP
EXECUTE(FORMAT($f$
INSERT INTO %s (effected_row_id)
VALUES (%s);
$f$, my_table.tablename, NEW.effected_row_id));
END LOOP;
END;

Syntax error while creating function in postgresql

I got a syntax error while creating a procedure in postgresql.Here I attached my code.I got a error syntax error near "Continue"
create function patient_form_values() RETURNS void AS
$$ begin
DECLARE columnName varchar(200) ;
DECLARE done boolean default true;
DECLARE CONTINUE handler for not found set done = false;
DECLARE cur1 cursor for select distinct COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'currentdiagnosis';
open cur1;
read_loop : loop
fetch from cur1 into columnName;
if done then leave read_loop;
end if;
set #insertValues := concat('INSERT INTO patient_form_temp(patient_id, form_template_id, creator_id, created_date)
SELECT c.patient_id as patient_id, 41 AS form_template_id, 2 AS creator_id, c.created_date AS created_date
FROM currentdiagnosis c
WHERE c.', columnName,' IS NOT NULL GROUP BY c.patient_id, c.created_date');
select #insertValues;
prepare stmt from #insertValues;
execute stmt;
end loop;
close cur1;
end ;
$$ LANGUAGE plpgsql
You are trying to use a MySQL (or other DB?) function in PostgreSQL. There is no concept of CONTINUE HANDLER in PostgreSQL, so you have to convert the function into PostgreSQL format.
drop FUNCTION if exists migratePartnerAdvertiser();
CREATE OR REPLACE FUNCTION migratePartnerAdvertiser() RETURNS int4 AS '
DECLARE r RECORD;
BEGIN
FOR r IN select distinct COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = ''currentdiagnosis'' and table_schema=''public'' LOOP
EXECUTE concat(''INSERT INTO patient_form_temp(patient_id, form_template_id, creator_id, created_date) SELECT c.patient_id as patient_id, 41 AS form_template_id, 2 AS creator_id, c.reg_date AS created_date FROM currentdiagnosis c WHERE c.'' , r.column_name , '' IS NOT NULL GROUP BY c.patient_id, c.reg_date'');
END LOOP;
return 1;
END;
' LANGUAGE plpgsql;