'curson does not exist' asnswer from PostgreSQL - postgresql

please your help...
object: a stored procedure whose ultimate purpose is writing flags in an specific column of a table. The flag location is decided based on the comparison of CURRENT values (i.e. in a 'current' row) and the values in the PRECEDING row.
General solution: using two cursors to scroll though the table.
It did work correctly using the standard structure, this is, cursors declared as :
cursor_evento_actual cursor for
select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update;
after that, I wanted the cursor declaration to accept different table names and colum names (taken from the procedure parameters), using EXECUTE. In order to do that I changed the way the cursor was declared: in the DECLARE section it went ...
cursor_evento_actual refcursor;
and in the BEGIN section it went...
open cursor_evento_actual for
select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update;
move cursor_evento_actual;
fetch cursor_evento_actual into vector_evento_actual;
Just the change described so far produced the error 'cursor does no exists'.
From what I see, it is not sintaxis... but I cannot find the problem
below, the procedure that works well and following, the procedure that doesn´t
the one that works, i.e., the one that correctly writes the flag 'repite' in the column 'aux_texto_1' of the selected table.
<<bloque_1>>
declare
vector_evento_actual record;
vector_evento_precedente record;
cursor_evento_actual cursor for
select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update;
cursor_evento_precedente cursor for
select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc;
begin
update public.camion_estado
set aux_texto_1 = DEFAULT;
open cursor_evento_actual;
open cursor_evento_precedente;
move cursor_evento_actual;
fetch cursor_evento_actual into vector_evento_actual;
fetch cursor_evento_precedente into vector_evento_precedente;
while (found) loop
if vector_evento_actual.calc_dt2 = vector_evento_precedente.calc_dt2 and vector_evento_actual.original_cam_id = vector_evento_precedente.original_cam_id then
update public.camion_estado
set aux_texto_1 = 'repite'
where current of cursor_evento_actual;
end if;
fetch cursor_evento_actual into vector_evento_actual;
fetch cursor_evento_precedente into vector_evento_precedente;
end loop;
close cursor_evento_actual;
close cursor_evento_precedente;
end bloque_1 $$;
...the key part of the code is.
update public.camion_estado set aux_texto_1 = 'repite'
where current of cursor_evento_actual;
after the changes in the way the cursos are declared, the coding looks like this:
(the RAISE NOTICE statements are there just for debugging)
declare
vector_evento_actual record;
vector_evento_precedente record;
cursor_evento_actual refcursor;
cursor_evento_precedente refcursor;
begin
update public.camion_estado
set aux_texto_1 = DEFAULT; -- asegurar que no está escrita de antes
open cursor_evento_actual for
select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update;
move cursor_evento_actual;
fetch cursor_evento_actual into vector_evento_actual;
open cursor_evento_precedente for
select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc;
fetch cursor_evento_precedente into vector_evento_precedente;
raise notice 'inicio Bloque 1b - tabla actual: %, columna; % ', tabla_objeto, columna_objeto;
while (found) loop
if vector_evento_actual.calc_dt2 = vector_evento_precedente.calc_dt2 and vector_evento_actual.original_cam_id = vector_evento_precedente.original_cam_id then
raise notice 'este es repetido %', vector_evento_actual.original_cam_id;
execute 'update public.'||tabla_objeto||'
set aux_texto_1 = '||quote_literal('repite')||'
where current of cursor_evento_actual';
/*
update public.camion_estado
set aux_texto_1 = 'repite'
where current of cursor_evento_actual;
*/
end if;
fetch cursor_evento_actual into vector_evento_actual;
fetch cursor_evento_precedente into vector_evento_precedente;
end loop;
close cursor_evento_actual;
close cursor_evento_precedente;
This the code reporting 'Cursor does not exist'

If you use UPDATE ... WHERE CURRENT OF <cursor_name>, you are referring the actual name of the cursor (portal), not to the name of the PL/pgSQL variable. Since you didn't specify that name, it was automatically generated by PostgreSQL. Name the cursor the same as the variable with
cursor_evento_actual refcursor := 'cursor_evento_actual';

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 $$;

What is the equivalent of PL/SQL %ISOPEN in PL/pgSQL?

I'm migrating an Oracle PLSQL SP to be compatible with Postgres plpgsql (version PostgreSQL 13.6 on x86_64-pc-linux-gnu, compiled by x86_64-pc-linux-gnu-gcc (GCC) 7.4.0, 64-bit).
The exception block of the PLSQL SP has the below code:
exception
when others then
if CURR1%isopen then
close SPV_RECON_INFO;
end if;
open CURR1 for execute select sysdate from dual;
END;
How can %isopen be implemented in Postgres?
That is simple. You have to assign a name to the cursor variable, then you can search for that cursor in pg_cursors. If there is a row with that name, the cursor is open.
Here is a self-contained example:
DO
$$DECLARE
c refcursor;
BEGIN
c := 'mycursor';
/* cursor is not open, EXIST returns FALSE */
RAISE NOTICE '%', EXISTS (SELECT 1 FROM pg_cursors WHERE name = 'mycursor');
OPEN c FOR SELECT * FROM pg_class;
/* cursor is open, EXIST returns TRUE */
RAISE NOTICE '%', EXISTS (SELECT 1 FROM pg_cursors WHERE name = 'mycursor');
END;$$;
NOTICE: f
NOTICE: t
If you do not assign a name, PostgreSQL will generate a name (but you don't know what the name is).
PostgreSQL cursors do not support %ISOPEN or %NOTFOUND. To address this problem %ISOPEN can be replaced by a boolean variable declared internally in the procedure and is updated manually when the cursor is opened or closed.
http://wiki.openbravo.com/wiki/PL-SQL_code_rules_to_write_Oracle_and_Postgresql_code
I often found it convenient in cases like this to create a function that emulates Oracle. In his case something like:
create or replace function cursor_isopen(cur text)
returns boolean
language sql
as $$
select exists (select null
from pg_cursors
where name = cur
) ;
$$;
Then your code becomes something like:
exception
when others then
if cursor_isopen(cur_name::text) then
close SPV_RECON_INFO;
end if;
Of course you need to have preset the cursor name as Laurenz Albe has pointed out. Sample test case.
do $$
declare
cur1 cursor for select table_name from information_schema.tables;
cur2 cursor for select table_name from information_schema.tables;
table_name text;
begin
cur1 := 'closed-cursor';
cur2 := 'open-cursor';
open cur2;
if cursor_isopen(cur1::text)
then
fetch cur1 into table_name;
raise notice 'First table name: %', table_name;
close cur1;
else raise notice 'cursor_isopen(''%'') returned %', cur1::text, cursor_isopen(cur1::text);
end if;
if cursor_isopen(cur2::text)
then
fetch cur2 into table_name;
raise notice 'First table name: %', table_name;
close cur2;
else raise notice 'cursor_isopen(''%'') returned %', cur1::text, cursor_isopen(cur1::text);
end if;
end;
$$;
results:
cursor_isopen('closed-cursor') returned f
cursor_isopen('open-cursor') returned t. First table name: task_states

Postgres SQL | IF ELSE | HOW TO

I am using psql (PostgreSQL) 11.2 (Debian 11.2-1.pgdg90+1).
I am trying to write a logic in .PSQL file that needs to import some data into a table if this table is empty, else do something else.
I am struggling to find the correct syntax to make it work.
Would appreciate some help around this.
DO $$ BEGIN
SELECT count(*) from (SELECT 1 table_x LIMIT 1) as isTableEmpty
IF isTableEmpty > 0
THEN
INSERT INTO table_x
SELECT * FROM table_b;
ELSE
INSERT INTO table_y
SELECT * FROM table_b;
END IF;
END $$;
thanks!
Read plpgsql structure. Then you would know you need a DECLARE section to declare isTableEmpty and from here Select into that you need to select into the isTableEmpty variable. So:
...
DECLARE
isTableEmpty integer;
BEGIN
SELECT count(*) into isTableEmpty from (SELECT 1 table_x LIMIT 1);
...
Though I'm not sure what you are trying to accomplish with?:
SELECT count(*) from (SELECT 1 table_x LIMIT 1) as isTableEmpty
As that is always going to return 1.
You are using count just to determine that a row exists or not in the table. To do so you need to create a variable in the DO block, select into that variable, and reference that variable. This is all unnecessary; you can just use exists(...) instead of count(*) .... See demo;
do $$
begin
if not exists (select null from table_x) then
insert into table_x (...)
values (...);
else
insert into table_y (...)
values (...);
end if;
end ;
$$;

Postgres query with variable in loop and condition on variable

I have a query which updates the records based on variables old_id and new_id. But condition is I need to fetch the variables dynamically. Here is simple query which I am using.
do
$$
declare
old_id bigint = 1561049391647687270;
declare new_id bigint = 2068236279446765699;
begin
update songs set poet_id = new_id where poet_id = old_id;
update poets set active = true where id = new_id;
update poets set deleted = true where id = old_id;
end
$$;
I need to assign the old_id and new_id dynamically
do
$$
declare
su record;
pc record;
old_id bigint;
new_id bigint;
begin
for pc in select name, count(name)
from poets
where deleted = false
group by name
having count(name) > 1
order by name
loop
for su in select * from poets where name ilike pc.name
loop
-- old_id could be null where I have 2 continue the flow without update
for old_id in (select id from su where su.link is null)
loop
raise notice 'old: %', old_id;
end loop;
-- new_id could be more than 2 skip this condition as well
for new_id in (select id from su where su.link is not null)
loop
raise notice 'new: %', new_id;
end loop;
end loop;
-- run the statement_1 example if new_id and old_id is not null
end loop;
end
$$;
The expected problem statement (to assign variable and use it in further execution) is with in comment.
(a) In your first "simple query", the update of the table poets could be automatically executed by a trigger function defined on the table songs :
CREATE OR REPLACE FUNCTION songs_update_id ()
RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
UPDATE poets SET active = true WHERE id = NEW.poet_id ;
UPDATE poets SET deleted = true WHERE id = OLD.poet_id ; -- SET active = false to be added ?
END ;
$$ ;
CREATE OR REPLACE TRIGGER songs_update_id AFTER UPDATE OF id ON songs
FOR EACH ROW EXECUTE songs_update_id () ;
Your first query can then be reduced as :
do
$$
declare
old_id bigint = 1561049391647687270;
declare new_id bigint = 2068236279446765699;
begin
update songs set poet_id = new_id where poet_id = old_id;
end
$$;
(b) The tables update could be performed with a sql query instead of a plpgsql loop and with better performances :
do
$$
BEGIN
UPDATE songs
SET poet_id = list.new_id[1]
FROM
( SELECT b.name
, array_agg(b.id) FILTER (WHERE b.link IS NULL) AS old_id
, array_agg(b.id) FILTER (WHERE b.link IS NOT NULL) AS new_id
FROM
( SELECT name
FROM poets
WHERE deleted = false
GROUP BY name
HAVING COUNT(*) > 1
-- ORDER BY name -- this ORDER BY sounds like useless and resource-intensive
) AS a
INNER JOIN poets AS b
ON b.name ilike a.name
GROUP BY b.name
HAVING array_length(old_id, 1) = 1
AND array_length(new_id, 1) = 1
) AS list
WHERE poet_id = list.old_id[1] ;
END ;
$$;
This solution is not tested yet and could have to be adjusted in order to work correctly. Please provide the tables definition of songs and poets and a sample of data in dbfiddle so that I can test and adjust the proposed solution.

Is it worth Parallel/Concurrent INSERT INTO... (SELECT...) to the same Table in Postgres?

I was attempting an INSERT INTO.... ( SELECT... ) (inserting a batch of rows from SELECT... subquery), onto the same table in my database. For the most part it was working, however, I did see a "Deadlock" exception logged every now and then. Does it make sense to do this or is there a way to avoid a deadlock scenario? On a high-level, my queries both resemble this structure:
CREATE OR REPLACE PROCEDURE myConcurrentProc() LANGUAGE plpgsql
AS $procedure$
DECLARE
BEGIN
LOOP
EXIT WHEN row_count = 0
WITH cte AS (SELECT *
FROM TableA tbla
WHERE EXISTS (SELECT 1 FROM TableB tblb WHERE tblb.id = tbla.id)
INSERT INTO concurrent_table (SELECT id FROM cte);
COMMIT;
UPDATE log_tbl
SET status = 'FINISHED',
WHERE job_name = 'tblA_and_B_job';
END LOOP;
END
$procedure$;
And the other script that runs in parallel and INSERTS... also to the same table is also basically:
CREATE OR REPLACE PROCEDURE myConcurrentProc() LANGUAGE plpgsql
AS $procedure$
DECLARE
BEGIN
LOOP
EXIT WHEN row_count = 0
WITH cte AS (SELECT *
FROM TableC c
WHERE EXISTS (SELECT 1 FROM TableD d WHERE d.id = tblc.id)
INSERT INTO concurrent_table (SELECT id FROM cte);
COMMIT;
UPDATE log_tbl
SET status = 'FINISHED',
WHERE job_name = 'tbl_C_and_D_job';
END LOOP;
END
$procedure$;
So you can see I'm querying two different tables in each script, however inserting into the same some_table. I also have the UPDATE... statement that writes to a log table so I suppose that could also cause issues. Is there any way to use BEGIN... END here and COMMIT to avoid any deadlock/concurrency issues or should I just create a 2nd table to hold the "tbl_C_and_D_job" data?