Decoding and Converting VARCHAR Time in Oracle - oracle

I need to query particular column in DB w/c is described as VARCHAR that I need to convert into Time Format (HH:MM) and then decode to its range value, to explain further... The column stores data when (time) a particular deal was made w/in the day and instead of returning the exact time it should return the time range it was done.
Col_1 is VARCHAR2(6)
select col_2, col_1 from table
Col_2 || Col_1
A || 9:56
B || 10:03
C || 21:53
My desired Output would be
Col_2 || Col_1
A || (09:00 - 10:00)
B || (10:00 - 11:00)
C || (2100 - 2200)
Really appreciate all your comments, as I'm stuck playing with this part for a couple of days now and its giving me nightmares, sorry I'm new to DB Oracle SQL stuff and still learning. :)

Like this?
In your output, 2100 - 2200 is not consistent with the rest; I ignored it.
What should happen when the input is like 10:00 even? That is in both 9:00-10:00 and in 10:00-11:00; I assumed you want the latter.
Finally, is 00:00 ok, or do you want that to be 24:00 instead (if it is at the end) - see the second example in my output.
with input_strings ( str ) as (
select '9:49' from dual union all
select '23:00' from dual
),
prep ( dt ) as (
select trunc(to_date(str, 'hh24:mi'),'hh') from input_strings
)
select to_char(dt, 'hh24:mi') || ' - ' || to_char(dt + 1/24, 'hh24:mi') as range
from prep;
RANGE
-------------
09:00 - 10:00
23:00 - 00:00

If you do to_date(col_1, 'HH24:MI') you'll get that tie on the first day of the month, as that what Oracle defaults to with no date specified. You can then truncate that to the hour, zeroing the minutes:
with t (col_2, col_1) as (
select 'A', '9:56' from dual
union all select 'B', '10:03' from dual
union all select 'C', '21:53' from dual
)
select col_2,
to_char(to_date(col_1, 'HH24:MI'), 'YYYY-MM-DD HH24:MI') as full_date,
to_char(trunc(to_date(col_1, 'HH24:MI'), 'HH24'), 'YYYY-MM-DD HH24:MI') as truncated
from t;
C FULL_DATE TRUNCATED
- ---------------- ----------------
A 2016-09-01 09:56 2016-09-01 09:00
B 2016-09-01 10:03 2016-09-01 10:00
C 2016-09-01 21:53 2016-09-01 21:00
That gives you the lower bound of your range. You can add an hour to the calculated date to get the upper limit, and then convert both back to string, and concatenate them together:
with t (col_2, col_1) as (
select 'A', '9:56' from dual
union all select 'B', '10:03' from dual
union all select 'C', '21:53' from dual
)
select col_2,
to_char(trunc(to_date(col_1, 'HH24:MI'), 'HH24'), 'HH24:MI') ||' - '||
to_char(trunc(to_date(col_1, 'HH24:MI') + 1/24, 'HH24'), 'HH24:MI')
from t;
C TO_CHAR(TRUNC
- -------------
A 09:00 - 10:00
B 10:00 - 11:00
C 21:00 - 22:00
You could also try to extract the hour value from your string and manipulate that, but then you have to manually deal with times between 23:00 and 00:00.
A third option is to use an interval instead of a date:
to_char(extract(hour from to_dsinterval('0 ' || col_1 || ':00')), 'FM00') ||':00 - '||
to_char(extract(hour from to_dsinterval('0 ' || col_1 || ':00')) + 1, 'FM00') ||':00'
but again this would show a time of say 23:15 as 23:00 - 24:00, so you'd need extra handling to show the upper bound as 00:00 in that case.

Related

How to get last workday before holiday in Oracle [duplicate]

This question already has answers here:
How to get the previous working day from Oracle?
(4 answers)
Closed 1 year ago.
need help for some oracle stuff ..
I need to get Day-1 from sysdate, holiday and weekend will be excluded .
And for holiday, we need to get the range to get the last workday before holiday.
The start date and end date will coming from my holiday table.
ex :
Holiday Table
HolidayName
Start_date
End_Date
holiday1
5th Aug'21
6th Aug'21
condition :
this query run on 9th Aug 2021
expected result :
4th Aug'21
I've tried some query and function but I just can't get what I need.
Thanks a lot for help!
Here's one way to do it.
select max(d) as last_workday
from (select trunc(sysdate)-level as d from dual connect by level < 30) prior_month
where to_char(d, 'DY') not in ('SAT','SUN')
and not exists (select holidayname from holiday_table
where prior_month.d between start_date and end_date)
;
Without seeing your Holiday table, it's hard to say how many days back you would need to look to find the last workday. If you have a holiday that lasts for more than 30 days, you'll need to change the 30 to a larger number.
You can use a simple case expression to determine what day of the week the start of your holiday is, then subtract a number of days based on that.
WITH
holiday (holidayname, start_date, end_date)
AS
(SELECT 'holiday1', DATE '2021-8-5', DATE '2021-8-6' FROM DUAL
UNION ALL
SELECT 'Christmas', DATE '2021-12-25', DATE '2021-12-26' FROM DUAL
UNION ALL
SELECT 'July 4th', DATE '2021-7-4', DATE '2021-7-5' FROM DUAL)
SELECT holidayname,
start_date,
end_date,
start_date - CASE TO_CHAR (start_date, 'Dy') WHEN 'Mon' THEN 3 WHEN 'Sun' THEN 2 ELSE 1 END AS prior_business_day
FROM holiday;
HOLIDAYNAME START_DATE END_DATE PRIOR_BUSINESS_DAY
______________ _____________ ____________ _____________________
holiday1 05-AUG-21 06-AUG-21 04-AUG-21
Christmas 25-DEC-21 26-DEC-21 24-DEC-21
July 4th 04-JUL-21 05-JUL-21 02-JUL-21
You can use a recursive sub-query factoring clause from this answer:
WITH start_date (dt) AS (
SELECT DATE '2021-05-02' FROM DUAL
),
days ( dt, day, found ) AS (
SELECT dt,
TRUNC(dt) - TRUNC(dt, 'IW'),
0
FROM start_date
UNION ALL
SELECT dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END,
CASE WHEN day IN (0, 6, 5) THEN 4 ELSE day - 1 END,
CASE WHEN h.start_date IS NULL THEN 1 ELSE 0 END
FROM days d
LEFT OUTER JOIN holidays h
ON ( dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END
BETWEEN h.start_date AND h.end_date )
WHERE found = 0
)
SELECT dt
FROM days
WHERE found = 1;
Which, for the sample data:
CREATE TABLE holidays (HolidayName, Start_date, End_Date) AS
SELECT 'holiday1', DATE '2021-08-05', DATE '2021-08-06' FROM DUAL;
Outputs:
DT
2021-08-04 00:00:00
db<>fiddle here
Don't know if it's very efficient. Did it just for fun
create table holidays (
holiday_name varchar2(100) primary key,
start_date date not null,
end_date date not null
)
/
Table created
insert into holidays (holiday_name, start_date, end_date)
values ('holiday1', date '2021-08-05', date '2021-08-06');
1 row inserted
with days_before(day, wrk_day) as
(select trunc(sysdate - 1) d,
case
when h.holiday_name is not null then 0
when to_char(trunc(sysdate - 1), 'D') in ('6', '7') then 0
else 1
end work_day
from dual
left join holidays h
on trunc(sysdate - 1) between h.start_date and h.end_date
union all
select db.day - 1,
case
when h.holiday_name is not null then 0
when to_char(db.day - 1, 'D') in ('6', '7') then 0
else 1
end work_day
from days_before db
left join holidays h
on db.day - 1 between h.start_date and h.end_date
where db.wrk_day = 0) search depth first by day set order_no
select day from days_before where wrk_day = 1;
DAY
-----------
04.08.2021

How to give a client's state by time

Table t_customer_statistics
trx_date - transaction date
cuid - id person(divide prospect and client)
lifecycle_status - this column must be filled
I need to give status to a client based on his condition
acquired - this month was the very first transaction
existing - there was a transaction last month
reactivated - there was no transaction last month
sleeping - there has been no transaction for the last 90 days (there have been no subsequent ones since the last transaction, more than 90 days)
I roughly made a code like this
UPDATE t_customer_statistics
SET Lifecycle_status =
case
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm'), 'mm.yyyy') then 'acquired'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm') - 1, 'mm.yyyy') then 'existing'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm') - 40, 'mm.yyyy') then 'reactivated'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') <to_char (trunc (sysdate, 'mm') - 90, 'mm.yyyy') then 'sleeping'
end
but when they gave me an example, if the client made the first transaction and then fell asleep, then he has two states in the end, and sleeping must be separated so that there is a separate
PS. must be considered by transaction from the first and last
You could use a MERGE statement like this:
MERGE INTO clients dst
USING (
SELECT rowid rid,
LEAD(dt, 1) OVER (PARTITION BY id ORDER BY dt DESC) AS prev_dt,
LAG(dt, 1) OVER (PARTITION BY id ORDER BY dt DESC) AS next_dt
FROM clients
) src
ON ( dst.ROWID = src.rid )
WHEN MATCHED THEN
UPDATE
SET status = CASE
WHEN prev_dt IS NULL
THEN 'acquired'
WHEN MONTHS_BETWEEN(TRUNC(dst.dt, 'MM'), TRUNC(src.prev_dt)) <= 1
THEN 'existing'
ELSE 'reactivated'
END
||
CASE
WHEN COALESCE(src.next_dt, SYSDATE) >= dst.dt + INTERVAL '90' DAY
THEN ', sleeping'
END;
Which, for the sample data:
CREATE TABLE clients (id, dt, status ) AS
SELECT 1, DATE '2020-01-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-02-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-03-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-05-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-09-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-10-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-10-01' + INTERVAL '91' DAY, CAST( NULL AS VARCHAR2(20) ) FROM DUAL;
Then the result of the MERGE is:
ID
DT
STATUS
1
01-JAN-20
acquired
1
01-FEB-20
existing
1
01-MAR-20
existing
1
01-MAY-20
reactivated, sleeping
1
01-SEP-20
reactivated
1
01-OCT-20
existing, sleeping
1
31-DEC-20
reactivated, sleeping
db<>fiddle here

Time difference of hours and minutes [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I am trying to find out the time difference in hours and minutes but getting error, Please help to find out the query , For example I need answer of below in oracle
16:55- 14:00 = 2:55
Convert the times to a DATE or TIMESTAMP and then subtract one from the other to get an INTERVAL DAY TO SECOND data type; then use EXTRACT to get the hour and minute components and format them:
SELECT start_time,
end_time,
TO_CHAR( EXTRACT( HOUR FROM difference ), 'FM00' )
|| ':'
|| TO_CHAR( ABS( EXTRACT( MINUTE FROM DIFFERENCE ) ), 'FM00' )
AS difference
FROM (
SELECT start_time,
end_time,
( TO_DATE( end_time, 'HH24:MI' ) - TO_DATE( start_time, 'HH24:MI' ) ) DAY TO SECOND
AS difference
FROM test_data
)
Which, for the test data:
CREATE TABLE test_data ( start_time, end_time ) AS
SELECT '14:00', '16:55' FROM DUAL UNION ALL
SELECT '14:50', '15:23' FROM DUAL UNION ALL
SELECT '16:00', '14:00' FROM DUAL UNION ALL
SELECT '16:00', '14:20' FROM DUAL UNION ALL
SELECT '00:00', '23:59' FROM DUAL;
Outputs:
START_TIME | END_TIME | DIFFERENCE
:--------- | :------- | :---------
14:00 | 16:55 | 02:55
14:50 | 15:23 | 00:33
16:00 | 14:00 | -02:00
16:00 | 14:20 | -01:40
00:00 | 23:59 | 23:59
db<>fiddle here
Please see below
SELECT
substr(
to_char(TO_TIMESTAMP ('16:55', 'HH24:MI') -
TO_TIMESTAMP ('14:00', 'HH24:MI'),
'HH24:MI')
,12
,5) "Hours"
FROM
DUAL;
SELECT
to_number(substr(
to_char(TO_TIMESTAMP ('16:55', 'HH24:MI') -
TO_TIMESTAMP ('14:00', 'HH24:MI'),
'HH24:MI')
,12
,2))
+
to_number(substr(
to_char(TO_TIMESTAMP ('16:55', 'HH24:MI') -
TO_TIMESTAMP ('14:00', 'HH24:MI'),
'HH24:MI')
,15
,2))/60 "Decimal Hours"
FROM
DUAL;
Output
Hours
-----
02:55
Decimal Hours
-------------
2.91666667
Assuming that your two times are strings, you can use a query like the one below to calculate the difference between the times. By casting time strings to dates, you can then subtract them from each other and convert the result into the format that you want.
Query
WITH
times (start_time, end_time)
AS
(SELECT '14:00', '16:55' FROM DUAL
UNION ALL
SELECT '5:00', '3:00' FROM DUAL
UNION ALL
SELECT '7:00', '23:45' FROM DUAL
UNION ALL
SELECT '15:00', '03:20' FROM DUAL)
SELECT start_time,
end_time,
TRUNC (
( TO_DATE ('1-JAN-2000 ' || end_time, 'DD-MON-YYYY HH24:MI')
- TO_DATE ('1-JAN-2000 ' || start_time, 'DD-MON-YYYY HH24:MI'))
* 24)
|| ':'
|| RPAD (
ABS (
TRUNC (
( ( ( TO_DATE ('1-JAN-2000 ' || end_time, 'DD-MON-YYYY HH24:MI')
- TO_DATE ('1-JAN-2000 ' || start_time, 'DD-MON-YYYY HH24:MI'))
* 24)
- TRUNC (
( TO_DATE ('1-JAN-2000 ' || end_time, 'DD-MON-YYYY HH24:MI')
- TO_DATE ('1-JAN-2000 ' || start_time, 'DD-MON-YYYY HH24:MI'))
* 24))
* 60)),
2,
'0') AS difference
FROM times;
Result
START_TIME END_TIME DIFFERENCE
_____________ ___________ _____________
14:00 16:55 2:55
5:00 3:00 -2:00
7:00 23:45 16:45
15:00 03:20 -11:40

How to calculate the difference of HH:MM:SS between two dates in oracle sql?

I have a table abc as:
-- start_time |end_time | total_time_taken
-- 27.05.2020 00:52:48 |27.05.2020 02:08:33 |
I want to set the value of total_time_taken as the difference of end_time-start_time. in the format "HH:MM:SS".I searched the similar topic but didnot find the exact answer.
My expected output is like : 01:44:12 (HH:MM:SS)
So,i tried :
SELECT To_Char(end_time,'HH24:MM:SS'),To_Char(start_time,'HH24:MM:SS'),
To_Char(end_time,'HH24:MM:SS')-To_Char(start_time,'HH24:MM:SS') FROM abc;
The datatypes of start_time,end_time,total_time_taken is DATE.Please help me to find the solution.
If you cast those dates as timestamps, you can easily subtract them and see relatively nice result:
SQL> with test (st, et) as
2 (select to_date('27.05.2020 00:52:48', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('27.05.2020 02:08:33', 'dd.mm.yyyy hh24:mi:ss')
4 from dual
5 )
6 select cast(et as timestamp) - cast(st as timestamp) diff
7 from test;
DIFF
--------------------------------------------------------------------------
+000000000 01:15:45.000000
SQL>
If you want to format it as you wanted (note that mm format mask is for months; mi is for minutes), then you could do some extracting - again from timestamp (won't work for date):
SQL> with test (st, et) as
2 (select to_date('27.05.2020 00:52:48', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('27.05.2020 02:08:33', 'dd.mm.yyyy hh24:mi:ss')
4 from dual
5 ),
6 diff as
7 (select cast(et as timestamp) - cast(st as timestamp) diff
8 from test
9 )
10 select extract(hour from diff) ||':'||
11 extract(minute from diff) ||':'||
12 extract(second from diff) diff
13 from diff;
DIFF
-------------------------------------------------------------------------
1:15:45
SQL>
You can further make it pretty (e.g. two digits for hours, using LPAD function). Or, you can even write your own function which will actually work on difference of DATE datatype values, do some calculations (using trunc function, subtractions, whatnot), but the above looks pretty elegant if compared to a home-made function.
The answer by Littlefoot is perfectly fine. This answer is just to show there is more than one way to get the result.
First, we can subtract one date from another and get the difference in days, then convert that difference to an interval.
with test (st, et) as
(select to_date('27.05.2020 00:52:48', 'dd.mm.yyyy hh24:mi:ss'),
to_date('27.05.2020 02:08:33', 'dd.mm.yyyy hh24:mi:ss')
from dual
)
select numtodsinterval(et-st, 'day') diff
from test;
Then, since we can't control interval formatting directly, we can add DIFF to an arbitrary date and then use built-in date formatting.
with test (st, et) as
(select to_date('27.05.2020 00:52:48', 'dd.mm.yyyy hh24:mi:ss'),
to_date('27.05.2020 02:08:33', 'dd.mm.yyyy hh24:mi:ss')
from dual
)
select to_char(date '1-1-1' + numtodsinterval(et-st, 'day'), 'hh24:mi:ss') diff
from test;
DIFF
--------
01:15:45

Returning times between other times

I have two tables AVAIL and AVAIL_TIMES. AVAIL contains avail_id, avail_date, open_flag. AVAIL_TIMES contains avail_times_id, Avail_id, Start_Time, End_time. All date and time fields are typed as DATE
If a date is flagged in the avail open_flag column it means that the facility is open for that date, but the times it is open is listed in avail_times. There can be multiple time ranges for a particular day.
I need to return a list of times it is not open for that day.
For Example (one day of many)
Open times for day:
Start_time: 08:00 End_time 10:00
Start_time: 12:00 End_time 14:00
Start_time: 15:00 End_time 17:00
I want it to return something like:
00:00 - 07:59
10:01 - 11:59
14:01 - 14:59
17:01 - 23:59
I think I would be able to work through this with a temporary table and some plsql logic, but ideally this would be a pure sql solution.
I am not exactly sure how you want to input the date of interest (I used a bind variable, passed in as a string - but that may not be the right way for you, perhaps you want to join to your other table, etc.) - or the exact output you want. In any case, the query below demonstrates the "core" of the code you need to achieve this kind of output from the inputs.
alter session set nls_date_format='mm/dd/yyyy hh24:mi';
with
avail_times ( start_time, end_time ) as (
select to_date('06/20/2017 08:00'), to_date('06/20/2017 10:00') from dual union all
select to_date('06/20/2017 12:00'), to_date('06/20/2017 14:00') from dual union all
select to_date('06/20/2017 15:00'), to_date('06/20/2017 17:00') from dual
)
select trunc(min(start_time)) as start_time, min(start_time) as end_time
from avail_times
where trunc(start_time) = to_date(:input_date, 'mm/dd/yyyy')
union all
select end_time,
lead(start_time, 1, trunc(start_time) + 1) over (order by start_time)
from avail_times
where trunc(end_time) = trunc(start_time)
order by start_time
;
START_TIME END_TIME
---------------- ----------------
06/20/2017 00:00 06/20/2017 08:00
06/20/2017 10:00 06/20/2017 12:00
06/20/2017 14:00 06/20/2017 15:00
06/20/2017 17:00 06/21/2017 00:00
Another Approach. Hope this helps.
SELECT ID,
START_TME,
END_TM,
DIFF_TM
FROM
--Not part of SQL just to simulate the table data
(WITH TMP AS
(SELECT 1 ID,
TO_DATE('06/27/2017 00:00','mm/dd/yyyy hh24:mi') START_TME,
TO_DATE('06/27/2017 08:00','mm/dd/yyyy hh24:mi') END_TM
FROM DUAL
UNION ALL
SELECT 1 ID,
TO_DATE('06/27/2017 10:00','mm/dd/yyyy hh24:mi') START_TME,
TO_DATE('06/27/2017 15:00','mm/dd/yyyy hh24:mi') END_TM
FROM DUAL
UNION ALL
SELECT 1 ID,
TO_DATE('06/27/2017 16:00','mm/dd/yyyy hh24:mi') START_TME,
TO_DATE('06/27/2017 17:00','mm/dd/yyyy hh24:mi') END_TM
FROM DUAL
UNION ALL
SELECT 1 id,
to_date('06/27/2017 17:00','mm/dd/yyyy hh24:mi') start_tme,
TO_DATE('06/27/2017 18:00','mm/dd/yyyy hh24:mi') END_TM
FROM DUAL
)
--SQL start from here
SELECT TMP.*,
LEAD(START_TME) OVER(PARTITION BY ID ORDER BY 1 DESC) next_st_tm,
LEAD(END_TM) OVER(PARTITION BY ID ORDER BY 1 DESC) NEXT_EN_TM,
EXTRACT( HOUR FROM TO_TIMESTAMP(LEAD(START_TME) OVER(PARTITION BY ID ORDER BY 1 DESC),'MM/DD/YYYY HH24:MI'))- EXTRACT(HOUR FROM TO_TIMESTAMP(end_tm,'MM/DD/YYYY HH24:MI')) DIFF_TM
FROM TMP
ORDER BY 1 ,
2
)
WHERE DIFF_TM <> 0;

Resources