I have two sets of Year, Month and Day values. Eg. 15 years 7 months and 23 Days and 7 years 9 months and 12 days. Can the difference between two such values be found in terms of Years, Month and Days? I have thought of converting the values to days, compute the difference and again convert the result back to Years, Months and Days? For this, I have to assume 30 days/month and 365 days/year. Will this approach be ok?
When you think about it thoroughly, you'll realize that you can't calculate a difference of two "time intervals" when months are in place; simply because a subtraction of months can result in different number of days. You can subtract years, you can subtract weeks, you can subtract days,... you can subtract days-to-seconds, you can subtract years-to-months. However, you can't subtract years-to-days.
Example:
SQL> select timestamp'1915-07-23 00:00:00' - timestamp'1907-09-12 00:00:00' as diff_day_to_second_interval from dual;
DIFF_DAY_TO_SECOND_INTERVAL
---------------------------
+000002871 00:00:00
This is your 15 years, 7 months, 23 days minus 7 years, 9 months, 12 days when based on January 1, 1900. This gave us 2871 days difference.
However, consider the following two examples, simply shifted by 1 and 6 months to past
select timestamp'1915-06-23 00:00:00' - timestamp'1907-08-12 00:00:00' as diff_day_to_second_interval from dual;
DIFF_DAY_TO_SECOND_INTERVAL
---------------------------
+000002872 00:00:00
select timestamp'1915-01-23 00:00:00' - timestamp'1907-03-12 00:00:00' as diff_day_to_second_interval from dual;
DIFF_DAY_TO_SECOND_INTERVAL
---------------------------
+000002874 00:00:00
SQL>
These now gave us 2872 and 2874 days of difference.
Now, speaking of possible subtractions...
(a) subtracting year-to-month intervals
SQL> select interval'1915-07' year(4) to month - interval'1907-09' year(4) to month as diff_year_to_month_interval from dual;
DIFF_YEAR_TO_MONTH_INTERVAL
---------------------------
+000000007-10
select interval'1915-06' year(4) to month - interval'1907-08' year(4) to month as diff_year_to_month_interval from dual;
DIFF_YEAR_TO_MONTH_INTERVAL
---------------------------
+000000007-10
select interval'1915-01' year(4) to month - interval'1907-03' year(4) to month as diff_year_to_month_interval from dual;
DIFF_YEAR_TO_MONTH_INTERVAL
---------------------------
+000000007-10
SQL>
All three correctly produce a difference of 7 years and 10 months.
(b) subtracting day-to-second intervals
SQL> select interval'15 01:02:03' day(2) to second - interval'07 02:03:04' day(2) to second as diff_day_to_second_interval from dual;
DIFF_DAY_TO_SECOND_INTERVAL
---------------------------
+000000007 22:58:59
select interval'14 00:01:02' day(2) to second - interval'06 01:02:03' day(2) to second as diff_day_to_second_interval from dual;
DIFF_DAY_TO_SECOND_INTERVAL
---------------------------
+000000007 22:58:59
select interval'09 11:12:13' day(2) to second - interval'01 12:13:14' day(2) to second as diff_day_to_second_interval from dual;
DIFF_DAY_TO_SECOND_INTERVAL
---------------------------
+000000007 22:58:59
SQL>
All three produce the same results, as all three are subtractions of day-to-second intervals with consistent offsetting of the day/hour/minute/second parts of the interval values.
(c) subtracting year-to-day intervals
As I said: Not possible. There even is no such thing as year-to-day interval in Oracle; makers of the DB server knew why they decided not to add those to the engine.
Related
i have a query and I want to calculate the number of sat+sun total count in oracle, for example, I have a query pasted below there should be a total count of Saturday and Sunday, how can I achieve that please help, I really appreciate any help you can provide.
SELECT TO_DATE('01-12-2022','dd-mm-yyyy') start_date , TO_DATE(sysdate) end_date
FROM dual;
Don't use a row-generator to create a calendar (as it is very inefficient); just calculate the number by calculating the number of full weeks and then deal with the part weeks at the start and end of the range:
WITH range (start_date, end_date) AS (
SELECT DATE '2022-12-01', TRUNC(SYSDATE) FROM DUAL
)
SELECT -- Number of full weeks
(TRUNC(end_date, 'IW') - TRUNC(start_date, 'IW')) * 2/7
-- Number of weekend days in final week
+ GREATEST(end_date - TRUNC(end_date, 'IW') - 4, 0)
-- Number of weekend days in before first week
- GREATEST(start_date - TRUNC(start_date, 'IW') - 5, 0)
AS weekend_day_count
FROM range;
Which outputs:
WEEKEND_DAY_COUNT
8
fiddle
One option is to create a calendar between these two dates and then count number of Saturdays and Sundays:
SQL> with
2 test (start_date, end_date) as
3 -- period
4 (select date '2022-12-01', date '2022-12-29' from dual),
5 calendar as
6 -- calendar (all dates between START_DATE and END_DATE)
7 (select start_date + level - 1 as datum
8 from test
9 connect by level <= end_date - start_date + 1
10 )
11 -- number of Saturdays and Sundays
12 select count(*)
13 from calendar
14 where to_char(datum, 'dy', 'nls_date_language = english') in ('sat', 'sun');
COUNT(*)
----------
8
SQL>
You'd change dates at line #4.
P.S. If you look at code MT0 posted and their objection that row generator is inefficient, that's true. Although both queries return the same result, timing is different. For example:
Period 01.01.2022 - 31.12.2022 01.01.1900 - 31.12.2022 01.01.0001 - 31.12.2022
------ ----------------------- ----------------------- -----------------------
LF 00:00:00.00 00:00:00.17 00:00:03.72
MT0 00:00:00.02 00:00:00.02 00:00:00.05
It is obvious that my timing gets worse with period length. If you're looking at one year or a century, the difference is mostly irrelevant. For 2000 years, the difference is huge!
However, if you consider debugging, from my own point of view, my code is easier to read: "select number of rows from the calendar where date is either saturday or sunday" - plain English.
On the other hand, the other code isn't that straightforward; truncate date to week, subtract them, multiply by 2/7 (why "2/7" and not 4/9?), add result returned by the GREATEST function minus 4 (why 4? Why not 7?), subtract GREATEST of something minus 5 (why 5? Why not 2?) - as I said, that's NOT easy to read nor understand.
Therefore, it depends on what you actually need, timing vs. readability. Pick one :)
I have a query to return first Sunday of current month. However, if first Sunday fall on the first day of the month, it gets the next Sunday instead. How could get rid of it to return the first Sunday that on the first day of the month?
Take 1 Aug, 2021 as an example,below query returns second Sunday, 8 Aug 2021.
SELECT TO_CHAR(NEXT_DAY(TRUNC(TO_DATE(('20210801'), 'yyyyMMdd'), 'MM'), 'Sunday'), 'yyyyMMdd') AS FIRST_SUNDAY FROM DUAL;
Move one day back, to the last day of previous month:
SQL> SELECT TO_CHAR (
2 NEXT_DAY (TRUNC (TO_DATE (('20210801'), 'yyyyMMdd'), 'MM') - 1,
3 'sunday'), -- ^^
4 'yyyyMMdd') AS FIRST_SUNDAY --> this
5 FROM DUAL;
FIRST_SU
--------
20210801
SQL> SELECT TO_CHAR (
2 NEXT_DAY (TRUNC (TO_DATE (('20210701'), 'yyyyMMdd'), 'MM') - 1,
3 'sunday'),
4 'yyyyMMdd') AS FIRST_SUNDAY
5 FROM DUAL;
FIRST_SU
--------
20210704
SQL>
I was curious to see how in Oracle 12c you can take a timestamp datatype and convert the records into EPOCH time to make them a number and then use that number to find any records within that date column that are within 1 minute of each other (assuming the same day if needed, or simply any calculations within 1 minute).
I tried the following but got an ORA-01873: the leading precision of the interval is too small error.
select (sold_date - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS'))*86400 as epoch_sold_date from test1;
What is SOLD_DATE? For e.g. SYSDATE (function that returns DATE datatype), your code works OK.
SQL> select (sysdate
2 - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
3 ) * 86400 as epoch_sold_date
4 from dual;
EPOCH_SOLD_DATE
---------------
1600807918
SQL>
As SOLD_DATE is a timestamp, but - it appears that fractions of a second aren't or special interest to you, cast it to DATE:
select (cast (systimestamp as date) --> this
- to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
) * 86400 as epoch_sold_date
from dual;
Saying that you get the same result for all rows: well, I don't, and you shouldn't either if SOLD_DATE differs.
SQL> with test (sold_date) as
2 (select timestamp '2020-09-22 00:00:00.000000' from dual union all
3 select timestamp '2015-03-18 00:00:00.000000' from dual
4 )
5 select sold_date,
6 (cast (sold_date as date)
7 - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
8 ) * 86400 as epoch_sold_date
9 from test;
SOLD_DATE EPOCH_SOLD_DATE
------------------------------ ---------------
22.09.20 00:00:00,000000000 1600732800
18.03.15 00:00:00,000000000 1426636800
SQL>
One more edit: when you subtract two timestamps, result is interval day to second. If you extract minutes from it, you get what you wanted:
SQL> with test (sold_date) as
2 (select timestamp '2020-09-22 10:15:00.000000' from dual union all
3 select timestamp '2015-03-18 08:05:00.000000' from dual
4 )
5 select sold_date,
6 lead(sold_date) over (order by sold_date) next_sold_date,
7 --
8 lead(sold_date) over (order by sold_date) - sold_date diff,
9 --
10 extract(day from lead(sold_date) over (order by sold_date) - sold_date) diff_mins
11 from test
12 order by sold_date;
SOLD_DATE NEXT_SOLD_DATE DIFF DIFF_MINS
------------------------------ ------------------------------ ------------------------------ ----------
18.03.15 08:05:00,000000000 22.09.20 10:15:00,000000000 +000002015 02:10:00.000000000 2015
22.09.20 10:15:00,000000000
SQL>
In your case, you'd check whether extracted minutes value is larger than 1 (minute).
If you just want to see how many minutes are there between two timestamps, then
cast them to dates
subtract those dates (and you'll get number of days)
multiply it by 24 (as there are 24 hours in a day) and by 60 (as there are 60 minutes in an hour)
Something like this:
SQL> with test (date_1, date_2) as
2 (select timestamp '2020-09-22 10:15:00.000000',
3 timestamp '2020-09-22 08:05:00.000000' from dual
4 )
5 select (cast(date_1 as date) - cast(date_2 as date)) * 24 * 60 diff_minutes
6 from test;
DIFF_MINUTES
------------
130
SQL>
If you are just looking to compare dates and find rows that are within one minute of each other, you do not need to use epoch time. There are several solutions to this problem on this thread.
I have to get the date of the first Monday of the previous month for an Oracle query.
The ms SQL is
select dateadd (day, (9 - datepart(dw, eomonth(getdate(), -2)))%7, eomonth(getdate(), -2))
but there is no dateadd function in Oracle.
Here you go:
SQL> alter session set nls_date_language = 'english';
Session altered.
SQL> alter session set nls_date_Format = 'dd.mm.yyyy day';
Session altered.
SQL>
SQL> select next_day(add_months(trunc(sysdate, 'mm'), -1), 'monday') first_monday
2 from dual;
FIRST_MONDAY
--------------------
02.03.2020 monday
SQL>
What does it do?
truncate today's date (SYSDATE) to 1st of month ('mm')
subtract 1 month (add_months)
use next_day function, along with the 'monday' parameter
You can use:
TRUNC(TRUNC(add_months(dt, -1), 'mm') + 6, 'iw')
This subtracts a month from the specified date, truncates it to the 1st of the month, then adds 6 days to it before finally truncating it to the start of the iso week (which is always a Monday).
You need to add 6 to the first of the month to allow the appropriate Monday to count as the first Monday of the month (otherwise you could end up picking the last Monday of the previous month.
You can see it working [here[(https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c25bc32b3223b954e8fcb02b9b76ffd5)
Is this the best way to determine if an Oracle date is on a weekend?
select * from mytable
where
TO_CHAR (my_date, 'DY', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('SAT', 'SUN');
As of Oracle 11g, yes. The only viable region agnostic alternative that I've seen is as follows:
SELECT *
FROM mytable
WHERE MOD(TO_CHAR(my_date, 'J'), 7) + 1 IN (6, 7);
Not an answer to the question. But some more information. There are many more SQL tricks with date.
to_char(sysdate, 'd') --- day of a week, 1,2,3 .. to 7
to_char(sysdate, 'dd') --- day of a month, 1,2,3 .. to 30 or 31
to_char(sysdate, 'ddd') --- day of a year, 1,2,3 .. to 365 or 366
to_char(sysdate, 'w') --- week of a month, 1,2,3,4 or 5
to_char(sysdate, 'ww') --- week of a year, 1,2,3 .. to 52
More here: to_char function.
MOD(TO_CHAR(my_date, 'J'), 7) + 1 IN (6, 7)
is NOT correct!
The day number of weekend days may be 6 & 7, or 7 and 1, depending on the NLS-settings.
So the PROPER test (irrespective of NLS-settings) is this one mentioned before:
TO_CHAR (my_date, 'DY', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('SAT', 'SUN')
In my opinion the best option is
TO_CHAR (my_date, 'DY', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('SAT', 'SUN')
set NLS_TERRITORY before
alter session set NLS_TERRITORY=SWEDEN;
So you are sure which numbers are weekends