Using parameters passed to stored procedure in query - postgresql

I have a sql query where I want to extract records older than 'X' number of days, here for eg its 7 days:
SELECT * FROM BOOKMARK.MONITORING_TABLE WHERE inserteddatetime < (now() - '7 day'::interval);
I have to execute this query through a stored procedure passing in the configurable 'X' no of days as arguments.
The procedure is as below:
CREATE OR REPLACE FUNCTION DELETE_REDUNDANT_RECORDS_STORED_PROCEDURE(days int)
RETURNS void AS
$func$
DECLARE
rec_old RECORD;
cursor_data CURSOR FOR
SELECT * FROM BOOKMARK.MONITORING_TABLE WHERE inserteddatetime < now() - '$1 day'::interval;
BEGIN
OPEN cursor_data;
// business logic for the procedure
CLOSE cursor_data;
END;
$func$
LANGUAGE plpgsql;
This doesn't work as I am not able to use the placeholder for days in my query. How do we use the arguments passed to my query in this case.

To create an interval based on an integer, make_interval() is much easier to use than casting to an interval type.
Additional I wouldn't use a cursor, but a FOR loop based on a SELECT statement (maybe using make_interval(days => $1) works in the cursor declaration as well)
CREATE OR REPLACE FUNCTION DELETE_REDUNDANT_RECORDS_STORED_PROCEDURE(days int)
RETURNS void AS
$func$
DECLARE
rec_old record;
BEGIN
for rec_old in SELECT *
FROM BOOKMARK.MONITORING_TABLE
WHERE inserteddatetime < now() - make_interval(days => $1)
loop
raise notice 'records %', rec_old;
end loop;
END;
$func$
LANGUAGE plpgsql;

Related

Pass date intervals as function parameters

I have a database function as below:
drop function test(month_interval text)
create or replace function test (month_interval text) returns date as
$$
select ('2020-07-01'::date - interval month_interval)::date;
$$ language sql;
select * from test('2 months')
I have a scenario where I want to dynamically compute month intervals and want to have one database query that can be used by passing month intervals as function parameters. However when i do this it gives me the following error :
ERROR: syntax error at or near "month_interval"
You could cast the text to an interval:
create or replace function test (month_interval text) returns date as
$$
select ('2020-07-01'::date - month_interval::interval)::date;
$$ language sql;
select test('2 months');
But why not pass an interval directly?
create or replace function test (month_interval interval) returns date as
$$
select ('2020-07-01'::date - month_interval)::date;
$$ language sql;
select test(interval '2 months');
Alternatively you can pass the number of months, then use make_interval:
create or replace function test (num_months int) returns date as
$$
select ('2020-07-01'::date - make_interval(months => num_months))::date;
$$ language sql;
select test(2);

How to use argument for table name in dynamic SQL

I am writing a Postgres function to get the number of new records in a table. Here table name is a variable.
create or replace function dmt_mas_updates(
tb_name text,
days integer)
returns integer as
$$
declare
ct integer;
begin
execute 'select count(*) from $1 where etl_create_dtm > now() - $2 * interval ''1 days'' '
using tb_name, days into ct;
return ct;
end;
$$ LANGUAGE 'plpgsql'
When I call the function with select * from dmt_mas_updates('dmt_mas_equip_store_dim',2);, I got syntax error at $1.
If I run the query directly select count(*) from dmt_mas_equip_store_dim where etl_create_dtm >= interval '3 days', it works correctly.
Why am I getting this error? What did I do wrong?
Per the documentation:
Note that parameter symbols can only be used for data values — if you want to use dynamically determined table or column names, you must insert them into the command string textually.
Use the format() function:
create or replace function dmt_mas_updates(
tb_name text,
days integer)
returns integer as
$$
declare
ct integer;
begin
execute format(
'select count(*) from %I where etl_create_dtm > now() - $1 * interval ''1 days'' ',
tb_name)
using days into ct;
return ct;
end;
$$ LANGUAGE 'plpgsql';

How to return multiple select queries from PostgreSQL's functions like we do in MS SQL Procedures

I am currently working on PostgreSQL's functions. Like we return multiple result set in MS SQL procedures by writing multiple select queries, same thing I need to integrate in postgreSQL. I did study and came through following solution :
CREATE OR REPLACE FUNCTION public.summary_test( IN shoppedfromdate date, IN shoppedtodate date, IN enrollmentfromdate date, IN enrollmenttodate date )
RETURNS
SETOF refcursor
AS
$my_body$
DECLARE Q1 refcursor;
DECLARE Q2 refcursor;
BEGIN
OPEN Q1 for
Select t_cs_dummy.text_data,t_cs_dummy.bigint_data from public.t_cs_dummy ;
RETURN NEXT Q1;
OPEN Q2 FOR
Select date_data,datetime_data from public.t_cs_dummy ;
RETURN NEXT Q2;
END;
$my_body$
LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
COST 10;
Please let me know, Is this is proper way ? if 'No' then is there any alternative way to do so??
CREATE OR REPLACE FUNCTION multiple_recordsets () RETURNS TABLE (id bigint)
AS $$
BEGIN
RETURN QUERY SELECT generate_series(1,10)::bigint v;
RETURN QUERY SELECT generate_series(20,30)::bigint v;
RETURN QUERY SELECT generate_series(100,110)::bigint v;
END;
$$
language plpgsql;
Then call it with:
SELECT * FROM multiple_recordsets();

Merge multiple result tables and perform final query on result

I have a function returning table, which accumulates output of multiple calls to another function returning table. I would like to perform final query on built table before returning result. Currently I implemented this as two functions, one accumulating and one performing final query, which is ugly:
CREATE OR REPLACE FUNCTION func_accu(LOCATION_ID INTEGER, SCHEMA_CUSTOMER TEXT)
RETURNS TABLE("networkid" integer, "count" bigint) AS $$
DECLARE
GATEWAY_ID integer;
BEGIN
FOR GATEWAY_ID IN
execute format(
'SELECT id FROM %1$I.gateway WHERE location_id=%2$L'
, SCHEMA_CUSTOMER, LOCATION_ID)
LOOP
RETURN QUERY execute format(
'SELECT * FROM get_available_networks_gw(%1$L, %2$L)'
, GATEWAY_ID, SCHEMA_CUSTOMER);
END LOOP;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION func_query(LOCATION_ID INTEGER, SCHEMA_CUSTOMER TEXT)
RETURNS TABLE("networkid" integer, "count" bigint) AS $$
DECLARE
BEGIN
RETURN QUERY execute format('
SELECT networkid, max(count) FROM func_accu(%2$L, %1$L) GROUP BY networkid;'
, SCHEMA_CUSTOMER, LOCATION_ID);
END;
$$ LANGUAGE plpgsql;
How can this be done in single function, elegantly?
Both functions simplified and merged, also supplying value parameters in the USING clause:
CREATE OR REPLACE FUNCTION pg_temp.func_accu(_location_id integer, schema_customer text)
RETURNS TABLE(networkid integer, count bigint) AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT f.networkid, max(f.ct)
FROM %I.gateway g
, get_available_networks_gw(g.id, $1) f(networkid, ct)
WHERE g.location_id = $2
GROUP BY 1'
, _schema_customer)
USING _schema_customer, _location_id;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM func_accu(123, 'my_schema');
Related:
Dynamically access column value in record
I am using alias names for the columns returned by the function (f(networkid, ct)) to be sure because you did not disclose the return type of get_available_networks_gw(). You can use the column names of the return type directly.
The comma (,) in the FROM clause is short syntax for CROSS JOIN LATERAL .... Requires Postgres 9.3 or later.
What is the difference between LATERAL and a subquery in PostgreSQL?
Or you could run this query instead of the function:
SELECT f.networkid, max(f.ct)
FROM myschema.gateway g, get_available_networks_gw(g.id, 'my_schema') f(networkid, ct)
WHERE g.location_id = $2
GROUP BY 1;

Postgres pl/pgsql ERROR: column "column_name" does not exist

i have a storerd procedure like below,
CREATE FUNCTION select_transactions3(text, text, int)
RETURNS SETOF transactions AS
$body$
DECLARE
rec transactions%ROWTYPE;
BEGIN
FOR rec IN (SELECT invoice_no, trans_date FROM transactions WHERE $1 = $2 limit $3 )
LOOP
RETURN NEXT rec;
END LOOP;
END;
$body$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
when i execute query like this :
select * from select_transactions3("invoice_no", '1103300105472',10);
or
select * from select_transactions3(invoice_no, '1103300105472',10);
it getting error like this :
ERROR: column "invoice_no" does not exist
but when i try execute with one colon like this :
select * from select_transactions3('invoice_no', '1103300105472',10);
the result is no row.
how i can get the data like this :
invoice_no | trans_date
---------------+-------------------------
1103300105472 | 2011-03-30 12:25:35.694
thanks .
UPDATE : If we want a certain column of table that we want to show
CREATE FUNCTION select_to_transactions14(_col character varying, _val character varying, _limit int)
RETURNS SETOF RECORD AS
$$
DECLARE
rec record;
BEGIN
FOR rec IN EXECUTE 'SELECT invoice_no, amount FROM transactions
WHERE ' || _col || ' = $1 LIMIT $2' USING _val, _limit LOOP
RETURN NEXT rec;
END LOOP;
END;
$$ LANGUAGE plpgsql;
to get the result :
SELECT * FROM select_to_transactions14( 'invoice_no', '1103300105472',1)
as ("invoice_no" varchar(125), "amount" numeric(12,2));
Your function could look like this:
CREATE FUNCTION select_transactions3(_col text, _val text, _limit int)
RETURNS SETOF transactions AS
$BODY$
BEGIN
RETURN QUERY EXECUTE '
SELECT *
FROM transactions
WHERE ' || quote_ident(_col) || ' = $1
LIMIT $2'
USING _val, _limit;
END;
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
IN PostgreSQL 9.1 or later that's simpler with format()
...
RETURN QUERY EXECUTE format('
SELECT *
FROM transactions
WHERE %I = $1
LIMIT $2', _col)
USING _val, _limit;
...
%I escapes identifiers like quote_ident().
Major points:
You were bumping into the limitation of dynamic SQL that you cannot use parameters for identifiers. You have to build the query string with the column name and then execute it.
You can do that with values though. I demonstrate the use of the USING clause for EXECUTE. Also note the use of quote_ident(): prevents SQL injection and certain syntax errors.
I also largely simplified your function. [RETURN QUERY EXECUTE][3] makes your code shorter and faster. No need to loop if all you do is return the row.
I use named IN parameters, so you don't get confused with the $-notation in the query string. $1 and $2 inside the query string refer to the values provided in the USING clause, not to the input parameters.
I change to SELECT * as you have to return the whole row to match the declared return type anyway.
Last but not least: Be sure to consider what the manual has to say about functions declared SECURITY DEFINER.
RETURN TYPE
If you don't want to return the whole row, one convenient possibility is:
CREATE FUNCTION select_transactions3(_col text, _val text, _limit int)
RETURNS TABLE (invoice_no varchar(125), amount numeric(12,2) AS ...
Then you don't have to provide a column definition list with every call and can simplify to:
SELECT * FROM select_to_transactions3('invoice_no', '1103300105472', 1);
You can query all databases from the server and sort them according to your own database.
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tableName';