PostgreSQL PgAgent syntax error at or near DECLARE - postgresql

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!

Related

FOR loop over a date range in Postgres

In one of my functions in Postgres, I am trying to loop over a range of dates using the following code:
FOR timesheet_date IN select generate_series('2012-11-24'::date,'2012-12-03','1 day'::interval)::date LOOP
//My code goes here
END LOOP;
But I am getting an error
Now as am getting dates, I think it is not a record variable and hence the error.
But, how can I loop through a date range ? I am very new to Postgres actually.
DO $$
declare
dt record;
begin
FOR dt IN SELECT generate_series('2023-02-10'::date, '2023-02-15'::date, '1 day'::interval) LOOP
RAISE NOTICE 'Processing date: %', dt.generate_series;
END LOOP;
end; $$

Measure the time it takes to execute a PostgreSQL query

Based on Measure the time it takes to execute a t-sql query, how would one time several trials of a query in PostgreSQL?
A general outline would be
-- set up number of trials (say 1000)
SELECT CURRENT_DATE ; -- save start time
BEGIN
LOOP
-- execute query to be tested
END LOOP;
END;
SELECT CURRENT_DATE ; -- save end time
I.E. I want a PostgreSQL equivalent of the following TSQL code, taken from an answer by HumbleWebDev from the linked TSQL question: see [reference for code]
declare #tTOTAL int = 0
declare #i integer = 0
declare #itrs integer = 100
while #i < #itrs
begin
declare #t0 datetime = GETDATE()
--your query here
declare #t1 datetime = GETDATE()
set #tTotal = #tTotal + DATEDIFF(MICROSECOND,#t0,#t1)
set #i = #i + 1
end
select #tTotal/#itrs
-- your query here: Standard SQL queries such as Select * from table1 inner -- join table2, or executing stored procedure, etc.
Coming from an MSSQL background myself and now more often working in Postgres I feel your pain =)
The "trouble" with Postgres is that it supports only 'basic' SQL commands (SELECT, INSERT, UPDATE, CREATE, ALTER, etc...) but the moment you want to add logic (IF THEN, WHILE, variables, etc.) you need to switch to pl/pgsql which you can only use inside functions (AFAIK). From a TSQL POV there are quite some limitations and in fact, some things suddenly don't work anymore (or need to be done differently.. e.g. SELECT * INTO TEMPORARY TABLE tempTable FROM someTable will not work but CREATE TABLE tempTable AS SELECT * FROM someTable will)
Something I learned the hard way too is that CURRENT_TIMESTAMP (or Now()) will return the same value within a transaction. And since everything inside a function runs inside a transaction this means you have to use clock_timstamp()
Anyway, to answer your question, I think this should get you going:
CREATE OR REPLACE FUNCTION fn_test ( nbrOfIterations int)
RETURNS TABLE (iterations int, totalTime interval, secondsPerIteration int)
AS $$
DECLARE
i int;
startTime TIMESTAMP;
endTime TIMESTAMP;
dummy text;
BEGIN
i := 1;
startTime := clock_timestamp();
WHILE ( i <= nbrOfIterations) LOOP
-- your query here
-- (note: make sure to not return anything or you'll get an error)
-- example:
SELECT pg_sleep INTO dummy FROM pg_sleep(1);
i := i + 1;
END LOOP;
endTime := clock_timestamp();
iterations := nbrOfIterations;
totalTime := (endTime - startTime);
secondsPerIteration := (EXTRACT(EPOCH FROM endTime) - EXTRACT(EPOCH FROM startTime)) / iterations;
RETURN NEXT;
END;
$$ language plpgsql;
SELECT * FROM fn_test(5);
While the accepted answer is correct, this tweaking of it worked better for me. Again, I want to emphasize this extra answer below is based on the above answer, and it would not be possible without it. It just works better in my own situation to use the tweak I made below.
The answer below is indeed almost entirely based on the accepted answer. However, I changed how the return is used and also seconds to milliseconds:
----------------------------------------------------------------------------------------------------
-- fn__myFunction_Q.sql
----------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
-- DROP FUNCTION mySchema.fn__myFunction
--------------------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION mySchema.fn__myFunction ( nbrOfIterations int)
RETURNS TABLE (iterations int, totalTime interval, millisecondsPerIteration int) -- interval --
AS $$
declare
i int;
startTime TIMESTAMP;
endTime TIMESTAMP;
-- dummy text;
iterations int;
millisecondsPerIteration int;
totalTime interval;
BEGIN
i := 1;
startTime := clock_timestamp();
WHILE ( i <= nbrOfIterations) LOOP
PERFORM /* Put your query here, replacing SELECT with PERFORM */
--------------------------------------------------------------------------------------------
--SELECT
-- YOUR QUERY HERE
-- ...
--------------------------------------------------------------------------------------------
i := i + 1; -- very important to increment loop counter, else one gets an infinite loop!!!
END LOOP;
endTime := clock_timestamp();
iterations := nbrOfIterations;
totalTime := (endTime - startTime);
millisecondsPerIteration := 1000 * (EXTRACT(EPOCH FROM endTime) - EXTRACT(EPOCH FROM startTime)) / iterations;
RETURN QUERY select iterations, totalTime, millisecondsPerIteration;
-- RETURNS TABLE (iterations int, totalTime interval, secondsPerIteration int) -- interval --
-- RETURN NEXT;
END;
$$ language plpgsql;
--------------------------------------------------------------------------------------------
To call this function, just use:
SELECT * from mySchema.fn__myFunction(1000) as ourTableResult;

I can't subtract days from current_date parametrically in 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 $$;

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!

Trying to create Postgres function

In the create screen of the Postgress GUI I'm getting a syntax error at the while statement. Language is plpgsql and return type is void. TIA
enter code here
begin
declare counter integer := 1;
declare CurrentDate Date := '1/1/2018';
while CurrentDate < '1/1/2019'
loop
insert into dimCalendar select CurrentDate, EXTRACT(DOW FROM
current_date), EXTRACT(DOY FROM current_date);
CurrentDate := CurrentDate + 1;
end loop
end
Next time, try to include all the code please
Always use ISO date format, unless you have a reason not to
declare goes before begin
You're missing a semi-colon after "end loop"
Use snake_case in PostgreSQL please
create or replace function foo() returns void as $$
declare
counter integer := 1;
curr_date date := '2018-01-01';
begin
while curr_date < '2019-01-01' loop
raise notice 'curr_date: %', curr_date;
curr_date := curr_date + 1;
end loop;
end
$$ language plpgsql;