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.
This question already has answers here:
how to add second in oracle timestamp
(2 answers)
How to add 10 seconds in current_timestamp SQL ( Oracle )
(2 answers)
Closed 3 years ago.
I have a problem with the query in jdbc logstash.: z.clock < to_unix_timestamp(sysdate)-10 seconds
how to save system date minus 10 seconds?
z.clock is Unix timestamp.
SELECT h.name as hostname,i.name as item,i.key_,z.clock,z.value FROM zabbix.hosts h where z.clock > :sql_last_value and z.clock < to_unix_timestamp(sysdate)-10 seconds
In Oracle, one option is to subract it as
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select sysdate as right_now,
2 sysdate - 10 / (24 * 60 * 60) as ten_seconds_ago,
3 sysdate - interval '10' second ten_seconds_ago_2
4 from dual;
RIGHT_NOW TEN_SECONDS_AGO TEN_SECONDS_AGO_2
------------------- ------------------- -------------------
31.07.2019 11:16:07 31.07.2019 11:15:57 31.07.2019 11:15:57
SQL>
This could be a way:
select sysdate - interval '10' second
from dual
For example:
SQL> select to_char(sysdate - interval '10' second, 'dd/mm/yyyy hh24:mi:ss') as d_minus_10,
2 to_char(sysdate, 'dd/mm/yyyy hh24:mi:ss') as d
3 from dual;
D_MINUS_10 D
------------------- -------------------
31/07/2019 11:18:46 31/07/2019 11:18:56
I would like to convert in PL/SQL miliseconds to Time(hh:mm:ss)
21649000 to 06:00:49
or
83293000 to 23:08:13
Use NUMTODSINTERVAL with SECOND option :
select NUMTODSINTERVAL( 83293000 / 1000, 'SECOND' ) "Time" from dual t;
Time
-------------------
+000000000 23:08:13
This is a quite self-explanatory way:
select val,
floor( val / 1000 / 60 / 60 ) as hours,
floor( mod(val / 1000 / 60 , 60) ) as minutes,
floor( mod(val / 1000 , 60) ) as seconds
from (
select 21649000 val from dual union
select 83293000 val from dual
)
VAL HOURS MINUTES SECONDS
---------- ---------- ---------- ----------
21649000 6 0 49
83293000 23 8 13
This does not handle days, so the number of milliseconds must be less than 24 hours.
This gives numbers, you can edit it the way you need to get your desired output format/type.
If you only want second precision you could divide the number by 1000 to get seconds, and by 86400 to get a fraction of a day, then add that to midnight on any nominal date - and convert the result to a string:
select to_char(date '1970-01-01' + (21649000/86400000), 'HH24:MI:SS') as time
from dual;
TIME
--------
06:00:49
select to_char(date '1970-01-01' + (83293000/86400000), 'HH24:MI:SS') as time
from dual;
TIME
--------
23:08:13
This only works properly for values less than a day, i.e. where your original number is less than 86400000; higher than that and you only see the leftover in the second day.
The below query worked for me to convert to the Milliseconds to the HH:MM:SS
SELECT
TO_CHAR(TRUNC([ColumnName]/3600000),'FM9900') || ':' ||
TO_CHAR(TRUNC(MOD([ColumnName],3600000)/60000),'FM00') || ':' ||
TO_CHAR( trunc(MOD([ColumnName],60000)/1000),'FM00') FROM [TableName]`
I require a query that selects rows where the time is less or equal to 12:00
I had something like this in mind:
SELECT daterow FROM datecolumn WHERE daterow <= TO_DATE('12:00, HH24:MI')
However i get an error:
ORA-01843: not a valid month
How would i go about to get all rows that have a time less than 12:00 mid-day?
Try this,
SELECT daterow FROM datecolumn WHERE daterow <= TO_DATE('12:00', 'HH24:MI');
Try This:
SELECT daterow FROM datecolumn
WHERE TO_DATE(daterow,'HH24:MI') <= TO_DATE('12:00', 'HH24:MI');
In order to select all rows where time portion of the daterow column value is less than or equal to mid-day 12:00 you can use to_char() function to extract hour and minutes and to_number() to convert it to a number for further comparison:
-- sample of data. Just for the sake of demonstration
SQL> with t1(col) as(
2 select sysdate - to_dsinterval('P0DT3H') from dual union all
3 select sysdate - to_dsinterval('P0DT2H') from dual union all
4 select sysdate - to_dsinterval('P0DT1H') from dual union all
5 select sysdate + to_dsinterval('P0DT3H') from dual union all
6 select sysdate + to_dsinterval('-P2DT0H') from dual
7 )
8 select to_char(col, 'dd.mm.yyyy hh24:mi:ss') as res
9 from t1 t
10 where to_number(to_char(col, 'hh24mi')) <= 1200
11 ;
Result:
RES
-------------------
26.08.2013 08:10:59
26.08.2013 09:10:59
26.08.2013 10:10:59
24.08.2013 11:10:59
Sorry, but <= TO_DATE('12:00', 'HH24:MI') does not work. It does not extract the hour and minute from each date and compares it to 12:00. Instead it constructs the date representing high noon on the fisrt of the current month and compares each date to this date.
If you want to extract something from a date, use the extract function.
Attention: When using extract on a date, and want to extract hours, minutes or seconds, you have first to convert the date to a timestamp.
Example:
SELECT
extract(hour FROM cast(A AS TIMESTAMP)) AS h,
extract(MINUTE FROM cast(A AS TIMESTAMP)) AS m
FROM
DEMO
;
You can find a complete example on sqlfiddle. The example also shows that the to_date method doesn't work.
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