How to convert a PL/PgSQL procedure into a dynamic one? - postgresql

I am trying to write a plpgsql procedure to perform spatial tiling of a postGIS table. I can perform the operation successfully using the following procedure in which the table names are hardcoded. The procedure loops through the tiles in tile_table and for each tile clips the area_table and inserts it into split_table.
CREATE OR REPLACE PROCEDURE splitbytile()
AS $$
DECLARE
tile RECORD;
BEGIN
FOR tile IN
SELECT tid, geom FROM test_tiles ORDER BY tid
LOOP
INSERT INTO split_table (id, areaname, ttid, geom)
SELECT id, areaname, tile.tid,
CASE WHEN st_within(base.geom, tile.geom) THEN st_multi(base.geom)
ELSE st_multi(st_intersection(base.geom, tile.geom)) END as geom
FROM area_table as base
WHERE st_intersects(base.geom, tile.geom);
COMMIT;
END LOOP;
END;
$$ LANGUAGE 'plpgsql';
Having tested this successfully, now I need to convert it to a dynamic procedure where I can provide the table names as parameters. I tried the following partial conversion, using format() for inside of loop:
CREATE OR REPLACE PROCEDURE splitbytile(in_table text, grid_table text, split_table text)
AS $$
DECLARE
tile RECORD;
BEGIN
FOR tile IN
EXECUTE format('SELECT tid, geom FROM %I ORDER BY tid', grid_table)
LOOP
EXECUTE
FORMAT(
'INSERT INTO %1$I (id, areaname, ttid, geom)
SELECT id, areaname, tile.tid,
CASE WHEN st_within(base.geom, tile.geom) THEN st_multi(base.geom)
ELSE st_multi(st_intersection(base.geom, tile.geom)) END as geom
FROM %2$I as base
WHERE st_intersects(base.geom, tile.geom)', split_table, in_table
);
COMMIT;
END LOOP;
END;
$$ LANGUAGE 'plpgsql';
But it throws an error
missing FROM-clause entry for table "tile"
So, how can I convert the procedure to a dynamic one? More specifically, how can I use the record data type (tile) returned by the for loop inside the loop? Note that it works when format is not used.

You can use EXECUTE ... USING to supply parameters to a dynamic query:
EXECUTE
format(
'SELECT r FROM %I WHERE c = $1.val',
table_name
)
INTO result_var
USING record_var;
The first argument to USING will be used for $1, the second for $2 and so on.
See the documentation for details.

Personally I use somehow different way to create dynamic functions. By concatination and execute function. You can also do like this.
CREATE OR REPLACE FUNCTION splitbytile()
RETURNS void AS $$
declare
result1 text;
table_name text := 'test_tiles';
msi text := '+7 9912 231';
msi text := 'Hello world';
code text := 'code_name';
_operator_id integer := 2;
begin
query1 := 'SELECT msisdn from ' || table_name || ' where msisdn = ''' || msi::text ||''';';
query2 := 'INSERT INTO ' || table_name || '(msisdn,usage,body,pr_code,status,sent_date,code_type,operator_id)
VALUES( ''' || msi::text || ''',' || true || ',''' || _body::text || ''',''' || code::text || ''',' || false || ',''' || time_now || ''',' || kod_type || ',' || _operator_id ||');';
execute query1 into result1;
execute query2;
END;
$function$
You just make your query as text then anywhere you want you can execute it. Maybe by checking result1 value inside If statement or smth like that.

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.

Postgres - Use table name (passed in parameter) in function body

I am a newbie wrt functions and I am struggling with using the name of a table in the function body. I get an error "SQL Error [42703]: ERROR: column "tname" does not exist" when I call the function using
select "JsonToView"('data_import.import_360xero_report');
My code is below
create or replace
function data_import."JsonToView"(tname text) returns numeric
language plpgsql
as $function$
begin
do
$$
declare
l_keys text;
begin
drop view if exists v_json_view cascade;
select
string_agg(distinct format('import_data ->> %L as %I', jkey, jkey), ', ')
into
l_keys
from
import_360xero_report,
json_object_keys(import_data) as t(jkey);
execute 'create view v_json_view as select ' || l_keys || ' from ' || tname;
end;
$$;
return 0;
end $function$ ;
I have modified the code and the second create view query works with the table name but the first one does not.
Below if my modified code
create or replace
function data_import."JsonToView"(tname text) returns numeric
language plpgsql
as $function$
declare
l_keys text;
begin
drop view if exists v_json_view cascade;
execute $a$select
string_agg(distinct format('import_data ->> %L as %I', jkey, jkey), ', ')
into
l_keys
from $a$ ||
tname || $b$,
json_object_keys(import_data) as t(jkey)$b$;
execute 'create view v_json_view as select ' || l_keys || ' from ' || tname;
return 0;
end $function$ ;
The error I am getting is
SQL Error [0A000]: ERROR: EXECUTE of SELECT ... INTO is not implemented
Hint: You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.
Where: PL/pgSQL function "JsonToView"(text) line 10 at EXECUTE
The problem is the superfluous nested DO statement.
The variable tname exists only in the scope of the function, not in the nested DO statement. DO is an SQL statement, not a PL/pgSQL statement, and there are no variables in SQL. Also, DO does not allow parameters.
Get rid of the DO and you will be fine.

How to call a stored procedure inside another stored procedure in postgreSQL

I have a two tables, table_version_1 and table_version_2, I am trying to generate a new table based on these two tables. For which I have to write 4 stored procedures,
procedure1, procedure2, procedure3, procedure4
I am triggering procedure1 from my application
select * from procedure1
I am calling procedure2 in procedure1
PERFORM procedure2
and I am calling procedure3 in procedure2
PERFORM procedure3
and calling procedure4 in procedure3
PERFORM procedure3
My stored procedure looks something like below.
CREATE OR REPLACE FUNCTION procedure1(tb_name, compare_tb_name, file_version, compare_file_version)
RETURNS text
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
createquery text;
fpo_data jsonb;
inddata jsonb;
f_primary_key text;
rowscount INTEGER;
datacount INTEGER;
fcdata1 text;
fid INTEGER;
BEGIN
createquery := 'CREATE TABLE IF NOT EXISTS ' || tb_name || '_' || file_version || '_' || compare_file_version || '_pk(
id serial PRIMARY KEY,
fc_pkey1 VARCHAR (250) NULL,
fc_pkey2 VARCHAR (250) NULL,
fc_pkey3 VARCHAR (250) NULL,
fpo_data TEXT NULL,
fid INTEGER NULL
)';
EXECUTE createquery;
EXECUTE 'SELECT count(*) FROM ' || tb_name || '_' || file_version || '_' || compare_file_version || '_pk' INTO rowscount;
EXECUTE 'SELECT count(*) FROM ' || tb_name INTO datacount;
IF(rowscount <> datacount) THEN
EXECUTE 'SELECT json_agg((fpdata, foseqid))::jsonb
FROM (SELECT fo_data AS fpdata, fo_seq_id as foseqid
FROM '||tb_name||'
LIMIT 1000
) t' INTO fpo_data;
FOR inddata IN SELECT * FROM jsonb_array_elements(fpo_data) LOOP
EXECUTE 'INSERT INTO ' || tb_name || '_' || file_version || '_' || compare_file_version || '_pk(fc_pkey1, fpo_data, fid) VALUES ($1, $2, $3)' USING f_primary_key, inddata, fid;
END LOOP;
ELSE
PERFORM procedure2(tb_name, compare_tb_name, file_version, compare_file_version);
END IF;
return 'Primary Key Generation completed';
END;
$BODY$;
I have not written the complete query. I just written important steps of my query.
My issue is that, in the above query I have create query, insert query and select query and at the end of the stored procedure I have written return statement. If I remove the return all my steps create query, insert query and select query are failing and if I write return then it is not going to procedure2. What is the correct process to run this procedure?

Apply function to temp table inside another function

I am new to creating functions in postgresql. The version that I'm using is rather old. It's 8.2.15 (not my choice, but my org's). The following example is trying to apply one function to a temp table in another function.
-- First function
create or replace function inner_func(_tbl anyelement)
RETURNS void AS
$$
BEGIN
EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN d_amount INTEGER';
EXECUTE 'UPDATE ' || _tbl || ' SET d_amount = 2* amount';
RETURN;
END;
$$
LANGUAGE plpgsql volatile;
-- Second function
CREATE OR REPLACE FUNCTION outer_func()
RETURNS void AS
$$
BEGIN
DROP TABLE IF EXISTS my_temp;
CREATE TEMP TABLE my_temp
(id serial primary key,
amount integer
);
INSERT into my_temp (amount) values (10),(20);
-- now apply the inner_func right here
EXECUTE 'SELECT inner_func(' || quote_ident('my_temp') || ')';
RETURN;
END;
LANGUAGE plpgsql volatile;
When I run
SELECT outer_func();
It spits out an ERROR:
column "my_temp" does not exist
But the inner_func works if I use it on its own like the following:
create temp table my_temp2
(id serial primary key,
amount integer
);
INSERT INTO my_temp2 (amount) values (10),(20);
SELECT inner_func(quote_ident('my_temp2'));
SELECT * from my_temp2;
id amount d_amount
1 10 20
2 20 40
How can I make this inner_func work inside outer_func? Any idea?
It looks like the problem is here:
EXECUTE 'SELECT inner_func(' || quote_ident('my_temp') || ')';
=>
EXECUTE 'SELECT inner_func(quote_ident(' || quote_literal('my_temp') || '));';
DBFiddle Demo

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)