Pass date intervals as function parameters - postgresql

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

Related

issue with create function in PostgreSQL

I am trying to get year from orderdate function
type
orderdate date
create or replace function getyearfromdate(year date)returns
table
as
$$
begin
return QUERY execute (
'select extract (year from orderdate) FROM public.orderalbum'
);
end;
$$
language plpgsql;
I write a logic but not able to create a function
I want to return year from the orderdate.
I want to pass a orderdate and return year from the function
I am facing below error
ERROR: syntax error at or near "as"
LINE 3: as
^
SQL state: 42601
Character: 70
Based on your comments, it seems you only want a wrapper around the extract() function. In that case you do not want a set returning function. And you don't need PL/pgSQL or even dynamic SQL for this:
create or replace function getyearfromdate(p_date_value date)
returns int --<< make this a scalar function!
as
$$
select extract(year from p_date_value)::int;
$$
language sql;
Note that I renamed your parameter as I find a parameter named year for a date value highly confusing.
That function can then be used as part of a SELECT list:
SELECT ..., getyearfromdate(orderdate)
FROM public.orderalbum
GROUP BY ...
Original answer based on the question before comments clarified it.
As documented in the manual returns table requires a table definition.
Your use of dynamic SQL is also useless.
create or replace function getyearfromdate(year date)
returns table (year_of_month int)
as
$$
begin
return QUERY
select extract(year from orderdate)::int
FROM public.orderalbum;
end;
$$
language plpgsql;
I am not sure why you are passing a parameter to the function that you never use.

Difficulties using Postgresql's EXTRACT tool via function call

I'm trying to figure out how to use the Postgresql EXTRACT function to convert a given date_variable into its equivalent day of the week. I understand that it will convert the date_variable into a numbering from 0 - 6 (0 is Sunday, 6 is Saturday etc)
I've created a simple table to test my queries. Here I will attempt to convert the start_date into its DOW equivalent.
DROP TABLE IF EXISTS test;
CREATE TABLE test(
start_date date PRIMARY KEY,
end_date date
);
INSERT INTO test (start_date, end_date) VALUES ('2021-03-31', '2021-03-31'); -- Today (wed), hence 3
INSERT INTO test (start_date, end_date) VALUES ('2021-03-30', '2021-03-30'); -- Yesterday (tues), hence 2
INSERT INTO test (start_date, end_date) VALUES ('2021-03-29', '2021-03-29'); -- Day before (mon), hence 1
If I were to run the query below
SELECT (EXTRACT(DOW FROM t.start_date)) AS day FROM test t;
It works fine, and returns the result as intended (returns a single column table with values (3, 2, 1) respectively.)
However, when I attempt to write a function to return the exact same query
CREATE OR REPLACE FUNCTION get_day()
RETURNS TABLE (day integer) AS $$
BEGIN
RETURN QUERY
SELECT (EXTRACT(DOW FROM t.start_date)) as day
FROM test t;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_day(); -- throws error "structure of query does not match function result type"
I get an error instead. I cant seem to find the issue and don't know what is causing it.
extract() returns a double precision value, but your function is declared to return an integer. You need to cast the value:
CREATE OR REPLACE FUNCTION get_day()
RETURNS TABLE (day integer) AS $$
BEGIN
RETURN QUERY
SELECT EXTRACT(DOW FROM t.start_date)::int as day
FROM test t;
END;
$$ LANGUAGE plpgsql;
But you don't really need PL/pgSQL for this, a language SQL function would also work.
CREATE OR REPLACE FUNCTION get_day()
RETURNS TABLE (day integer) AS $$
SELECT EXTRACT(DOW FROM t.start_date)::int as day
FROM test t;
$$
LANGUAGE sql
stable;
As you are not passing any parameters, I would actually use a view for this:
create or replace view day_view
AS
SELECT EXTRACT(DOW FROM t.start_date)::int as day
FROM test t;
The extract function returns values of type double precision.
You declare result to be integer.
You should cast the result of EXTRACT to integer:
CREATE OR REPLACE FUNCTION get_day()
RETURNS TABLE (day integer) AS $$
BEGIN
RETURN QUERY
SELECT EXTRACT(DOW FROM t.start_date)::integer as day
FROM test t;
END;
$$ LANGUAGE plpgsql;

Using parameters passed to stored procedure in query

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;

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

Date part in WHERE clause of a function

I want to select persons from a table where the date is within a given month.
This is what I have so far, but it's not working:
CREATE OR REPLACE FUNCTION u7()
RETURNS character varying AS
$BODY$
DECLARE
data varchar=`data`;
mes varchar=`2016-11-21`;
incidencia varchar=`expulsions`;
valor varchar;
BEGIN
EXECUTE `SELECT `
||quote_ident(data)
||`FROM `
||quote_ident(incidencia)
||` WHERE data IN(select date_part(`month`, TIMESTAMP $1))`
INTO valor USING mes;
return valor;
END;
$BODY$
LANGUAGE plpgsql;
select * FROM u7();
Clean syntax for what you are trying to do could look like this:
CREATE OR REPLACE FUNCTION u7()
RETURNS TABLE (valor text) AS
$func$
DECLARE
data text := 'data'; -- the first 3 would typically be function parameters
incidencia text := 'expulsions';
mes timestamp = '2016-11-21';
mes0 timestamp := date_trunc('month', mes);
mes1 timestamp := (mes0 + interval '1 month');
BEGIN
RETURN QUERY EXECUTE format(
'SELECT %I
FROM %I
WHERE datetime_column_name >= $1
AND datetime_column_name < $2'
, data, incidencia)
USING mes0, mes1;
END
$func$ LANGUAGE plpgsql;
SELECT * FROM u7();
Obviously, data cannot be a text column and a timestamp or date column at the same time. I use datetime_column_name for the timestamp column - assuming it's data type timestamp.
Aside from various syntax errors, do not use the construct with date_part(). This way you would have to process every row of the table and could not use an index on datetime_column_name - which my proposed alternative can.
See related answers for explanation:
EXECUTE...INTO...USING statement in PL/pgSQL can't execute into a record?
Table name as a PostgreSQL function parameter
How do I match an entire day to a datetime field?