I find myself needing to generate a data set where I get the average number of rows over a time period. This period needs to be daily, by hour, for the same period for previous days. As an example, get the average number records from 1 am – 2 am for the last 180 days. In effect count the number of rows on 17 Nov 2022 between 1 am and 2 am, then count the number for rows on 16 Nov between 1 am and 2 am, then count the number of rows on 15 Nov between 1 am and 2 am, etc. Putting the date and time in the WHERE clause is easy enough but is manual. I would like to dynamically create these values for the WHERE clause based on a start date and time passed to the query. Therefore, if the start date and time passed in is 11-17-2022 01:00, an hour is added to get the end hour and then subtract 1 day for the past 180 days to get the average row count for the date range.
Is there some function(s) within Oracle that I could use to dynamically create the date and times based on the start date time?
Update 23 Nov 2022
Based on some research I've been doing, here is some code I have come up with so far. I thought it best to put this in a procedure.
CREATE OR REPLACE PROCEDURE TestAvgATMTransactions
(
DaysToLookBack INT,
CurrentStartDateTimeInput TIMESTAMP,
CurrentEndDateTimeInput TIMESTAMP,
PreviousStartDateTimeInput TIMESTAMP,
PreviousEndDateTimeInput TIMESTAMP,
RTXNTYPCDToCount VARCHAR2(4 BYTE)
)
IS
BEGIN
DECLARE
Low_ATM_Tran_Count_PWTH EXCEPTION,
TYPE two_cols_rt IS RECORD
(
PreviousStartDateTime TIMESTAMP,
PreviousEndDateTime TIMESTAMP
);
TYPE DateTimesToQuery IS TABLE OF two_cols_rt;
StartEndDateTime DateTimesToQuery;
PRAGMA EXCEPTION_INIT(Low_ATM_Tran_Count_PWTH, -20001);
PWTHCount RTXN.RTXNNBR%TYPE;-- the average transactions during the given period
AvgTransactions INT; -- the current average transactions
BEGIN
-- This does generate the days given how far I want to look back
-- however, it does not include the time for some reason
-- Need to figure out why as the time is crucial
-- (Chris Saxon - https://livesql.oracle.com/apex/livesql/file/content_LIHBDFVF9IUU6AFZB4H6NVLWL.html)
SELECT TO_TIMESTAMP (PreviousStartDateTimeInput, 'mm-dd-yyyy hh24:mi') + LEVEL - 1,-- AS StartDateTime,
TO_TIMESTAMP (PreviousEndDateTimeInput, 'mm-dd-yyyy hh24:mi') + LEVEL - 1-- AS EndDateTime
BULK COLLECT INTO StartEndDateTime
FROM DUAL
CONNECT BY LEVEL < DaysToLookBack;
DBMS_OUTPUT.PUT_LINE (StartEndDateTime.COUNT);
END;
-- Based on the article Bulk data processing with BULK COLLECT and FORALL in PL/SQL
-- by Steven Feuerstein (https://blogs.oracle.com/connect/post/bulk-processing-with-bulk-collect-and-forall)
-- FORALL seemes like it would do what I want, but not sure
FORALL index IN 1 .. StartEndDateTime.COUNT
-- This code needs to get the average for the given RTXNTYPCD for every day
-- in my two_cols_rt record
-- Yes needs work as this will not give me the overal average of transactions
SELECT COUNT(RTXNSOURCECD)
INTO PWTHCount
FROM RTXN
WHERE datelastmaint BETWEEN PreviousStartDateTimeInput AND PreviousEndDateTimeInput
AND RTXNSOURCECD = 'ATM'
AND RTXNTYPCD RTXNTYPCDToCount;
-- end FORALL
-- This gets the current count for the given RTXNTYPCD
SELECT COUNT(RTXNSOURCECD)
INTO AvgTransactions
FROM RTXN
WHERE datelastmaint BETWEEN CurrentStartDateTimeInput AND CurrentEndDateTimeInput
AND RTXNSOURCECD = 'ATM'
AND RTXNTYPCD = RTXNTYPCDToCount;
-- If the current count for the given RTXNTYPCD
-- is less than the average for the period raise the error
IF AvgTransactions < PWTHCount
THEN RAISE Low_ATM_Tran_Count_PWTH;
END IF
END;
Thank you
Michael
I am going to suggest a different approach. Here's my demo table
SQL> create table t as
2 select *
3 from dba_objects;
Table created.
SQL>
SQL> select
2 min(created),
3 max(created)
4 from t;
MIN(CREAT MAX(CREAT
--------- ---------
30-MAY-19 28-NOV-22
SQL>
SQL> select to_char(created,'HH24'), count(*)
2 from t
3 group by to_char(created,'HH24')
4 order by to_number(to_char(created,'HH24'));
TO COUNT(*)
-- ----------
00 9
02 69
03 18197
04 46309
05 4991
06 52
07 321
08 224
09 111
10 949
11 689
12 856
13 3673
14 2177
15 1158
16 48
17 41
18 187
19 613
20 56
21 102
22 116
23 72
23 rows selected.
So you can see I've got plenty of dates, and plenty of stuff created on certain hours of the day
Let's say I want to report for everything from June 1 to July 1 for 8am to 9am.
So first I'll need list of all the days in my date range
SQL>
SQL> select date '2022-06-01' + level - 1 dte
2 from dual
3 connect by date '2022-06-01' + level - 1 <= date '2022-07-01';
DTE
---------
01-JUN-22
02-JUN-22
03-JUN-22
04-JUN-22
05-JUN-22
06-JUN-22
07-JUN-22
08-JUN-22
09-JUN-22
10-JUN-22
11-JUN-22
12-JUN-22
13-JUN-22
14-JUN-22
15-JUN-22
16-JUN-22
17-JUN-22
18-JUN-22
19-JUN-22
20-JUN-22
21-JUN-22
22-JUN-22
23-JUN-22
24-JUN-22
25-JUN-22
26-JUN-22
27-JUN-22
28-JUN-22
29-JUN-22
30-JUN-22
01-JUL-22
31 rows selected.
Now I also need the real data, ie, for each day, how much stuff did I do between 8am and 9am. Rather than a query for every day (expensive) I'll scan the entire range, but only count the rows when they fall into the 8-9am timeslot.
SQL>
SQL> select
2 trunc(created) dy,
3 count(case when to_char(created,'HH24') = '08' then 1 end) in_this_hr
4 from t
5 where created >= date '2022-06-01'
6 and created < date '2022-07-02'
7 group by trunc(created)
8 order by 1;
DY IN_THIS_HR
--------- ----------
01-JUN-22 7
03-JUN-22 0
08-JUN-22 2
09-JUN-22 0
13-JUN-22 0
Now I can just bring those together with an outer join and we're done
SQL>
SQL> with
2 all_the_days as
3 ( select date '2022-06-01' + level - 1 dte
4 from dual
5 connect by date '2022-06-01' + level - 1 <= date '2022-07-01'
6 )
7 select *
8 from all_the_days;
DTE
---------
01-JUN-22
02-JUN-22
03-JUN-22
04-JUN-22
05-JUN-22
06-JUN-22
07-JUN-22
08-JUN-22
09-JUN-22
10-JUN-22
11-JUN-22
12-JUN-22
13-JUN-22
14-JUN-22
15-JUN-22
16-JUN-22
17-JUN-22
18-JUN-22
19-JUN-22
20-JUN-22
21-JUN-22
22-JUN-22
23-JUN-22
24-JUN-22
25-JUN-22
26-JUN-22
27-JUN-22
28-JUN-22
29-JUN-22
30-JUN-22
01-JUL-22
31 rows selected.
SQL>
SQL>
SQL> with
2 all_the_days as
3 ( select date '2022-06-01' + level - 1 dte
4 from dual
5 connect by date '2022-06-01' + level - 1 <= date '2022-07-01'
6 ),
7 daily_summary as
8 ( select
9 trunc(created) dy,
10 count(case when to_char(created,'HH24') = '08' then 1 end) in_this_hr
11 from t
12 where created >= date '2022-06-01'
13 and created < date '2022-07-02'
14 group by trunc(created)
15 )
16 select *
17 from all_the_days a left outer join daily_summary d
18 on a.dte = d.dy
19 order by a.dte;
DTE DY IN_THIS_HR
--------- --------- ----------
01-JUN-22 01-JUN-22 7
02-JUN-22
03-JUN-22 03-JUN-22 0
04-JUN-22
05-JUN-22
06-JUN-22
07-JUN-22
08-JUN-22 08-JUN-22 2
09-JUN-22 09-JUN-22 0
10-JUN-22
11-JUN-22
12-JUN-22
13-JUN-22 13-JUN-22 0
14-JUN-22
15-JUN-22
16-JUN-22
17-JUN-22
18-JUN-22
19-JUN-22
20-JUN-22
21-JUN-22
22-JUN-22
23-JUN-22
24-JUN-22
25-JUN-22
26-JUN-22
27-JUN-22
28-JUN-22
29-JUN-22
30-JUN-22
01-JUL-22
31 rows selected.
SQL>
SQL>