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
Related
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
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.
I want to select rows with a specific time interval but the date doesn't matter. So I need a function to return just the time part. I tried using:
to_char(mydate, 'HH12:MI:SS') between '00:00:00' and '08:00:00'
but this doesn't seem to work. Any ideas?
With some sample data you can see that using HH12 doens't necessarly produce the strings you are expecting:
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
-- CTE just for dummy data
with mytable (mydate) as (
select cast(timestamp '2018-08-01 00:00:00' as date) from dual
union all select cast(timestamp '2018-08-02 07:59:59' as date) from dual
union all select cast(timestamp '2018-08-03 08:00:00' as date) from dual
union all select cast(timestamp '2018-08-04 08:00:01' as date) from dual
union all select cast(timestamp '2018-08-05 19:59:59' as date) from dual
union all select cast(timestamp '2018-08-06 20:00:00' as date) from dual
union all select cast(timestamp '2018-08-07 20:00:01' as date) from dual
)
-- actual query
select mydate,
to_char(mydate, 'HH24:MI:SS') as time_24,
to_char(mydate, 'HH12:MI:SS') as time_12
from mytable;
MYDATE TIME_24 TIME_12
------------------- -------- --------
2018-08-01 00:00:00 00:00:00 12:00:00
2018-08-02 07:59:59 07:59:59 07:59:59
2018-08-03 08:00:00 08:00:00 08:00:00
2018-08-04 08:00:01 08:00:01 08:00:01
2018-08-05 19:59:59 19:59:59 07:59:59
2018-08-06 20:00:00 20:00:00 08:00:00
2018-08-07 20:00:01 20:00:01 08:00:01
So when you try to filter using that HH12-based string it includes records you don't expect to see, between 8am and 8pm; and also excludes midnight (as that is '12:00:00' not '00:00:00'):
select mydate
from mytable
where to_char(mydate, 'HH12:MI:SS') between '00:00:00' and '08:00:00';
MYDATE
-------------------
2018-08-02 07:59:59
2018-08-03 08:00:00
2018-08-05 19:59:59
2018-08-06 20:00:00
If you use HH24 instead then you get
select mydate
from mytable
where to_char(mydate, 'HH24:MI:SS') between '00:00:00' and '08:00:00';
MYDATE
-------------------
2018-08-01 00:00:00
2018-08-02 07:59:59
2018-08-03 08:00:00
Also, notice that between is inclusive, so it picks up records at exactly 08:00:00. That may not be what you want - if you're splitting the day into three 8-hour periods, you don't data for that second to be included multiple times; so you can use a more explicit range instead:
select mydate
from mytable
where to_char(mydate, 'HH24:MI:SS') >= '00:00:00'
and to_char(mydate, 'HH24:MI:SS') < '08:00:00';
MYDATE
-------------------
2018-08-01 00:00:00
2018-08-02 07:59:59
then your second shift is:
where to_char(mydate, 'HH24:MI:SS') >= '08:00:00'
and to_char(mydate, 'HH24:MI:SS') < '16:00:00';
and your third shift is:
where to_char(mydate, 'HH24:MI:SS') >= '16:00:00';
or if you prefer, for consistency:
where to_char(mydate, 'HH24:MI:SS') >= '16:00:00'
and to_char(mydate, 'HH24:MI:SS') < '24:00:00';
You can't ever get the hour reported as 24 but as it's a string comparison that doesn't matter here, though it is slightly jarring.
TO_CHAR(mydate, 'HH24:MI:SS')
WHERE mydate BETWEEN '00:00:00' AND '08:00:00';
I'm looking to return data in Oracle for the last full week starting Sunday and finishing Saturday. This needs to be able to run any day of the week.
So I know that this is possible in SQL Server as I have reports that do the exact same thing:-
SET #startdate = DATEADD(wk, -1, DATEADD(wk, DATEDIFF(wk, 0,getdate()), -1))
SET #enddate = DATEADD(wk, DATEDIFF(wk, 0, getdate()), -1)
Today being Friday 17th March the above would return data between Sunday 5th March and Saturday 11th March.
I want to do the same thing in Oracle. Everywhere I've looked so far comes back with results like this:-
SELECT TRUNC (SYSDATE) - (SELECT TO_CHAR (SYSDATE, 'D') FROM DUAL),
TRUNC (SYSDATE) - (SELECT TO_CHAR (SYSDATE, 'D') + 1 FROM DUAL)
FROM DUAL
Or
SELECT SYSDATE AS TODAYS_DATE,
NEXT_DAY (SYSDATE - 7, 'SAT') AS PREV_SATURDAY,
NEXT_DAY (SYSDATE - 7, 'SUN') AS PREV_SUNDAY
FROM DUAL
I'm trying to avoid any 'sysdate-7' type code since that's pretty unwieldy in this situation - can anyone help at all?
Thanks
If, at any given point in time, "previous week" means the seven-day period that ENDS on the most recent midnight at the beginning of a Sunday, then something like this should work:
with inputs (dt) as (
select sysdate from dual union all
select sysdate + 1 from dual union all
select sysdate + 2 from dual union all
select sysdate + 3 from dual
)
-- end of test data; SQL solution begins below this line
select to_char(dt, 'Dy dd-Mon-yyyy hh:mi AM') as dt,
trunc(dt + 1, 'iw') - 8 as prev_wk_start,
trunc(dt + 1, 'iw') - 1 as prev_wk_end
from inputs;
DT PREV_WK_START PREV_WK_END
------------------------ ------------------- -------------------
Fri 17-Mar-2017 10:58 AM 03/05/2017 00:00:00 03/12/2017 00:00:00
Sat 18-Mar-2017 10:58 AM 03/05/2017 00:00:00 03/12/2017 00:00:00
Sun 19-Mar-2017 10:58 AM 03/12/2017 00:00:00 03/19/2017 00:00:00
Mon 20-Mar-2017 10:58 AM 03/12/2017 00:00:00 03/19/2017 00:00:00
Note: Whenever we work with time intervals, we must decide if the endpoints are included. In most cases, the best (and most used) convention is that the start date/time is included, while the end date/time is NOT included. The query above is consistent with this interpretation. If the query is run for an input like date '2017-03-19', which is midnight at the beginning of a Sunday, the query will return the week that ENDS exactly at that date and time. All of this "previous week" strictly precedes the input date/time, because the end point of the week is NOT included in the "one-week interval."
Use TRUNC( date_value, 'IW' ) to do it independent of the NLS_TERRITORY or NLS_DATE_LANGUAGE session parameters:
SELECT SYSDATE AS TODAYS_DATE,
TRUNC( SYSDATE, 'IW' ) AS MONDAY_OF_THIS_ISO_WEEK,
TRUNC( SYSDATE, 'IW' ) - INTERVAL '2' DAY AS PREV_SATURDAY,
TRUNC( SYSDATE, 'IW' ) - INTERVAL '8' DAY AS PREV_SUNDAY
FROM DUAL;
Output:
TODAYS_DATE MONDAY_OF_THIS_ISO_ PREV_SATURDAY PREV_SUNDAY
------------------- ------------------- ------------------- -------------------
2017-03-17 15:45:25 2017-03-13 00:00:00 2017-03-11 00:00:00 2017-03-05 00:00:00
What I am trying to do is to create two timestamps a StartDate timestamp which will be 09/08/2015 00:00:00 and an EndDate time stamp which should be 09/08/2015 23:59:59 as easy as it is to achieve in MS SQL, I have not been able to find a Make_Date function or Add_Days function to get either of the timestamps in Oracle PL SQL.
Can anyone help me out?
Rather than using fractional numbers 86399 / 86400 (which requires some working out when reviewing the code to see why you picked those magic numbers) to get the end date you can explicitly state the time periods using INTERVALS (which is easy to see at a glance what you are doing):
SQL Fiddle
Oracle 11g R2 Schema Setup:
Query 1:
SELECT TRUNC( CURRENT_DATE ) AS START_DATE,
TRUNC( CURRENT_DATE ) + INTERVAL '1' DAY - INTERVAL '1' SECOND AS END_DATE
FROM DUAL
Results:
| START_DATE | END_DATE |
|-----------------------------|-----------------------------|
| September, 08 2015 00:00:00 | September, 08 2015 23:59:59 |
Use TO_DATE to convert string into DATE.
SQL> alter session set nls_date_format='mm/dd/yyyy hh24:mi:ss';
Session altered.
SQL> SELECT to_date('09/08/2015 00:00:00' ,'mm/dd/yyyy hh24:mi:ss') start_date,
2 to_date('09/08/2015 23:59:59' ,'mm/dd/yyyy hh24:mi:ss') end_date
3 FROM dual;
START_DATE END_DATE
------------------- -------------------
09/08/2015 00:00:00 09/08/2015 23:59:59
SQL>
You could also use the ANSI TIMESTAMP Literal.
SQL> SELECT TIMESTAMP '2015-08-09 00:00:00' start_date,
2 TIMESTAMP '2015-08-09 23:59:59' end_date
3 FROM dual;
START_DATE END_DATE
---------------------------- -------------------------------
09-AUG-15 12.00.00.000000000 09-AUG-15 11.59.59.000000000 PM
SQL>
Update OP wants the date literal to be dynamic.
SQL> SELECT TRUNC(SYSDATE) start_date,
2 TRUNC(SYSDATE) + 86399 / 86400 end_date
3 FROM dual;
START_DATE END_DATE
------------------- -------------------
09/08/2015 00:00:00 09/08/2015 23:59:59
Update 2 OP wants to know why the time part is hidden in the date.
SQL> alter session set nls_date_format='mm/dd/yyyy';
Session altered.
SQL> SELECT sysdate FROM DUAL;
SYSDATE
----------
09/08/2015
SQL> alter session set nls_date_format='mm/dd/yyyy hh24:mi:ss';
Session altered.
SQL> SELECT sysdate FROM DUAL;
SYSDATE
-------------------
09/08/2015 15:46:14
So, what happened above? The same SYSDATE returns two different values. The reason is that the DATE has both datetime elements, what you see depends on the display properties driven by your locale-specific NLS settings.
Use TO_CHAR to convert the date into string to display it in your
desired format.
Using values from table:
SELECT
DATE_VALUE,
TRUNC(DATE_VALUE) START_DATE,
TRUNC(DATE_VALUE) + 86399 / 86400 END_DATE
FROM
(SELECT SYSDATE - LEVEL + 1 DATE_VALUE FROM DUAL CONNECT BY LEVEL <= 10)