Generate a table with a range of timestamps - Oracle SQL - sql

I am trying to create a table with 2 columns in the below format with all the dates of 2019:-
START_TIME END_TIME
2010-01-01 17:00:00|2019-01-02 17:00:00
2019-01-02 17:00:00|2019-01-03 17:00:00
2019-01-03 17:00:00|2019-01-04 17:00:00
...
...
2019-12-31 17:00:00|2020-01-01 17:00:00
Could you please help troubleshoot the error in this?
Please suggest any optimized way of achieving this.
CREATE TABLE s.dates_2019
(
ts_range_begin timestamp(6),
ts_range_end timestamp(6),
);
insert into s.dates_2019 (ts_range_begin)
select
to_timestamp('12/31/2018 05:00 PM', 'YYYY-MM-DD HH24:MI:SS') + n.n
from
(select rownum n
from ( select 1 just_a_column
from dual
connect by level <=
to_timestamp('12/31/2019 05:00 PM', 'YYYY-MM-DD HH24:MI:SS')
- to_timestamp('12/31/2018 05:00 PM', 'YYYY-MM-DD HH24:MI:SS')
+ 1
) t
) n
where
to_timestamp('12/31/2018 05:00 PM','YYYY-MM-DD HH24:MI:SS') + n.n <= to_timestamp('12/31/2019 05:00 PM','YYYY-MM-DD HH24:MI:SS')
insert into s.dates_2019 (ts_range_end)
select
to_timestamp('2019-01-01 05:00 PM', 'YYYY-MM-DD HH24:MI:SS') + n.n
from
(select rownum n
from ( select 1 just_a_column
from dual
connect by level <=
to_timestamp('2020-01-01 05:00 PM', 'YYYY-MM-DD HH24:MI:SS')
- to_timestamp('2019-01-01 05:00 PM', 'YYYY-MM-DD HH24:MI:SS')
+ 1
) t
) n
where
to_timestamp('2019-01-01 05:00 PM','YYYY-MM-DD HH24:MI:SS') + n.n <= to_timestamp('2020-01-01 05:00 PM','YYYY-MM-DD HH24:MI:SS')
Error is :-
[Error Code: 30081, SQL State: 99999] ORA-30081: invalid data type for datetime/interval arithmetic

How about this?
SQL> alter session set nls_date_format = 'yyyy-mm-dd hh24:mi';
Session altered.
SQL> with dates as
2 (select date '2019-01-01' + 17/24 + level - 1 datum
3 from dual
4 connect by level <= date '2020-01-01' - date '2019-01-01' + 1
5 ),
6 staend as
7 (select datum as start_time,
8 lead(datum) over (order by datum) as end_time
9 from dates
10 )
11 select start_time,
12 end_time
13 from staend
14 where end_time is not null
15 order by start_time;
START_TIME END_TIME
---------------- ----------------
2019-01-01 17:00 2019-01-02 17:00
2019-01-02 17:00 2019-01-03 17:00
2019-01-03 17:00 2019-01-04 17:00
2019-01-04 17:00 2019-01-05 17:00
<snip>
2019-12-30 17:00 2019-12-31 17:00
2019-12-31 17:00 2020-01-01 17:00
365 rows selected.
SQL>
If you want to insert dates into a table, you don't really need a timestamp - date will do.
SQL> create table dates_2019
2 (ts_range_begin date,
3 ts_range_end date
4 );
Table created.
SQL> insert into dates_2019 (ts_range_begin, ts_range_end)
2 with dates as
3 (select date '2019-01-01' + 17/24 + level - 1 datum
4 from dual
5 connect by level <= date '2020-01-01' - date '2019-01-01' + 1
6 ),
7 staend as
8 (select datum as start_time,
9 lead(datum) over (order by datum) as end_time
10 from dates
11 )
12 select start_time,
13 end_time
14 from staend
15 where end_time is not null
16 order by start_time;
365 rows created.
SQL>
If you want to aggregate weekends, consider using offset in the lead analytic function. That offset depends on day name (Friday). Also, remove weekend days from the result set (line #21, where day not in ('sat', 'sun')).
SQL> insert into dates_2019 (ts_range_begin, ts_range_end)
2 with dates as
3 (select date '2019-01-01' + 17/24 + level - 1 datum,
4 --
5 to_char(date '2019-01-01' + 17/24 + level - 1,
6 'fmdy', 'nls_date_language = english') day
7 from dual
8 connect by level <= date '2020-01-01' - date '2019-01-01' + 1
9 ),
10 staend as
11 (select datum as start_time,
12 day,
13 lead(datum, case when day = 'fri' then 3
14 else 1
15 end) over (order by datum) as end_time
16 from dates
17 )
18 select start_time,
19 end_time
20 from staend
21 where day not in ('sat', 'sun')
22 and end_time is not null;
261 rows created.
SQL> select * from dates_2019 order by ts_range_begin;
TS_RANGE_BEGIN TS_RANGE_END
---------------- ----------------
2019-01-01 17:00 2019-01-02 17:00
2019-01-02 17:00 2019-01-03 17:00
2019-01-03 17:00 2019-01-04 17:00
2019-01-04 17:00 2019-01-07 17:00 --> aggregated
2019-01-07 17:00 2019-01-08 17:00
2019-01-08 17:00 2019-01-09 17:00
2019-01-09 17:00 2019-01-10 17:00
2019-01-10 17:00 2019-01-11 17:00
2019-01-11 17:00 2019-01-14 17:00 --> aggregated
2019-01-14 17:00 2019-01-15 17:00
2019-01-15 17:00 2019-01-16 17:00
<snip>

I think your actual error is because subtracting timestamps returns an interval, whereas you're using the result as a number in CONNECT BY LEVEL. You could cast the timestamps as dates (you might find the answers here useful) or use an interval expression to get the day component between the timestamps.
But if this is your actual SQL and not a simplification, I suggest just using dates in the CONNECT BY (you can still keep timestamps in your table if that's what you want) and doing something like...
CREATE TABLE dates_2019
(
ts_range_begin timestamp(6),
ts_range_end timestamp(6)
);
insert into dates_2019 (ts_range_begin)
select
to_timestamp('2018-12-31 17', 'YYYY-MM-DD HH24') + rownum
from
dual
connect by level <= to_date('2019-12-31 17', 'YYYY-MM-DD HH24') - to_date('2018-12-31 17', 'YYYY-MM-DD HH24')
;
update dates_2019 SET ts_range_end = ts_range_begin + 1;
... which I tested in Oracle 18c, but probably works 10g.

Related

Wonder if there is any reason to write query like "~~ WHERE order_date >= :ord_dt1 AND order_date < :ord_dt2 + 1 "

I'm a newbie student for oracle tuning.
I'm now solving some query optimization problems from a book and when I checked one of the author's model answer for a query and the where condition was something like
select customer_id, order_date
from table1
where customer_id = nvl(:cust_no, customer_id)
and order_date >= to_date(:ord_dt1, 'yyyymmdd')
and order_date < to_date(:ord_dt2, 'yyyymmdd') + 1
order by order_date
What I'm curious about is the reason he wrote date comparison differently.
I thought there might be a reason he wrote fifth line as
order_date < to_date(:ord_dt2, 'yyyymmdd') + 1
instead of
order_date <= to_date(:ord_dt2, 'yyyymmdd')
Is it some kinds of SQL formatting standards?
Is there any good?
If so, Why < date + 1 for end range is more desirable than <= date ?
I would be grateful any idea for this. Thanks for reading my question.
Let's try it with two sample date columns, one truncated to 'dd' and the other with hours in the date.
WITH
tbl As
(
Select
25 - LEVEL "ID",
TRUNC(SYSDATE + 1 - LEVEL, 'dd') "DATE_DD",
TRUNC(SYSDATE + 1 - LEVEL, 'dd') + LEVEL/24 "DATE_HH"
From Dual
CONNECT BY LEVEL <= 25
)
Table looks like this:
ID DATE_DD DATE_HH
---------- ------------------- -------------------
24 22.08.2022 00:00:00 22.08.2022 01:00:00
23 21.08.2022 00:00:00 21.08.2022 02:00:00
22 20.08.2022 00:00:00 20.08.2022 03:00:00
21 19.08.2022 00:00:00 19.08.2022 04:00:00
20 18.08.2022 00:00:00 18.08.2022 05:00:00
19 17.08.2022 00:00:00 17.08.2022 06:00:00
18 16.08.2022 00:00:00 16.08.2022 07:00:00
17 15.08.2022 00:00:00 15.08.2022 08:00:00
16 14.08.2022 00:00:00 14.08.2022 09:00:00
15 13.08.2022 00:00:00 13.08.2022 10:00:00
14 12.08.2022 00:00:00 12.08.2022 11:00:00
13 11.08.2022 00:00:00 11.08.2022 12:00:00
12 10.08.2022 00:00:00 10.08.2022 13:00:00
11 09.08.2022 00:00:00 09.08.2022 14:00:00
10 08.08.2022 00:00:00 08.08.2022 15:00:00
9 07.08.2022 00:00:00 07.08.2022 16:00:00
8 06.08.2022 00:00:00 06.08.2022 17:00:00
7 05.08.2022 00:00:00 05.08.2022 18:00:00
6 04.08.2022 00:00:00 04.08.2022 19:00:00
5 03.08.2022 00:00:00 03.08.2022 20:00:00
4 02.08.2022 00:00:00 02.08.2022 21:00:00
3 01.08.2022 00:00:00 01.08.2022 22:00:00
2 31.07.2022 00:00:00 31.07.2022 23:00:00
1 30.07.2022 00:00:00 31.07.2022 00:00:00
0 29.07.2022 00:00:00 30.07.2022 01:00:00
If we select the data:
SELECT
ID,
To_Char(DATE_DD, 'dd.mm.yyyy hh24:mi:ss') "DATE_DD",
To_Char(DATE_HH, 'dd.mm.yyyy hh24:mi:ss') "DATE_HH"
FROM
tbl
... with column DATE_DD used to filter the data any of folowing WHERE conditions returns the same rows...
WHERE
-- Between
DATE_DD Between To_Date('19.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss') And To_Date('21.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss')
-- or >= and <=
DATE_DD >= To_Date('19.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss') And DATE_DD <= To_Date('21.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss')
-- or >= and < (+1)
DATE_DD >= To_Date('19.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss') And DATE_DD < To_Date('21.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss') + 1
... the result is the same....
ID DATE_DD DATE_HH
---------- ------------------- -------------------
23 21.08.2022 00:00:00 21.08.2022 02:00:00
22 20.08.2022 00:00:00 20.08.2022 03:00:00
21 19.08.2022 00:00:00 19.08.2022 04:00:00
... but if we try the same conditions on the column (DATE_HH) containing hours/minutes/seconds...
WHERE
DATE_HH Between To_Date('19.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss') And To_Date('21.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss')
DATE_HH >= To_Date('19.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss') And DATE_HH <= To_Date('21.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss')
DATE_HH >= To_Date('19.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss') And DATE_HH < To_Date('21.08.2022 00:00:00', 'dd.mm.yyyy hh24:mi:ss') + 1
... then the first two (between),(>= and <=) will return
ID DATE_DD DATE_HH
---------- ------------------- -------------------
22 20.08.2022 00:00:00 20.08.2022 03:00:00
21 19.08.2022 00:00:00 19.08.2022 04:00:00
... and the one from your question returns this:
ID DATE_DD DATE_HH
---------- ------------------- -------------------
23 21.08.2022 00:00:00 21.08.2022 02:00:00
22 20.08.2022 00:00:00 20.08.2022 03:00:00
21 19.08.2022 00:00:00 19.08.2022 04:00:00
As you can see it is the same dataset as the one filtered on DATE_DD column. Hope this will help you to see the difference and the reason to use the date filtering like this. Regards...
Your proposed rewrite is not equivalent and could give different results.
Original version (I've included the parameter values and the filtering condition in the results to make it easier to follow):
select to_char(order_date,'YYYY-MM-DD HH24:MI') as order_date
, :ord_dt1, :ord_dt2
, '< :ord_dt2 + 1' as rule
from table1
where customer_id = nvl(:cust_no, customer_id)
and order_date >= to_date(:ord_dt1, 'yyyymmdd')
and order_date < to_date(:ord_dt2, 'yyyymmdd') + 1
order by order_date;
ORDER_DATE :ORD_DT1 :ORD_DT2 RULE
---------------- ---------- ---------- --------------
2022-08-02 00:00 20220802 20220803 < :ord_dt2 + 1
2022-08-03 16:30 20220802 20220803 < :ord_dt2 + 1
2 rows selected.
Your proposed rewrite:
select to_char(order_date,'YYYY-MM-DD HH24:MI') as order_date
, :ord_dt1, :ord_dt2
, '<= :ord_dt2' as rule
from table1
where customer_id = nvl(:cust_no, customer_id)
and order_date >= to_date(:ord_dt1, 'yyyymmdd')
and order_date <= to_date(:ord_dt2, 'yyyymmdd')
order by order_date;
ORDER_DATE :ORD_DT1 :ORD_DT2 RULE
---------------- ---------- ---------- -----------
2022-08-02 00:00 20220802 20220803 <= :ord_dt2
1 row selected.
Parameter :ord_dt2 when converted to a date evaluates to 2022-08-03 00:00, so :ord_dt2 + 1 is 2022-08-04 00:00.
2022-08-03 16:30 is less than 2022-08-04 00:00, but not less than or equal to 2022-08-03 00:00.
Sample data:
create table table1 (customer_id, order_date)
as
select 100, to_date('2022-08-02 00:00', 'YYYY-MM-DD HH24:MI') from dual union all
select 100, to_date('2022-08-03 16:30', 'YYYY-MM-DD HH24:MI') from dual union all
select 100, to_date('2022-08-04 19:15', 'YYYY-MM-DD HH24:MI') from dual union all
select 100, to_date('2022-08-05 00:00', 'YYYY-MM-DD HH24:MI') from dual;
Variable setup in SQL*Plus:
var ord_dt1 varchar2(8)
var ord_dt2 varchar2(8)
exec :ord_dt1 := '20220802'; :ord_dt2 := '20220803'

Creating n minute intervals per table row

I'm trying to get time intervals based on a table.
My source table is something like this:
ID
OTHER_DATA
TIME_BEG
TIME_END
DURATION
1
abcd
10:00
11:00
15
2
xyzt
16:00
17:00
30
Desired output:
ID
OTHER_DATA
ITVL_BEG
ITVL_END
1
abcd
10:00
10:15
1
abcd
10:15
10:30
1
abcd
10:30
10:45
1
abcd
10:45
11:00
2
xyzt
16:00
16:30
2
xyzt
16:30
17:00
TIME_BEG and TIME_END are VARCHAR columns but I also have them as DAY TO SECOND INTERVAL which are not shown here (TIME_BEG_INT and TIME_END_INT respectively).
Basically I need to duplicate every row TRUNC (EXTRACT (DAY FROM 24 * 60 * (TIME_END_INT - TIME_BEG_INT)) / DURATION) times and add this*DURATION to my dates, in one SQL.
Any help is appreciated.
If you are using intervals:
CREATE TABLE table_name (ID, OTHER_DATA, TIME_BEG, TIME_END, DURATION) AS
SELECT 1, 'abcd', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '11:00' HOUR TO MINUTE, INTERVAL '15' MINUTE FROM DUAL UNION ALL
SELECT 2, 'xyzt', INTERVAL '16:00' HOUR TO MINUTE, INTERVAL '17:00' HOUR TO MINUTE, INTERVAL '30' MINUTE FROM DUAL;
Then you can use:
WITH range (ID, OTHER_DATA, TIME_BEG, TIME_INT_END, TIME_END, DURATION) AS (
SELECT ID,
OTHER_DATA,
TIME_BEG,
LEAST(time_beg + duration, time_end),
TIME_END,
DURATION
FROM table_name
UNION ALL
SELECT ID,
OTHER_DATA,
TIME_INT_END,
LEAST(time_int_end + duration, time_end),
TIME_END,
DURATION
FROM range
WHERE time_int_end < time_end
)
SEARCH DEPTH FIRST BY id SET id_order
SELECT ID,
OTHER_DATA,
TIME_BEG AS itvl_beg,
TIME_INT_END AS itvl_end
FROM range;
Which outputs:
ID
OTHER_DATA
ITVL_BEG
ITVL_END
1
abcd
+000000000 10:00:00.000000000
+000000000 10:15:00.000000000
1
abcd
+000000000 10:15:00.000000000
+000000000 10:30:00.000000000
1
abcd
+000000000 10:30:00.000000000
+000000000 10:45:00.000000000
1
abcd
+000000000 10:45:00.000000000
+000000000 11:00:00.000000000
2
xyzt
+000000000 16:00:00.000000000
+000000000 16:30:00.000000000
2
xyzt
+000000000 16:30:00.000000000
+000000000 17:00:00.000000000
If you have the values as strings then you can convert them to intervals first:
CREATE TABLE table_name (ID, OTHER_DATA, TIME_BEG, TIME_END, DURATION) AS
SELECT 1, 'abcd', '10:00', '11:00', 15 FROM DUAL UNION ALL
SELECT 2, 'xyzt', '16:00', '17:00', 30 FROM DUAL;
WITH data(ID, OTHER_DATA, TIME_BEG, TIME_END, DURATION) AS (
SELECT ID,
OTHER_DATA,
TO_DSINTERVAL('0 '||TIME_BEG||':00'),
TO_DSINTERVAL('0 '||TIME_END||':00'),
NUMTODSINTERVAL(DURATION, 'MINUTE')
FROM table_name
),
range (ID, OTHER_DATA, TIME_BEG, TIME_INT_END, TIME_END, DURATION) AS (
SELECT ID,
OTHER_DATA,
TIME_BEG,
LEAST(time_beg + duration, time_end),
TIME_END,
DURATION
FROM data
UNION ALL
SELECT ID,
OTHER_DATA,
TIME_INT_END,
LEAST(time_int_end + duration, time_end),
TIME_END,
DURATION
FROM range
WHERE time_int_end < time_end
)
SEARCH DEPTH FIRST BY id SET id_order
SELECT ID,
OTHER_DATA,
TIME_BEG AS itvl_beg,
TIME_INT_END AS itvl_end
FROM range;
db<>fiddle here
This is the classic solution based on the pre-interval DATE calculation
with time_int(ID, OTHER_DATA, ITVL_BEG, ITVL_END, DURATION, TIME_END) as (
select
ID, OTHER_DATA, TIME_BEG,
to_char(to_date(time_beg,'HH24:mi')+duration/(24*60),'HH24:mi'),
DURATION, TIME_END
from tab
union all
select
ID, OTHER_DATA, ITVL_END,
to_char(to_date(ITVL_END,'HH24:mi')+duration/(24*60),'HH24:mi'),
DURATION, TIME_END
from time_int
where ITVL_END <= TIME_END
)
select
ID, OTHER_DATA, ITVL_BEG, ITVL_END
from time_int
order by 1,3;
ID OTHE ITVL_ ITVL_
---------- ---- ----- -----
1 abcd 10:00 10:15
1 abcd 10:15 10:30
1 abcd 10:30 10:45
1 abcd 10:45 11:00
1 abcd 11:00 11:15
2 xyzt 16:00 16:30
2 xyzt 16:30 17:00
2 xyzt 17:00 17:30
A recursive CTE is used with the step based on the following calculation to get the next interval boundary (assuming that your time is stored as VARCHAR2)
to_char(to_date(time_beg,'HH24:mi')+duration/(24*60),'HH24:mi')
Note that you may convert 10:00 to date and Oracle provides the default missing day, month and year, that you do not need because after increasing by duration you converts back to a HH24:MI string.

Find peaks of data

So I have a table Integrations.
Inte
Start Date
End Date
Total_Duration
INT1
1/7/2021 7:16:00
1/7/2021 9:22:00
02:06:00
INt2
2/7/2021 3:48:00
2/7/2021 5:10:00
01:22:00
Output I need:
Running Time
No of Inte.
1/7/2021 7:00:00
1
1/7/2021 8:00:00
1
1/7/2021 9:00:00
1
2/7/2021 4:00:00
1
2/7/2021 5:00:00
1
Basically it want to plot the peak hour when most Integrations were running.
Sql query I wrote:
select time, sum(value) as No_of_Inte
from(
select round(Start_Date, 'HH24') as time, count(*) as value
from Integrations
group by Start_Date
)
group by time
order by time asc
But this does not consider Total Duration.
Output :
Running Time
No of Inte.
1/7/2021 7:00:00
1
2/7/2021 4:00:00
1
Also, new Integrations are added every day.
This can be done using a recursive query. First create the test data
CREATE TABLE integrations (inte,start_date, end_date)
AS
(
SELECT 'INT1', TO_DATE('1/7/2021 7:16:00','DD/MM/YYYY HH24:MI:SS'), TO_DATE('1/7/2021 9:22:00','DD/MM/YYYY HH24:MI:SS') FROM dual UNION ALL
SELECT 'INT2', TO_DATE('2/7/2021 3:48:00','DD/MM/YYYY HH24:MI:SS'), TO_DATE('2/7/2021 5:10:00','DD/MM/YYYY HH24:MI:SS') FROM dual
);
Now use a recursive query to loop through the hours between start and end date. Then group by hour to get the correct counts per hour.
WITH row_per_hours (id, run_hour, end_date) AS
(
SELECT inte,
TRUNC(start_date,'HH24'),
end_date
FROM integrations
UNION ALL
SELECT id,
run_hour + INTERVAL '1' HOUR,
end_date
FROM row_per_hours
WHERE run_hour + INTERVAL '1' HOUR < end_date
)
SELECT TO_CHAR(run_hour,'DD/MM/YYYY HH24:MI:SS') as running_time,
COUNT(id) as integration_count
FROM row_per_hours
GROUP BY TO_CHAR(run_hour,'DD/MM/YYYY HH24:MI:SS') ORDER BY 1;
RUNNING_TIME INTEGRATION_COUNT
------------------- -----------------
01/07/2021 07:00:00 1
01/07/2021 08:00:00 1
01/07/2021 09:00:00 1
02/07/2021 03:00:00 1
02/07/2021 04:00:00 1
02/07/2021 05:00:00 1
For 12C and above:
You may use lateral join to generate required number of rows per each interval. Since it looks like you need some rounding of dates towards neares hour, I've added round instead of trunc. Or is there any other reason for the first interval is treating 7:00 as inclusion?.
with a(Inte, start_dt, end_dt) as (
select
'INT1'
, to_date('1/7/2021 07:16:00', 'dd/mm/yyyy hh24:mi:ss')
, to_date('1/7/2021 09:22:00', 'dd/mm/yyyy hh24:mi:ss')
from dual union all
select
'INt2'
, to_date('2/7/2021 03:48:00', 'dd/mm/yyyy hh24:mi:ss')
, to_date('2/7/2021 05:10:00', 'dd/mm/yyyy hh24:mi:ss')
from dual
)
select /*+ gather_plan_statistics */
b.hour_
, count(1) as int_cnt
from a
outer apply (
select
round(a.start_dt + numtodsinterval(level - 1, 'HOUR'), 'hh24') as hour_
from dual
connect by round(start_dt, 'hh24') + numtodsinterval(level - 1, 'HOUR') <= trunc(end_dt, 'hh24')
) b
group by b.hour_
order by 1
HOUR_ | INT_CNT
:------------------ | ------:
2021-07-01 07:00:00 | 1
2021-07-01 08:00:00 | 1
2021-07-01 09:00:00 | 1
2021-07-02 04:00:00 | 1
2021-07-02 05:00:00 | 1
db<>fiddle here

Counting records and grouping them by the hour

I'm trying to count the records in my table and grouping them by hour, i'm getting results with my query but I want it to return every hour even if there are no records.
My current query is,
SELECT nvl(count(*),0) AS transactioncount, trunc(date_modified, 'HH') as TRANSACTIONDATE
FROM TABLE
WHERE date_modified between to_date('23-JAN-19 07:00:00','dd-MON-yy hh24:mi:ss') and to_date('24-Jan-19 06:59:59','dd-MON-yy hh24:mi:ss')
group by trunc(date_modified, 'HH');
This returns a result like this,
TRANSACTIONCOUNT | TRANSACTIONDATE
43 | 23-Jan-19 07:00:00
47 | 23-Jan-19 08:00:00
156 | 23-Jan-19 14:00:00
558 | 23-Jan-19 15:00:00
What I want is for it to return every hour between my 2 dates so,
TRANSACTIONCOUNT | TRANSACTIONDATE
43 | 23-Jan-19 07:00:00
47 | 23-Jan-19 08:00:00
0 | 23-Jan-19 09:00:00
0 | 23-Jan-19 10:00:00
0 | 23-Jan-19 11:00:00
0 | 23-Jan-19 12:00:00
0 | 23-Jan-19 13:00:00
156 | 23-Jan-19 14:00:00
558 | 23-Jan-19 15:00:00
--......
0 | 24-Jan-19 00:00:00
0 | 24-Jan-19 01:00:00
0 | 24-Jan-19 02:00:00
--and so on
To fill the holes in the transaction hours you create first a complete table of hours.
You may use Recursive Subquery Factoring to do it
WITH hour_table(TRANSACTIONDATE) AS (
SELECT to_date('23-JAN-19 07:00:00','dd-MON-yy hh24:mi:ss') /* init hour here */
FROM DUAL
UNION ALL
SELECT TRANSACTIONDATE + 1/24
FROM hour_table
WHERE TRANSACTIONDATE + 1/24 < to_date('24-JAN-19 06:59:59','dd-MON-yy hh24:mi:ss') /* limit here */
)
select * from hour_table;
TRANSACTIONDATE
-------------------
23.01.2019 07:00:00
23.01.2019 08:00:00
...
24.01.2019 05:00:00
24.01.2019 06:00:00
Note that you use the staring and ending date in this query, the starting date must be exact an hour.
Next step is as simple as to outer join this hour table to your aggregation and set the default value for the missing hours with NVL.
with hour_table(TRANSACTIONDATE) AS (
SELECT to_date('23-JAN-19 07:00:00','dd-MON-yy hh24:mi:ss') /* init hour here */
FROM DUAL
UNION ALL
SELECT TRANSACTIONDATE + 1/24
FROM hour_table
WHERE TRANSACTIONDATE + 1/24 < to_date('24-JAN-19 06:59:59','dd-MON-yy hh24:mi:ss') /* limit */
),
agg as (
SELECT nvl(count(*),0) AS transactioncount, trunc(date_modified, 'HH') as TRANSACTIONDATE
FROM "TABLE"
WHERE date_modified between to_date('23-JAN-19 07:00:00','dd-MON-yy hh24:mi:ss') and to_date('24-Jan-19 06:59:59','dd-MON-yy hh24:mi:ss')
group by trunc(date_modified, 'HH')
)
select t.TRANSACTIONDATE, nvl(transactioncount,0) transactioncount
from hour_table t
left outer join agg a
on t.TRANSACTIONDATE = a.TRANSACTIONDATE
order by 1;
You might consider using the following with CONNECT BY level logic :
SELECT sum(transactioncount) as transactioncount, transactiondate
FROM
(
with "TABLE"(date_modified) as
(
SELECT timestamp'2019-01-23 08:00:00' FROM dual union all
SELECT timestamp'2019-01-23 08:30:00' FROM dual union all
SELECT timestamp'2019-01-23 09:00:00' FROM dual union all
SELECT timestamp'2019-01-24 05:01:00' FROM dual
)
SELECT nvl(count(*),0) AS transactioncount, trunc(date_modified, 'hh24') as transactiondate
FROM "TABLE" t
GROUP BY trunc(date_modified, 'HH24')
UNION ALL
SELECT 0, timestamp'2019-01-23 07:00:00' + ( level - 1 )/24
FROM dual
CONNECT BY level <= 24 * extract( day from
timestamp'2019-01-24 06:59:59'-
timestamp'2019-01-23 07:00:00') +
extract( hour from
timestamp'2019-01-24 06:59:59'-
timestamp'2019-01-23 07:00:00') + 1
)
GROUP BY transactiondate
ORDER BY transactiondate
Rextester Demo

Previous Weekdays

I have a requirement in which i have to find start and end date.
Start date is First sat of the previous month of created date and end date is previous friday of created date.
Eg Below .. I am passing created date and need to derive start and end date like this below.
CREATED_DT Start_date end_date
04/08/2015 15:36 04/07/2015 00:00 31/07/2015 23:59
07/07/2015 15:32 06/06/2015 00:00 03/07/2015 23:59
You should not depend on the locale-specific NLS settings.
You could use following functions:
NEXT_DAY
ADD_MONTHS
TRUNC
For example,
SQL> alter session set nls_date_format='DD/MM/YYYY HH24:MI:SS';
SQL> WITH t(created_dt) AS(
2 SELECT to_date('04/08/2015 15:36','DD/MM/YYYY HH24:MI') FROM DUAL UNION ALL
3 SELECT to_date('07/07/2015 15:32','DD/MM/YYYY HH24:MI') FROM DUAL
4 )
5 SELECT CREATED_DT,
6 NEXT_DAY(TRUNC(add_months(created_dt, -1),'MM') -1,TO_CHAR(to_date('6','J'),'Day')) -1 start_date,
7 NEXT_DAY(TRUNC(created_dt, 'MM') -1, TO_CHAR(to_date('5','J'),'Day')) -1 + 0.99999 AS end_date
8 FROM t;
CREATED_DT START_DATE END_DATE
------------------- ------------------- -------------------
04/08/2015 15:36:00 04/07/2015 00:00:00 31/07/2015 23:59:59
07/07/2015 15:32:00 06/06/2015 00:00:00 03/07/2015 23:59:59
SQL>
To get the time portion as 23:59:59, you could either add 0.99999 or subtract INTERVAL '1' SECOND. For example,
SQL> alter session set nls_date_format='DD/MM/YYYY HH24:MI:SS';
Session altered.
SQL> WITH t(created_dt) AS(
2 SELECT to_date('04/08/2015 15:36','DD/MM/YYYY HH24:MI') FROM DUAL UNION ALL
3 SELECT to_date('07/07/2015 15:32','DD/MM/YYYY HH24:MI') FROM DUAL
4 )
5 SELECT CREATED_DT,
6 NEXT_DAY(TRUNC(add_months(created_dt, -1),'MM') -1,TO_CHAR(to_date('6','J'),'Day')) -1 start_date,
7 NEXT_DAY(TRUNC(created_dt, 'MM') -1, TO_CHAR(to_date('5','J'),'Day')) - (INTERVAL '1' SECOND) AS end_date
8 FROM t;
CREATED_DT START_DATE END_DATE
------------------- ------------------- -------------------
04/08/2015 15:36:00 04/07/2015 00:00:00 31/07/2015 23:59:59
07/07/2015 15:32:00 06/06/2015 00:00:00 03/07/2015 23:59:59
SQL>
You can use some of the Date functions. I'm giving for sysdate. Use according to your requirement.
select NEXT_DAY(trunc((trunc (add_months (sysdate, -1), 'mm')), 'MONTH')-1, 'Saturday') as Start_date,
NEXT_DAY(SYSDATE-8, 'FRIDAY') as End_date
from dual;
Output
START_DATE END_DATE
04-JUL-15 21-AUG-15
Use Next_day function. The Oracle/PLSQL NEXT_DAY function returns the first weekday that is greater than a date.
select TO_DATE('04/08/2015 15:36' ,'DD/MM/YYYY hh24:mi') as created_date,
next_day(ADD_MONTHS(TRUNC(TO_DATE('04/08/2015 15:36','DD/MM/YYYY hh24:mi')+1,'MM'),-1),'SATURDAY')
as start_date,
next_day(trunc(TO_DATE('04/08/2015 15:36','DD/MM/YYYY hh24:mi')-8)+0.99999 ,'FRIDAY')as end_date
FROM DUAL
Instead of adding 0.99999 we can also achieve same thing with 1-(1/(24*60*60)) we are adding one day after that subtracting 1 part from 24*60*60 seconds.
I have achieved by this way
end date: Where created _dt is date value what i am passing..!!
next_day(TRUNC(to_date(created_dt,'DD-MM-YYYY HH24:MI:SS'))-7,'FRIDAY') +
INTERVAL '23:59:59' HOUR TO SECOND AS range_end_dt