Oracle - Condition on a datetime - sql

I am working on this DB that contains informations on shop orders in a plant. I am using this query that retrieves order data, using the closing date as condition. It extracts order that are closed in a time range of two weeks.
select order.id, order.end_date, material.id, material.description
from plant.order inner join plant.material
on order.material = material.id
where order.end_date <= to_date('26/10/2018 06:00:00', 'DD/MM/YYYY HH24:MI:SS')
where order.end_date <= to_date('12/10/2018 06:00:00', 'DD/MM/YYYY HH24:MI:SS')
Since the workday is divided in shifts (for example, the workday 26th October starts from the 26/10 6:00 and ends in 27/10 6:00 to cover the 24 hours), I need to manage this thing.
Is it possible to use a condition that checks if the hour is < 6, and if the condition is true, it writes the correct value in another column?
Or is there a better way to manage it?
order.id order.end_date material.id material.description
-------- -------------- ----------- --------------------
1 26/10/2018 05:00 1 Lorem Ipsum
2 26/10/2018 07:00 2 Lorem Ipsum
order.workday (new column)
-------------
25/10/2018
26/10/2018
In this case the order id 1 should result of date 25/10/2018, while order id 2 should result of date 26/10/2018.
I hope that I have been clear.
Thanks

You could simply use TRUNC(order.end_date - 6/24)
WITH t AS (
SELECT TO_DATE('26/10/2018 05:00' , 'dd/mm/yyyy hh24:mi') AS end_date FROM dual
UNION ALL SELECT TO_DATE('26/10/2018 07:00' , 'dd/mm/yyyy hh24:mi') AS end_date FROM dual)
SELECT end_date, TRUNC(end_date - 6/24)
from t;
+----------------------------------------+
|END_DATE |TRUNC(END_DATE-6/24)|
+----------------------------------------+
|26.10.2018 05:00:00|25.10.2018 |
|26.10.2018 07:00:00|26.10.2018 |
+----------------------------------------+

Try below using CASE WHEN
select
order.id,
order.end_date,
material.id,
material.description,
case when TO_CHAR (order.end_date, 'HH24:MI:SS')<'06:00:00' then order.end_date- interval '1' day else order.end_date end as workday
from plant.order inner join plant.material,
on order.material = material.id
where order.end_date <= to_date('26/10/2018 06:00:00', 'DD/MM/YYYY HH24:MI:SS')
where order.end_date <= to_date('12/10/2018 06:00:00', 'DD/MM/YYYY HH24:MI:SS')

Related

Column based on time range in Oracle

I have a sales table with created datetime, my business hours are from 9 AM to 2 AM in the night on the following day. I am trying to convert the dates into my business date.
01/08/22 09:39:12.000000000 AM +04:00
Lets say I have a sale at 1 AM, this sale has to be considered in the previous day.
Any function that can help me solve this issue would be appreciated
It might be a bit of an overkill, but you could just use EXTRACT:
WITH dat AS
(
SELECT to_date('01/08/22 09:39:12','DD/MM/YY HH24:MI:SS') AS t_stmp FROM dual UNION ALL
SELECT to_date('02/08/22 01:03:15','DD/MM/YY HH24:MI:SS') FROM dual UNION ALL
SELECT to_date('02/08/22 08:27:33','DD/MM/YY HH24:MI:SS') FROM dual UNION ALL
SELECT to_date('02/08/22 14:11:51','DD/MM/YY HH24:MI:SS') FROM dual UNION ALL
SELECT to_date('02/08/22 02:01:15','DD/MM/YY HH24:MI:SS') FROM dual
)
SELECT CASE WHEN EXTRACT(HOUR FROM CAST(t_stmp AS TIMESTAMP)) BETWEEN 2 AND 8 THEN -1
ELSE 0
END + TRUNC(t_stmp,'DD') AS business_date
FROM dat;
business_date
01.08.2022
02.08.2022
01.08.2022
02.08.2022
01.08.2022
It looks like you just need to make a 2 hour shift to get your sales in the right date. You can add or substract hours from DATE/DATETIME/TIMESTAMP data type. If your column is TIMESTAMP then it would be like this:
-- when selecting data for date of sales
SELECT TRUNC(your_column_name - INTERVAL '2' HOUR, 'dd') "SALE_DATE"
-- And/Or
WHERE TRUNC(your_column_name - INTERVAL '2' HOUR, 'dd') = :DATE_OF_SALES
-- TRUNC function always returns DATE datatype
--
-- The opposite conversion would be
CAST(your_datetime_column + INTERVAL '2' HOUR as TIMESTAMP) ...
Here is the small sample with result:
SELECT
to_char(SYSDATE, 'dd.mm.yyyy hh24:mi:ss') "DATETIME",
to_char(SYSDATE - INTERVAL '2' HOUR, 'dd.mm.yyyy hh24:mi:ss') "DATETIME_MINUS_2H",
to_char(SYSDATE + INTERVAL '2' HOUR, 'dd.mm.yyyy hh24:mi:ss') "DATETIME_PLUS_2H",
to_char(SYSDATE - INTERVAL '10' HOUR, 'dd.mm.yyyy hh24:mi:ss') "DATETIME_MINUS_10H"
FROM
DUAL
--
-- R e s u l t
--
-- DATETIME DATETIME_MINUS_2H DATETIME_PLUS_2H DATETIME_MINUS_10H
-- ------------------- ------------------- ------------------- -------------------
-- 07.08.2022 09:58:38 07.08.2022 07:58:38 07.08.2022 11:58:38 06.08.2022 23:58:38
The last column now has the date from day before.

Oracle recursive CTE for date with interval

I'm trying to create a recursive CTE, which generates a date/time for every 10 minutes and stops at midnight but I am struggling with the syntax and can't seem to get it to work.
I would be very appreciative to anyone who can help me out. Thanks in advance to all who answer.
ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
with date_rows
(( SELECT TO_DATE('2021/08/20 18:30:25', 'YYYY/MM/DD HH24:MI:SS') as start_date FROM DUAL )
union all
select start_date+interval '10' minute
from date_rows
where start_date <
TRUNC(start_date) + INTERVAL '1' DAY)
select * from date_rows;
You can use:
WITH inputs ( value ) AS (
SELECT TO_DATE('2021/08/20 18:30:25', 'YYYY/MM/DD HH24:MI:SS')
FROM DUAL
),
date_rows ( start_date, end_date ) AS (
SELECT value,
TRUNC(value) + INTERVAL '1' DAY
FROM inputs
UNION ALL
SELECT start_date + INTERVAL '10' MINUTE,
end_date
FROM date_rows
WHERE start_date + INTERVAL '10' MINUTE < end_date
)
SELECT start_date
FROM date_rows;
or, if you want to duplicate the input value then you can use:
WITH date_rows ( start_date ) AS (
SELECT TO_DATE('2021/08/20 18:30:25', 'YYYY/MM/DD HH24:MI:SS')
FROM DUAL
UNION ALL
SELECT start_date + INTERVAL '10' MINUTE
FROM date_rows
WHERE start_date + INTERVAL '10' MINUTE
< TRUNC(TO_DATE('2021/08/20 18:30:25', 'YYYY/MM/DD HH24:MI:SS'))
+ INTERVAL '1' DAY
)
SELECT *
FROM date_rows;
Note: At each iteration of the recursive sub-query, start_date is the previous value so you need to check if start_date + INTERVAL '10' MINUTE is before midnight (rather than just start_date) otherwise you will have the final row after midnight.
db<>fiddle here
What was wrong with your code:
For a non-recursive sub-query factoring clause, the syntax is:
WITH query_alias AS (
or
WITH query_alias (column_alias) AS (
You were missing the AS keyword.
However, for a recursive sub-query factoring clause, you need to use the second version and specify the column aliases.
The () brackets around the first SELECT are redundant (but won't cause an error).
start_date < TRUNC(start_date) + INTERVAL '1' DAY) is always going to be true, even if the date goes past the midnight boundary; so the query will recurse infinitely.
db<>fiddle here
Something like this, I presume.
SQL> with date_rows (datum) as
2 (select cast (to_date('2021/08/20 18:30:25', 'YYYY/MM/DD HH24:MI:SS') as date)
3 from dual
4 union all
5 select cast (datum + interval '10' minute as date)
6 from date_rows
7 where datum < trunc(to_date('2021/08/20 18:30:25', 'YYYY/MM/DD HH24:MI:SS')) + interval '1' day
8 )
9 select * from date_rows;
DATUM
-----------------
08202021 18:30:25
08202021 18:40:25
08202021 18:50:25
08202021 19:00:25
08202021 19:10:25
08202021 19:20:25
08202021 19:30:25
08202021 19:40:25
08202021 19:50:25
08202021 20:00:25
08202021 20:10:25
<snip>
08202021 23:20:25
08202021 23:30:25
08202021 23:40:25
08202021 23:50:25
08212021 00:00:25
34 rows selected.
SQL>

Data for the last date not included by the "BETWEEN OPERATOR" IN ORACLE

I have an oracle query for getting the values between 12pm and 12am ( each day) from dates 11/06/2021 to 16/06/2021. The query i have below will collect the info from the 11th up to the 15th , but will not obtain data for the 16th. Is there any other way i can solve this issue? Thanks a lot for your time
SELECT TO_CHAR(datadate, 'dd/mm/yyyy') ,
ROUND(AVG(BATTERYVOLTAGE),2) AVGVOLTAGE
FROM MYTABLE
WHERE BATTERYVOLTAGE!= 0
AND to_char(datadate, 'hh24:mi:ss') BETWEEN '12:00:00' and '24:00:00'
AND DATADATE BETWEEN TO_DATE('11/06/2021', 'dd/mm/yyyy')
AND TO_DATE('16/06/2021', 'dd/mm/yyyy')
GROUP BY TO_CHAR(datadate, 'dd/mm/yyyy')
ORDER BY 1
datadate BETWEEN to_date('11/06/2021', 'dd/mm/yyyy')
AND to_date('16/06/2021', 'dd/mm/yyyy')
includes only rows where datadate if before or at 2021-06-16 00:00:00. Everything past 2021-06-16 00:00:00 isn't included.
That's a common mistake. Don't use BETWEEN for such things. Use a range with >= and < instead.
datadate >= to_date('2021-06-11 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND datadate < to_date('2021-06-17 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
If anyone wondering how to do it.
Change the formatting of the last date.
DATADATE BETWEEN TO_DATE('12/06/2021', 'dd/mm/yyyy') AND (TO_DATE('22/06/2021', 'dd/mm/yyyy')+(1-1/24/60/60))
For more info
https://asktom.oracle.com/pls/apex/asktom.search?tag=date-queries-using-between
You will have problems try to query date and time separated; is easier if you join date and time and search for datetime value like to_date('2021-06-11 09:00:00','YYYY-MM-DD HH24:MI:SS') the range you'll get is since 9 at the limit you want

How to retrieve rows between hours

I want to get all rows from my table, the table have one column with the following date type '14/07/2017 05:01:35 p.m.' between specific hours. Like between '00:01:00 am' and '01:00:00 am'.
I'm running Oracle Database 11g Release 2
select ID, CREATION_TIME
from my_table
where ID = 4 and
CREATION_TIME between to_date('29/04/2017 12:01:00', 'DD/MM/YYYY HH:MI:SS[AM]') and to_date('29/04/2019 01:00:00', 'DD/MM/YYYY HH:MI:SS[AM]')
order by creation_time asc;
I want to select hours instead of dates.
If you mean (from your last comment) that you want to get all records that are between the dates of Jan 1st 2019 and Apr 4th 2019, but only if the time portion of the recod is between 12:01AM and 1:00AM, then you could try this:
select ID, CREATION_TIME
from my_table
where ID = 4
and CREATION_TIME between to_date('01/01/2017', 'DD/MM/YYYY')
and to_date('04/04/2019', 'DD/MM/YYYY')
and CREATION_TIME BETWEEN TO_DATE(TO_CHAR(CREATION_TIME, 'MM/DD/YYYY') || ' 00:01:00', 'MM/DD/YYYY HH24:MI:SS')
AND
TO_DATE(TO_CHAR(CREATION_TIME, 'MM/DD/YYYY') || ' 01:00:00', 'MM/DD/YYYY HH24:MI:SS')
order by creation_time asc;

ADD_MONTHS function does not return the correct date in Oracle

See the results of below queries:
>> SELECT ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR'),-4) FROM DUAL;
30-NOV-10
>> SELECT ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4) FROM DUAL;
31-MAR-11
How can I get '30-MAR-11' when adding 4 months to some date?
Please help.
There is another question here about Oracle and Java
It states that
From the Oracle reference on add_months http://download-west.oracle.com/docs/cd/B19306_01/server.102/b14200/functions004.htm
If date is the last day of the month or if the resulting month has fewer days than the day component of date, then the result is the last day of the resulting month. Otherwise, the result has the same day component as date.
So I guess you have to manually check stating day and ending day to change the behaviour of the function. Or maybe by adding days instead of months. (But I didn't find a add_day function in the ref)
As a workaround, I might possibly use this algorithm:
Calculate the target date TargetDate1 using ADD_MONTHS.
Alternatively calculate the target date TargetDate2 like this:
1) apply ADD_MONTHS to the first of the source date's month;
2) add the difference of days between the source date and the beginning of the same month.
Select the LEAST between the TargetDate1 and TargetDate2.
So in the end, the target date will contain a different day component if the source date's day component is greater than the number of day in the target month. In this case the target date will be the last day of the corresponding month.
I'm not really sure about my knowledge of Oracle's SQL syntax, but basically the implementation might look like this:
SELECT
LEAST(
ADD_MONTHS(SourceDate, Months),
ADD_MONTHS(TRUNC(SourceDate, 'MONTH'), Months)
+ (SourceDate - TRUNC(SourceDate, 'MONTH'))
) AS TargetDate
FROM (
SELECT
TO_DATE('30-NOV-10', 'DD-MON-RR') AS SourceDate,
4 AS Months
FROM DUAL
)
Here is a detailed illustration of how the method works:
SourceDate = '30-NOV-10'
Months = 4
TargetDate1 = ADD_MONTHS('30-NOV-10', 4) = '31-MAR-11' /* unacceptable */
TargetDate2 = ADD_MONTHS('01-NOV-10', 4) + (30 - 1)
= '01-MAR-11' + 29 = '30-MAR-11' /* acceptable */
TargetDate = LEAST('31-MAR-11', '30-MAR-11') = '30-MAR-11'
And here are some more examples to show different cases:
SourceDate | Months | TargetDate1 | TargetDate2 | TargetDate
-----------+--------+-------------+-------------+-----------
29-NOV-10 | 4 | 29-MAR-11 | 29-MAR-11 | 29-MAR-11
30-MAR-11 | -4 | 30-NOV-10 | 30-NOV-10 | 30-NOV-10
31-MAR-11 | -4 | 30-NOV-10 | 01-DEC-10 | 30-NOV-10
30-NOV-10 | 3 | 28-FEB-11 | 02-MAR-11 | 28-FEB-11
You can use interval arithmetic to get the result you want
SQL> select date '2011-03-30' - interval '4' month
2 from dual;
DATE'2011
---------
30-NOV-10
SQL> ed
Wrote file afiedt.buf
1 select date '2010-11-30' + interval '4' month
2* from dual
SQL> /
DATE'2010
---------
30-MAR-11
Be aware, however, that there are pitfalls to interval arithmetic if you're working with days that don't exist in every month
SQL> ed
Wrote file afiedt.buf
1 select date '2011-03-31' + interval '1' month
2* from dual
SQL> /
select date '2011-03-31' + interval '1' month
*
ERROR at line 1:
ORA-01839: date not valid for month specified
How about something like this:
SELECT
LEAST(
ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR'),-4),
ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR')-1,-4)+1
)
FROM
DUAL
;
Result: 30-NOV-10
SELECT
LEAST(
ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4),
ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR')-1,4)+1
)
FROM
DUAL
;
Result: 30-MAR-11
the add_months function returns a date plus n months.
Since 30th November is the last date of the month, adding 4 months will result in a date that's the end of 4 months. This is expected behavior. If the dates are not bound to change, a workaround is to subtract a day after the new date has been returned
SQL> SELECT ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4) -1 from dual;
ADD_MONTH
---------
30-MAR-11
SELECT TO_DATE('30-NOV-10','DD-MON-RR') +
(
ADD_MONTHS(TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM'),4) -
TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM')
) RESULT
FROM DUAL;
This section in paranthesis:
ADD_MONTHS(TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM'),4) - TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM')
gives you number of days between the date you entered and 4 months later. So, adding this number of days to the date you given gives the exact date after 4 months.
Ref: http://www.dba-oracle.com/t_test_data_date_generation_sql.htm
Simple solution:
ADD_MONTHS(date - 1, x) + 1
Here is the trick:
select add_months(to_date('20160228', 'YYYYMMDD')-1, 1)+1 from dual;
Enjoy!
We have come to simpler (in our understanding) solution to this problem - take the least day number from original and add_month result dates, as this:
TRUNC(ADD_MONTHS(input_date,1),'MM') + LEAST(TO_CHAR(input_date, 'DD'), TO_CHAR(ADD_MONTHS(input_date,1), 'DD')) - 1
Some other examples here do not work on every date, below our test results:
WITH DATES as (
SELECT TO_DATE('2020-01-31', 'YYYY-MM-DD HH24:MI:SS') as input_date,
'2020-02-29' as expected_date
FROM dual
UNION ALL
SELECT TO_DATE('2020-02-28', 'YYYY-MM-DD HH24:MI:SS'),
'2020-03-28'
FROM dual
UNION ALL
SELECT TO_DATE('2020-09-30', 'YYYY-MM-DD HH24:MI:SS'),
'2020-10-30'
FROM dual
UNION ALL
SELECT TO_DATE('2020-09-01', 'YYYY-MM-DD HH24:MI:SS'),
'2020-10-01'
FROM dual
UNION ALL
SELECT TO_DATE('2019-01-30', 'YYYY-MM-DD HH24:MI:SS'),
'2019-02-28'
FROM dual
UNION ALL
SELECT TO_DATE('2020-02-29', 'YYYY-MM-DD HH24:MI:SS'),
'2020-03-29'
FROM dual
UNION ALL
SELECT TO_DATE('2020-09-29', 'YYYY-MM-DD HH24:MI:SS'),
'2020-10-29'
FROM dual
UNION ALL
SELECT TO_DATE('2020-03-01', 'YYYY-MM-DD HH24:MI:SS'),
'2020-04-01'
FROM dual
),
methods as (
SELECT
input_date,
expected_date,
ADD_MONTHS(input_date,1) as standard_way,
add_months(input_date-1, 1)+1 as wrong_way,
TO_DATE(LEAST(TO_CHAR(input_date, 'DD'), TO_CHAR(ADD_MONTHS(input_date,1), 'DD')) || '-' || TO_CHAR(ADD_MONTHS(input_date,1), 'MM-YYYY'), 'DD-MM-YYYY') as good_way,
TRUNC(ADD_MONTHS(input_date,1),'MM') + LEAST(TO_CHAR(input_date, 'DD'), TO_CHAR(ADD_MONTHS(input_date,1), 'DD')) - 1 as better_way
FROM
DATES
)
SELECT
input_date,
expected_date,
standard_way,
CASE WHEN TO_CHAR(standard_way,'YYYY-MM-DD') = expected_date THEN 'OK' ELSE 'NOK' END as standard_way_ok,
wrong_way,
CASE WHEN TO_CHAR(wrong_way,'YYYY-MM-DD') = expected_date THEN 'OK' ELSE 'NOK' END as wrong_way_ok,
good_way,
CASE WHEN TO_CHAR(good_way,'YYYY-MM-DD') = expected_date THEN 'OK' ELSE 'NOK' END as good_way_ok,
better_way,
CASE WHEN TO_CHAR(better_way,'YYYY-MM-DD') = expected_date THEN 'OK' ELSE 'NOK' END as better_way_ok
FROM
methods
;
CREATE OR REPLACE FUNCTION My_Add_Month(
STARTDATE DATE,
MONTHS_TO_ADD NUMBER
)
RETURN DATE
IS
MY_ADD_MONTH_RESULT DATE;
BEGIN
SELECT ORACLES_ADD_MONTH_RESULT + NET_DAYS_TO_ADJUST INTO MY_ADD_MONTH_RESULT FROM
(
SELECT T.*,CASE WHEN SUBSTRACT_DAYS > ADD_DAYS THEN ADD_DAYS - SUBSTRACT_DAYS ELSE 0 END AS NET_DAYS_TO_ADJUST FROM
(
SELECT T.*,EXTRACT(DAY FROM ORACLES_ADD_MONTH_RESULT) AS SUBSTRACT_DAYS FROM
(
SELECT ADD_MONTHS(STARTDATE,MONTHS_TO_ADD) AS ORACLES_ADD_MONTH_RESULT,EXTRACT(DAY FROM STARTDATE) AS ADD_DAYS FROM DUAL
)T
)T
)T;
RETURN TRUNC(MY_ADD_MONTH_RESULT);
END My_Add_Month;
/
--test & verification of logic & function both
SELECT T.*,ORACLES_ADD_MONTH_RESULT + NET_DAYS_TO_ADJUST AS MY_ADD_MONTH_RESULT,
My_Add_Month(STARTDATE,MONTHS_TO_ADD) MY_ADD_MONTH_FUNCTION_RESULT
FROM
(
SELECT T.*,CASE WHEN SUBSTRACT_DAYS > ADD_DAYS THEN ADD_DAYS - SUBSTRACT_DAYS ELSE 0 END AS NET_DAYS_TO_ADJUST FROM
(
SELECT T.*,EXTRACT(DAY FROM ORACLES_ADD_MONTH_RESULT) AS SUBSTRACT_DAYS FROM
(
SELECT T.*,ADD_MONTHS(STARTDATE,MONTHS_TO_ADD) AS ORACLES_ADD_MONTH_RESULT,EXTRACT(DAY FROM STARTDATE) AS ADD_DAYS FROM
(
SELECT TO_DATE('28/02/2014','DD/MM/YYYY') AS STARTDATE, 1 AS MONTHS_TO_ADD FROM DUAL
)T
)T
)T
)T;
Query-result
STARTDATE 2/28/2014
MONTHS_TO_ADD 1
ORACLES_ADD_MONTH_RESULT 3/31/2014
ADD_DAYS 28
SUBSTRACT_DAYS 31
NET_DAYS_TO_ADJUST -3
MY_ADD_MONTH_RESULT 3/28/2014
MY_ADD_MONTH_FUNCTION_RESULT 3/28/2014