I can't subtract days from current_date parametrically in PostgreSQL - postgresql

I want to subtract some days from the current date and insert it into a table. If I write the number of days directly into the code it works. So this works.
do $$
DECLARE
myDate Date;
BEGIN
myDate = current_date - interval '10' day;
insert into myTable (myDate) values (myDate);
end $$;
The problem is that I want to put the number of days in a variable to make it parametric. But I can't. In fact the following doesn't work:
do $$
DECLARE
myDate Date;
daysAgo character varying := '10';
BEGIN
myDate = current_date - interval daysAgo day;
insert into myTable (myDate) values (myDate);
end $$;

You can subtract a number of days (integer) from a date:
do $$
DECLARE
myDate Date;
daysAgo int := 10;
BEGIN
myDate = current_date - daysAgo;
insert into myTable (myDate) values (myDate);
end $$;
Date/Time Functions and Operators.

Alternatively if you need to do this not just for days, you can do certain math operations on intervals:
do $$
declare
minutes int4 := 34;
begin
raise notice '%', interval '1 minute' * minutes;
raise notice '%', interval '15 minute' / minutes;
end;
$$;
-- outpus:
-- 00:34:00
-- 00:00:26.470588

This also works (with the make_interval function) but is more verbouse than Klin's solution.
do $$
DECLARE
myDate Date;
daysAgo int := 10;
BEGIN
myDate = current_date - make_interval(days=>daysAgo);
insert into myTable (myDate) values (myDate);
end $$;

Related

PostgreSQL PgAgent syntax error at or near DECLARE

Could someone tells me what is wrong please.
I try to create a job with using PgAgent with declaring some variables. When I run this code manually, it works successfully. But when I try to put this code in job step and save it, it throws me an error.
DO $$
DECLARE
start_date date;
dates date;
d SMALLINT;
counter integer := 0;
res date[];
treshold bigint;
BEGIN
TRUNCATE ditdemo.daily;
start_date:= now();
dates := start_date;
while counter <= 14 loop
dates := dates - INTERVAL '1 DAY';
select cal.is_holiday into d from ditdemo.calendar as cal where cal.calendardate = dates;
if d=0 then
res := array_append(res,dates);
counter := counter + 1;
end if;
/*
raise notice 'dates %', dates;
raise notice 'is holiday %', d;
raise notice 'result %', res;
*/
end loop;
insert into ditdemo.daily
select
time_bucket('1 day', j."timestamp") as day,
j.account,
count(*) as cnt
from ditdemo.jrnl as j
where
cast(j."timestamp" as date) in (select unnest(res)) AND
j.account not in (select account from ditdemo.user where is_service = 1)
group by day, j.account;
SELECT
round(PERCENTILE_CONT(0.95) WITHIN GROUP(ORDER BY d.cnt))
into treshold
FROM ditdemo.daily as d;
UPDATE ditdemo.calendar
SET daily_treshold = treshold
WHERE calendardate > start_date and calendardate <=(start_date::date + interval '7 day');
END $$;
It seems like PgAgent translates your code to another format, perhaps to string or something else and then can't parse it. To understand this try to:
Delete some special symbols from your code like brackets, quotes etc
Try to understand is it error from pgAgent or PostgreSQL
Good lucK!

make_date function does not exist in plpgsql

I've got a plpgsql function. I need to take the date 5 days from today, and then divide month into "fives" to takte the start of "last five". The problem is thay make_date does not exist in the posgres version that is used on the server....
create or replace function getFirstDayOfFive()
returns timestamp with time zone as $$
declare
firstDay timestamp;
startOp timestamp;
begin
startOp = now() - interval '5 day';
SELECT
make_date(
date_part('year', startOp)::int,
date_part('month', startOp)::int,
greatest(
floor(date_part('day', startOp) / 5) * 5,
1
)::int
)
INTO firstDay;
RETURN firstDay;
end;
$$
language plpgsql;
It worked fine last week, but now I got an error when I call it
ERROR: BŁĄD: function make_date(integer, integer, integer) does not exist
LINE 2: make_date(
^
HINT: There is no function matching provided name and arguments. Maybe you should cast data.
QUERY: SELECT
make_date(
date_part('year', startOp)::int,
date_part('month', startOp)::int,
greatest(
floor(date_part('day', startOp) / 5) * 5,
1
)::int
)
CONTEXT: PL/pgSQL function "getfirstdayoffive" line 7 at wyrażenie SQL
SQL state: 42883
What happened that earlier it worked and now it gives error?
[Edit]
I found out that make_date is available from postgresQL 9.4, but on the server there is posthresQL 9.1 is there any way to do the same in this old version od DB? I'm trying to replace the make_date with something like
create or replace function getFirstDayOfFive()
returns timestamp with time zone as $$
declare
firstDay timestamp;
startOp timestamp;
begin
startOp = now() - interval '5 day';
SELECT
date to_char(startOp, 'YYYY-MM-')||to_char(greatest(
floor(date_part('day', startOp) / 5) * 5,
1
)::int)
INTO firstDay;
RETURN firstDay;
end;
$$
language plpgsql;
I think you can simplify this by simply adding the desired number of days to the start of the month. Apparently you only want a date so I would also recommend to change the return type to date
create or replace function getfirstdayoffive()
returns date
as
$$
select date_trunc('month', current_date - 5)::date
+ (greatest(floor(extract(day from current_date - 5) / 5) * 5, 1))::int - 1;
$$
language sql
stable;

how to get the next date for a day of month

thanks for reading, this is the situation
I have a current_date and a day of month, so i need to know what will be the next date for this day of month, having in mind that some month don't have 30 and 31.
Example:
current_date = '2018-09-24'
day_of_week = 31
Expected result: '2018-12-31'
Currently i have this:
create or replace function next_diff(vals int[], current_val int) returns int as
$$
declare v int;
declare o int := vals[1];
begin
foreach v in array vals loop
if current_val >= o and current_val < v then
return v - current_val;
end if;
o := v;
end loop;
return vals[1] - current_val;
end;
$$ language plpgsql;
and this:
create or replace function next_day_of_month(days_of_month int[], curr_date date) returns date as
$$
declare cur_dom int := extract(day from curr_date);
declare next_diff int := next_diff(days_of_month, cur_dom);
begin
if next_diff < 0 then
curr_date := curr_date + '1 months'::interval;
end if;
curr_date := curr_date + (next_diff || 'days')::interval;
return curr_date;
end;
$$ language plpgsql;
but for this calling:
select next_day_of_month(array[31], '2018-09-24');
i am getting:
"2018-10-01"
Extra example
If i have this value
current_date = '2018-02-01'
day_of_week = 31
i will need the next month with 31th but i can't get '2018-02-31' because February don't have 31th then i should get '2018-02-31' because March have 31th.
Conclusion
if the month don't have the specified day must ignore the month and jump to the next.
thanks for all
Final method
Using Carlos Gomez answer, i create this PostgreSQL function and work perfectly:
create or replace function next_day_date(curr_date date, day_of_month int) returns date as
$$
declare next_day date;
begin
SELECT next_day_date into next_day FROM (
SELECT make_date_nullable(EXTRACT(year from n.month)::int, EXTRACT(month from n.month)::int, day_of_month) AS next_day_date
FROM (
SELECT generate_series(curr_date, curr_date + '3 months'::interval, '1 month'::interval) as month
) n
) results
WHERE results.next_day_date IS NOT NULL and results.next_day_date > curr_date LIMIT 1;
return next_day;
end;
$$ language plpgsql;
just add other filter in where clause and results.next_day_date > curr_date to prevent get the same or previous values for specified date
Thanks everyone for helping
Thenks Carlos you are the best
Gracias carlos eres el mejor :)
Your examples don't really match up but I think I know what you are trying to solve for (your first example result should be '2018-10-31' since October has 31 days and your second example result should be '2018-03-31'). It seems that given a date and a day of month you want to find the next month that has that day of month. To do this, I would do the following:
This function just wraps make_date to let it return null since it throws an exception if a date given to it is out of bounds (like February 30).
CREATE OR REPLACE FUNCTION make_date_nullable(year int, month int, day int)
RETURNS date as $$
BEGIN
RETURN make_date(year, month, day);
EXCEPTION WHEN others THEN RETURN null;
END;
$$ language plpgsql;
This SELECT first generates the next three months starting with the current one, then makes date out of them with your provided day_of_month and finally gets the first one that isn't null (exists according to postgresql.
SELECT next_day_date FROM (
SELECT make_date_nullable(EXTRACT(year from n.month)::int, EXTRACT(month from n.month)::int, day_of_month) AS next_day_date
FROM (
SELECT generate_series(current_date, current_date + '3 months'::interval, '1 month'::interval) as month
) n
) results
WHERE results.next_day_date IS NOT NULL LIMIT 1;
Hope this helps!

How to return results from a loop in plpgsql?

I want to see the result displaying dates like this
"2001-01-01 00:00:00+05:30"
"2001-01-02 00:00:00+05:30"
"2001-01-03 00:00:00+05:30"
"2001-01-04 00:00:00+05:30"
"2001-01-05 00:00:00+05:30"
"2001-01-06 00:00:00+05:30"
"2001-01-07 00:00:00+05:30"
so on...
but iam getting error
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function insert_date_dimension(date) line 12 at SQL statement
can you tell me what is the issue
function i created
create or replace function insert_date_dimension("Date" date)
returns text as
$$
Declare dat date;
start_date date;
end_date date;
Begin
start_date:='2016/01/01';
end_date:='2016/12/31';
while start_date<=end_date
loop
select start_date;
start_date:=start_date+ interval '1 day';
End loop;
return start_date;
end;
$$
LANGUAGE 'plpgsql';
can you tell me what is the issue with this function i was not able to execute it.I want to take all the column in select statment..please tell me create or replace function insert_date_dimension("date" date)
returns setof date as $$
declare
dat date;
start_date date;
end_date date;
begin
start_date := '2016/01/01';
end_date := '2016/12/31';
while start_date <= end_date loop
--return next start_date;
select start_date,date_part('week',start_date),date_part('quarter',start_date),to_char(start_date, 'day'),to_char(start_date, 'month'),
extract(year from current_date),extract(month from current_date);
start_date:= start_date + interval '1 day';
end loop;
end;
$$ language plpgsql;
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function insert_date_dimension(date) line 11 at SQL statement
********** Error **********
No need for a user defined function. Just use generate_series:
select generate_series('2016/01/01'::date, '2016/12/31', '1 day');
But if you are just fiddling with plpgsql then return next a setof date
create or replace function insert_date_dimension("date" date)
returns setof date as $$
declare
dat date;
start_date date;
end_date date;
begin
start_date := '2016/01/01';
end_date := '2016/12/31';
while start_date <= end_date loop
return next start_date;
start_date := start_date + interval '1 day';
end loop;
end;
$$ language plpgsql;
plpgsql is an identifier. Do not quote it.

PostgreSQL: month := interval '30 days';

Trying to delete records older than 1 month from 2 tables, where 1 references the "id" column in another:
create or replace function quincytrack_clean()
returns void as $BODY$
begin
month := interval '30 days';
delete from hide_id
where id in
(select id from quincytrack
where age(QDATETIME) > month);
delete from quincytrack
where age(QDATETIME) > month;
end;
$BODY$ language plpgsql;
but this fails with:
ERROR: syntax error at or near "month"
LINE 1: month := interval '30 days'
^
QUERY: month := interval '30 days'
CONTEXT: SQL statement in PL/PgSQL function "quincytrack_clean" near line 2
I'm reading the doc, but don't understand what's wrong with my declaration...
You need to declare the variable 'month', viz.:
declare
month interval;
begin
month := interval '30 days';
end;
Also, you might want to re-examine your "where" criteria. If QDATETIME is an indexed column, I don't think it will use the index, whereas QDATETIME < (now() - month) would.
You need to declare the variable before you can use it.
...
DECLARE
month INTERVAL;
BEGIN
month := interval '30 days';
...
But I would avoid using variable names that are reserved words or internal function names.