Round off time value to before midnight in oracle sql - oracle

I am writing a query that will allow me to workout dates for when particular jobs need to be completed.
The following is a list of my jobs and codes.
JOB_NO | CODES | RCVD_TIME
ABC1 | 1 | 07-JAN-17 09:44:07
DEF2 | 3 | 20-MAR-17 14:32:49
GHI3 | 3 | 27-MAR-17 10:00:03
JKL4 | 1 | 12-JAN-17 12:59:05
Now I have a few conditions in order to workout the end date/time for these jobs.
Code 1 - Add 1 day + anytime until 23:59:59
Code 3 - If rcvd time is before 1pm then job to be completed same day until 23:59:59, if rcvd time is 1pm or after then job to be completed before 1pm next day.
My simple query:
SELECT
JOB_NO, CODES, RCVD_TIME,
CASE
WHEN CODES = '1'
THEN RCVD_TIME + 1
ELSE RCVD_TIME
END AS TARGET
FROM...
This gives me (not sure how to write query for codes 3):
JOB_NO | CODES | RCVD_TIME | TARGET
ABC1 | 1 | 07-JAN-17 09:44:07 | 08-JAN-17 09:44:07
DEF2 | 3 | 20-MAR-17 14:32:49 | 20-MAR-17 14:32:49
GHI3 | 3 | 27-MAR-17 10:00:03 | 27-MAR-17 10:00:03
JKL4 | 1 | 12-JAN-17 12:59:05 | 13-JAN-17 12:59:05
This is what I would like:
JOB_NO | CODES | RCVD_TIME | TARGET
ABC1 | 1 | 07-JAN-17 09:44:07 | 08-JAN-17 23:59:59
DEF2 | 3 | 20-MAR-17 14:32:49 | 21-MAR-17 13:00:00
GHI3 | 3 | 27-MAR-17 10:00:03 | 27-MAR-17 23:59:59
JKL4 | 1 | 12-JAN-17 12:59:05 | 13-JAN-17 23:59:59
Would really appreciate if somebody could advise on how I can get my required answer.
Thanks in advance.
EDIT
bit frustrating but been told to add one more condition to consider working day of the week is between 1 and 6 so have to make sure the target time does not fall into a sunday (day 7). sorry guys for the double, just thought I would add it here in case it gets missed.

You can use a CASE statement in your SQL to handle your complex if-then logic.
-- TEST_DATA is not part of the solution
with test_data ( job_no, code, rcvd_time, target ) AS (
SELECT 'ABC1',1,to_date('07-JAN-17 09:44:07','DD-MON-YY HH24:MI:SS'),to_date('08-JAN-17 09:44:07','DD-MON-YY HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'DEF2',3,to_date('20-MAR-17 14:32:49','DD-MON-YY HH24:MI:SS'),to_date('20-MAR-17 14:32:49','DD-MON-YY HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'GHI3',3,to_date('27-MAR-17 10:00:03','DD-MON-YY HH24:MI:SS'),to_date('27-MAR-17 10:00:03','DD-MON-YY HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'JKL4',1,to_date('12-JAN-17 12:59:05','DD-MON-YY HH24:MI:SS'),to_date('13-JAN-17 12:59:05','DD-MON-YY HH24:MI:SS') FROM DUAL)
-- Actual solution begins here...
SELECT job_no, code, rcvd_time,
case
when rcvd_time - trunc(rcvd_time) < 13/24 and code = 1 then
trunc(rcvd_time) + (86399/86400) + 1
when rcvd_time - trunc(rcvd_time) < 13/24 and code = 3 then
trunc(rcvd_time) + (86399/86400)
when rcvd_time - trunc(rcvd_time) >= 13/24 then
trunc(rcvd_time) + 1 + (13/24)
end target
from test_data;

You can use interval literals (so you do not need to sprinkle magic numbers throughout your code):
CASE
WHEN code = 1
THEN TRUNC( rcvd_time ) + INTERVAL '2' DAY - INTERVAL '1' SECOND
WHEN code = 3 AND TO_CHAR( rcvd_time, 'HH24' ) < '13'
THEN TRUNC( rcvd_time ) + INTERVAL '1' DAY - INTERVAL '1' SECOND
WHEN code = 3 -- AND TO_CHAR( rcvd_time, 'HH24' ) >= '13'
THEN TRUNC( rcvd_time ) + INTERVAL '1' DAY + INTERVAL '13' HOUR
END
To compare the time you can use various different techniques:
TO_CHAR( rcvd_time, 'HH24' ) < '13'
EXTRACT( HOUR FROM CAST( rcvd_time AS TIMESTAMP ) ) < 13
rcvd_time < TRUNC( rcvd_time ) + INTERVAL '13' HOUR

In the solution below, I assume that the "special" handling applies only when CODES is 3, otherwise the TARGET is the end of the next day. (This allows CODES values other than 1 and 3... you can adapt if you have more CODES values and additional rules.)
Since all TARGET date-times are offset from trunc(rcvd_time), I use a CASE expression just to determine the offset.
select job_no, codes, rcvd_time,
trunc(rcvd_time) +
case codes when 3 then
case when extract (hour from cast (rcvd_time as timestamp)) < 13
then interval '23:59:59' hour to second
else interval '1 13:00:00' day to second
end
else interval '1 23:59:59' day to second
end as target
from your_table;
NOTE: Thanks to MT0 for showing the ways to extract hour from a date; I had forgotten that extract(hour from...) works only for timestamps. I edited my answer accordingly.

Try this
select
t.*
,case when codes=3
and to_date(to_char(rcvd_time,'HH24:MI:SS'),'HH24:MI:SS')
>=to_date('13:00:00','HH24:MI:SS')
then trunc(rcvd_time)+1+(13/24) -- add 1 day and then add 13 hours
when codes=1
then trunc(rcvd_time)+2-(1/(24*60*60)) --add 2 days and subtract 1 second
else
trunc(rcvd_time)+1-(1/(24*60*60)) -- add 1 day and subtract 1 second
end as target
from tbl t;
Working sample
with tbl (JOB_NO,CODES,RCVD_TIME) as (
select 'ABC1',1,to_date('07-JAN-17 09:44:07','DD-MON-YY HH24:MI:SS') from dual union all
select 'DEF2',3,to_date('20-MAR-17 14:32:49','DD-MON-YY HH24:MI:SS') from dual union all
select 'GHI3',3,to_date('27-MAR-17 10:00:03','DD-MON-YY HH24:MI:SS') from dual union all
select 'JKL4',1,to_date('12-JAN-17 12:59:05','DD-MON-YY HH24:MI:SS') from dual)
select
t.*
,case when codes=3
and to_date(to_char(rcvd_time,'HH24:MI:SS'),'HH24:MI:SS')
>=to_date('13:00:00','HH24:MI:SS')
then trunc(rcvd_time)+1+(13/24) -- add 1 day and then add 13 hours
when codes=1
then trunc(rcvd_time)+2-(1/(24*60*60)) --add 2 days and subtract 1 second
else
trunc(rcvd_timeJOB_NO,CODES,RCVD_TIME)+1-(1/(24*60*60)) -- add 1 day and subtract 1 second
end as target
from tbl t;
Output
JOB_NO CODES RCVD_TIME TARGET
ABC1 1 07-JAN-2017 09:44:07 08-JAN-2017 23:59:59
DEF2 3 20-MAR-2017 14:32:49 21-MAR-2017 13:00:00
GHI3 3 27-MAR-2017 10:00:03 27-MAR-2017 23:59:59
JKL4 1 12-JAN-2017 12:59:05 13-JAN-2017 23:59:59

Related

SQL PROBLEM: checking shipments from yesterday, today and tomorrow with exceptions on Monday and Friday

is it possible to create a data model in BI publisher (OTM) that looks at the shipments of:
yesterdays date
today date
tomorrow date
With the exception that on Friday instead of looking at tomorrow we look at next Monday and on Monday instead of looking at yesterday we look at last Friday.
The code that we have so far to look at the shipments for yesterday, today and tomorrow is, but we would like to put in the exception. Can anyone help us with this? thanks in advance for the help!!!
SELECT
S.SHIPMENT_GID SHIPMENT
FROM
SHIPMENT S,
SHIPMENT_INVOLVED_PARTY INV
WHERE
S.PERSPECTIVE = 'B'
AND
S.SHIPMENT_GID = INV.SHIPMENT_GID
AND
INV.INVOLVED_PARTY_QUAL_GID = 'BILL-TO'
AND
INV.INVOLVED_PARTY_CONTACT_GID = 'RSK.50144'
AND
(TRUNC(sysdate)+ 1 = TRUNC(S.START_TIME) OR TRUNC(sysdate) = TRUNC(S.START_TIME) OR TRUNC (sysdate) -1 = TRUNC(S.START_TIME))
You can use a case statement to look 5 days ahead if today is Thursday or Friday, and 3 days ahead the other days.
As there are not expected to be any rows the weekend this should always return 3 days if records.
with days as(
--create 31 days for a month
select sysdate -1 + level AS dt
from dual
connect by level <= 30
)
select
dt,
TO_CHAR( dt, 'D' ) "dayNo",
TO_CHAR( dt, 'day' )"dayName"
from days
where dt <
sysdate +
case when to_char(sysdate,'D')
in ('5','4')then 5
else 3 end
DT | dayNo | dayName
:-------- | :---- | :--------
15-APR-22 | 5 | friday
16-APR-22 | 6 | saturday
17-APR-22 | 7 | sunday
18-APR-22 | 1 | monday
19-APR-22 | 2 | tuesday
db<>fiddle here

Database Query with 3 Months Current Year and Previous Year

Hope am not over thinking this...
I am currently looking for any pointers on this. I have a table/view
|Cust_ID | CName | Inv_Date | Sales
|01A | A | 2/7/2006 | 20
|02A | B | 2/7/2006 | 10
|01A | A | 11/5/2005 | 15
Each customer has very many invoices on various dates
I would then like to extract each customer with sales for
Three (months) of the current year (e.g. 2006). This is 3 months prior to today's date.
Inv_Date >= add_months(trunc(sysdate, 'month'), -3)
and Inv_Date <= add_months(last_day(sysdate), -1)
The same three (3) months of the previous year (2005)?!
Would assume something like
|Cust_ID | CName | CY_Sum_Sales | PY_Sum_Sales
|01A | A | 20 | 15
|02A | B | 0 | 0
Assuming 'sysdate' is 1/8/2008. The following is my trial
select Customer_No, Customer_Name,
TO_CHAR(SUM(Sales_Colmn), 'fm999G999G999G999G999D0') as "TYear_Sale",
TO_CHAR((SELECT SUM(Sales_Colmn) from cust_table CL
where CL.invoice_date BETWEEN add_months(trunc(to_date('&From_Dt', 'DD/MM/YYYY'), 'mm'), -(3 + 12)) --15 months
and add_months(trunc(to_date('&To_Dt', 'DD/MM/YYYY'), 'mm'), -(1 + 12)) --13 months)
and Customer_No = CL.Customer_No), 'fm999G999G999G999G999D0') as "LYear_Sale"
from cust_table
where invoice_date BETWEEN add_months(trunc(to_date('&From_Dt', 'DD/MM/YYYY'), 'mm'), -3) --3 months
and add_months(trunc(to_date('&To_Dt', 'DD/MM/YYYY'), 'mm'), -1) --1 months)
GROUP BY Customer_No, Customer_Name
ORDER BY 3
But it takes too long to run. Must be doing something wrong
Functions (add_months, sysdate) you use suggest this is Oracle.
As you want to go back to same months previous year, you just have to add (OK, subtract) another 12 months. Something like this:
SQL> select trunc(sysdate) c_today,
2 trunc(sysdate, 'mm') c_trunc_today,
3 --
4 add_months(trunc(sysdate, 'mm'), -3) c_3_months_ago,
5 add_months(trunc(sysdate, 'mm'), -1) c_1_month_ago,
6 --
7 add_months(trunc(sysdate, 'mm'), -(3 + 12)) c_15_months_ago,
8 add_months(trunc(sysdate, 'mm'), -(1 + 12)) c_13_months_ago
9 from dual; ^^
this!
C_TODAY C_TRUNC_TO C_3_MONTHS C_1_MONTH_ C_15_MONTH C_13_MONTH
---------- ---------- ---------- ---------- ---------- ----------
03.11.2021 01.11.2021 01.08.2021 01.10.2021 01.08.2020 01.10.2020
--------------------- =====================
this year previous year, same months
SQL>

How to get the date difference between start date and end date in oracle as hours and minutes

I have a scenario in which for example,my start_date ='12-SEP-2018 00:01:00' and End_date ='13-SEP-2018 14:55:00' . The difference between the 2 dates must be found out in Hours and minutes like'12:20'. This must be achieved in oracle database. I tried using the following logic :
SELECT
24 * (to_date('2009-07-07 22:00', 'YYYY-MM-DD hh24:mi') - to_date(
'2009-07-07 19:30', 'YYYY-MM-DD hh24:mi')) diff_hours
FROM
dual;
I was able to get the hour difference but unable to get minutes along with it.
CREATE TABLE table_name ( start_date DATE, end_date DATE );
INSERT INTO table_name VALUES ( TIMESTAMP '2009-07-07 19:30:00', TIMESTAMP '2009-07-07 22:00:00' );
Then you can subtract one from the other and cast it to a DAY TO SECOND interval and then just EXTRACT the component parts of the time:
SELECT EXTRACT( DAY FROM difference ) AS days,
EXTRACT( HOUR FROM difference ) AS hours,
EXTRACT( MINUTE FROM difference ) AS minutes,
EXTRACT( SECOND FROM difference ) AS seconds
FROM (
SELECT ( end_date - start_date ) DAY TO SECOND AS difference
FROM table_name
);
Outputs:
DAYS | HOURS | MINUTES | SECONDS
---: | ----: | ------: | ------:
0 | 2 | 30 | 0
or you can use arithmetic to calculate the values:
SELECT TRUNC( 24 * ( end_date - start_date ) ) AS hours,
TRUNC( MOD( 24 * 60 * ( end_date - start_date ), 60 ) ) AS minutes,
ROUND( MOD( 24 * 60 * 60 * ( end_date - start_date ), 60 ) ) AS seconds
FROM table_name;
which outputs:
HOURS | MINUTES | SECONDS
----: | ------: | ------:
2 | 30 | 0
db<>fiddle here
Since you want a string value, an alternative based on your query attempt is to add the difference between your two date values (which is a numeric value, the number of days between them, including fractional days) to an arbitrary fixed date; and then convert the result of that to a string:
SELECT to_char(date '0001-01-01'
+ (to_date('2009-07-07 22:00', 'YYYY-MM-DD hh24:mi') - to_date( '2009-07-07 19:30', 'YYYY-MM-DD hh24:mi')),
'HH24:MI') as diff
FROM dual;
DIFF
-----
02:30
If the difference can exceed 24 hours then you need to decide how to report that; if you want to include days as a separate figure then you can still use this approach, but need to subtract one (if your fixed date is the first) from the difference before formatting as a string:
SELECT to_char(date '0001-01-01'
+ (to_date('2009-07-08 22:00', 'YYYY-MM-DD hh24:mi') - to_date( '2009-07-07 19:30', 'YYYY-MM-DD hh24:mi'))
- 1,
'DDD:HH24:MI') as diff
FROM dual;
DIFF
---------
001:02:30
If you want the 'hours' value to be higher instead - e.g. '26:30' in this example - then it gets rather more complicated; I see #MTO has added the 'arithmetic' approach already so I won't repeat that. But then might be better off going down the extract() route (which you should consider anyway as it's more flexible and elegant...)

How to split record in multiple records from start/end date record

I'm trying to split record to multiple record from start/end date in Oracle
I have data like this
MachineID | start date | end date | running time |
WC01 | 2019/09/05 07:00 | 2019/09/07 09:00 | 26:00 |
and I want to split record to each day from 08:00 to 08:00
MachineID | running date | running time |
WC01 | 2019/09/05 | 1:00 |
WC01 | 2019/09/06 | 24:00 |
WC01 | 2019/09/07 | 1:00 |
Thank you for your help!
We can handle this via the help from a calendar table which contains all dates you expect to appear in your data set, along with a separate record for each minute:
WITH dates AS (
SELECT TIMESTAMP '2019-09-05 00:00:00' + NUMTODSINTERVAL(rownum, 'MINUTE') AS dt
FROM dual
CONNECT BY level <= 5000
)
SELECT
m.MachineID,
TRUNC(d.dt) AS running_date,
COUNT(t.MachineID) / 60 AS running_hours
FROM dates d
CROSS JOIN (SELECT DISTINCT MachineID FROM yourTable) m
LEFT JOIN yourTable t
ON d.dt >= t.start_date AND d.dt < t.end_date
WHERE
TO_CHAR(d.dt, 'HH24') >= '08' AND TO_CHAR(d.dt, 'HH24') < '21'
GROUP BY
m.MachineID,
TRUNC(d.dt)
ORDER BY
TRUNC(d.dt);
Demo
You can try below query:
SELECT
MACHINEID,
RUNNING_DATE,
DECODE(RUNNING_DATE, TRUNC(START_DATE), CASE
WHEN DIFF_START < 0 THEN 0
WHEN DIFF_START > 12 THEN 12
ELSE DIFF_START
END, TRUNC(END_DATE), CASE
WHEN DIFF_END < 0 THEN 0
WHEN DIFF_END > 12 THEN 12
ELSE DIFF_END
END, 24) AS RUNNING_HOURS
FROM
(
SELECT
MACHINEID,
RUNNING_DATE,
ROUND(24 *((TRUNC(START_DATE + LVL - 1) + 8 / 24) - START_DATE)) AS DIFF_START,
ROUND(24 *(END_DATE -(TRUNC(START_DATE + LVL - 1) + 8 / 24))) AS DIFF_END,
START_DATE,
END_DATE
FROM
(
SELECT
DISTINCT MACHINEID,
LEVEL AS LVL,
START_DATE,
END_DATE,
TRUNC(START_DATE + LEVEL - 1) AS RUNNING_DATE
FROM
YOURTABLE
CONNECT BY
LEVEL <= TRUNC(END_DATE) - TRUNC(START_DATE) + 1
)
);
db<>fiddle demo
Change the logic wherever it is not meeting your requirement. I have created the query taking sample data and expected output into consideration.
Cheers!!

Want to ROUND the Data according to DAY difference

Query :
select
TO_CHAR((to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS')+ (level-1)),'DD-MM-YYYY'),
TO_CHAR(to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS') + level,'DD-MM-YYYY') ,
to_number(regexp_substr(IP_PLAN_CONSUMPTION, '^\d+'))/(TO_DATE(IP_END_DATE, 'DD-MM-YYYY HH24:MI:SS') - TO_DATE(IP_START_DATE, 'DD-MM-YYYY HH24:MI:SS')) || regexp_substr(IP_PLAN_CONSUMPTION, '[A-Z]') as IP_PLAN_CONSUMPTION
FROM
dual
CONNECT BY
level <= to_date(IP_END_DATE,'DD-MM-YYYY HH24:MI:SS')-to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS')+1;
-> Data in Query :
select
TO_CHAR((to_date('16-07-2018 11:02','DD-MM-YYYY HH24:MI:SS')+ (level-1)),'DD-MM-YYYY'),
TO_CHAR(to_date('16-07-2018 11:02','DD-MM-YYYY HH24:MI:SS') + level,'DD-MM-YYYY'),
to_number(regexp_substr('4000 T', '^\d+'))/(TO_DATE('18-07-2018 00:00', 'DD-MM-YYYY HH24:MI:SS') - TO_DATE('16-07-2018 11:02', 'DD-MM-YYYY HH24:MI:SS')) || regexp_substr('4000 T', '[A-Z]') as IP_PLAN_CONSUMPTION
FROM
dual
CONNECT BY
level <= to_date('18-07-2018 00:00','DD-MM-YYYY HH24:MI:SS')-to_date('16-07-2018 11:02','DD-MM-YYYY HH24:MI:SS')+1;
Output will Be :
But its should be 2000 T
Not : If Start Date: 16-07-2018 00:00 & End Date : 19-07-2018 00:00 then Day Difference is 3 Days & Consumption is 4000 T then Inserted Consumption Should be 1333.333333333333 T ~ 1334 T in each date.
If you are storing dates, you should store them in your table as the DATE data type (and not as strings).
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE your_table( id, ip_start_date, ip_end_date, ip_plan_consumption ) AS
SELECT 1,
DATE '2018-07-16' + INTERVAL '11:02' HOUR TO MINUTE,
DATE '2018-07-18' + INTERVAL '00:00' HOUR TO MINUTE,
'4000 T'
FROM DUAL
UNION ALL
SELECT 2,
DATE '2018-07-16' + INTERVAL '11:02' HOUR TO MINUTE,
DATE '2018-07-16' + INTERVAL '23:08' HOUR TO MINUTE,
'3000 T'
FROM DUAL
UNION ALL
SELECT 3,
DATE '2018-07-10' + INTERVAL '00:00' HOUR TO MINUTE,
DATE '2018-07-13' + INTERVAL '23:59' HOUR TO MINUTE,
'15000 U'
FROM DUAL
;
Query 1:
WITH data ( id, start_dt, end_dt, consumption, units ) AS (
SELECT id,
TRUNC( IP_START_DATE ),
GREATEST( TRUNC( IP_START_DATE ) + 1, TRUNC( IP_END_DATE ) ),
TO_NUMBER( REGEXP_SUBSTR( IP_PLAN_CONSUMPTION, '^\d+' ) ),
REGEXP_SUBSTR( IP_PLAN_CONSUMPTION, '\S+$' )
FROM your_table
)
SELECT id,
t.column_value AS start_dt,
t.column_value + 1 AS end_dt,
consumption / ( end_dt - start_dt ) || units AS IP_PLAN_CONSUMPTION
FROM data d
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT d.start_dt + LEVEL - 1
FROM DUAL
CONNECT BY d.start_dt + LEVEL - 1 < d.end_dt
)
AS SYS.ODCIDATELIST
)
) t
Results:
| ID | START_DT | END_DT | IP_PLAN_CONSUMPTION |
|----|----------------------|----------------------|---------------------|
| 1 | 2018-07-16T00:00:00Z | 2018-07-17T00:00:00Z | 2000T |
| 1 | 2018-07-17T00:00:00Z | 2018-07-18T00:00:00Z | 2000T |
| 2 | 2018-07-16T00:00:00Z | 2018-07-17T00:00:00Z | 3000T |
| 3 | 2018-07-10T00:00:00Z | 2018-07-11T00:00:00Z | 5000U |
| 3 | 2018-07-11T00:00:00Z | 2018-07-12T00:00:00Z | 5000U |
| 3 | 2018-07-12T00:00:00Z | 2018-07-13T00:00:00Z | 5000U |

Resources