I want to find the occurrence of a particular day with respect to a month using Oracle RDBMS.
For Example: Like if it is 14-Dec-2020 today. So it is 2nd Monday.
So i want a output as 2. Likewise.
More Examples
7-Dec-2020 --> Output Should Be 1 (As it is first Monday of December)
29-Dec-2020 --> Output Should Be 5 (as it is the 5th Tuesday of December)
You can use a case expression:
select (case when extract(day from sysdate) <= 7 then '1st '
when extract(day from sysdate) <= 14 then '2nd '
when extract(day from sysdate) <= 21 then '3rd '
when extract(day from sysdate) <= 28 then '4th '
else '5th '
end) || to_char(sysdate, 'Day')
from dual;
Here is a db<>fiddle.
In the with clause I build the dates I want (e.g. the whole month of December 2020), and I use row_number analytic function to rank my dates based on YYYYMMD format. I use D not DD, because D means the day of the week. I also use nsl_parameter (nls_date_language) because the starting day is not the same for all countries
Then I convert the rank column (rnb) to date using to_date(lpad(rnb, 2, '0'), 'DD'). I did that as I need to make oracle spell the occurence using this date format 'DDth'.
Then I concatenate the result with "fmDay Month Year" format
with dates as (
select DATE '2020-12-01' + level - 1 dt
, row_number()over (
partition by to_char(DATE '2020-12-01' + level - 1, 'YYYYMMD','nls_date_language = ENGLISH')
order by DATE '2020-12-01' + level - 1) rnb
from dual
connect by level <= 31
)
select dt
, Initcap(to_char(to_date(lpad(rnb, 2, '0'), 'DD'), 'DDth','nls_date_language = ENGLISH'))
||' '||to_char(dt, 'fmDay Month YYYY', 'nls_date_language = ENGLISH') Occurence
from dates
order by dt
;
This is just a little math. The first seven days is the first occurrence for each day, the next seven days is the second occurrrence and so on. So get the day number, e.g. 14, subtract one and apply an integer division then add one again.
select trunc((extract(day from sysdate) - 1) / 7) + 1 from dual;
Related
I would like to insert in table :
the first day of the week starting from Monday.
the last day of the week as Sunday.
the WEEK number => (1-52 or 1-53) based on the ISO standard.
First i tried to select first day, the last day and week number for specific date and it's works :
WITH ranges AS
(
SELECT to_date('29-10-2012', 'dd-MM-yyyy') AS DATE_TEST FROM DUAL
)
SELECT DATE_TEST "DATE",
TO_CHAR( NEXT_DAY( TRUNC(DATE_TEST) , 'SUNDAY' )) "WEEK END DATE",
TO_CHAR(TO_DATE(DATE_TEST,'DD-MON-YYYY'),'WW')+1 "WEEK NUMBER"
FROM ranges ;
But now i would like to display this data between two dates, but i get result only for the start_date. someone can help please.
after, when all good i will insert all in the table.
Thanks
WITH ranges AS(
select to_date('29-OCT-2012', 'dd-MM-yyyy') START_DATE,
to_date('31-DEC-2016', 'dd-MM-yyyy') END_DATE
from dual
)
SELECT START_DATE "DATE",
TO_CHAR( NEXT_DAY( TRUNC(START_DATE) , 'SUNDAY' )) "WEEK END DATE",
TO_CHAR(TO_DATE(START_DATE,'DD-MON-YYYY'),'WW')+1 "WEEK NUMBER"
FROM ranges ;
Format WW returns Week of year (1-53) where week 1 starts on the first day of the year and continues to the seventh day of the year, see Datetime Format Elements
In order to get the week number according to ISO-8601 standard use format IW. I would suggest like this:
WITH ranges AS(
SELECT
DATE '2012-10-29' START_DATE,
DATE '2016-12-31' END_DATE
FROM dual
)
SELECT
START_DATE, END_DATE,
TRUNC(START_DATE + 7*(LEVEL-1), 'IW') AS Week_Start_Date,
TRUNC(START_DATE + 7*(LEVEL-1), 'IW') + 6 AS Week_End_Date,
TO_CHAR(TRUNC(START_DATE + 7*(LEVEL-1)), 'IYYY-"W"IW') WEEK_NUMBER
FROM ranges
CONNECT BY START_DATE + 7*(LEVEL-1) <= END_DATE;
Looks like you're looking for a calendar.
Based on your RANGES CTE, there's another - calendar which utilizes hierarchical query to create all dates between start_date and end_date. Once you have all dates, extract values you're interested in.
SQL> with
2 ranges as
3 (select to_date('29-OCT-2012', 'dd-MM-yyyy') start_date,
4 to_date('31-DEC-2016', 'dd-MM-yyyy') end_date
5 from dual
6 ),
7 calendar as
8 (select start_date + level - 1 as datum
9 from ranges
10 connect by level <= end_date - start_date + 1
11 )
12 select
13 min(datum) start_date,
14 min(next_day(datum, 'sunday')) week_end_date,
15 to_char(datum, 'ww') week_number
16 from calendar
17 group by to_char(datum, 'yyyy'), to_char(datum, 'ww')
18 order by 1;
START_DATE WEEK_END_D WE
---------- ---------- --
29-10-2012 04-11-2012 44
04-11-2012 11-11-2012 45
11-11-2012 18-11-2012 46
18-11-2012 25-11-2012 47
25-11-2012 02-12-2012 48
<snip>
09-12-2016 11-12-2016 50
16-12-2016 18-12-2016 51
23-12-2016 25-12-2016 52
30-12-2016 01-01-2017 53
222 rows selected.
SQL>
How are you?
to help, I tried to do some research, I found these links.
enter link description here
enter link description here
I found:
SELECT
ROUND((TRUNC(SYSDATE) - TRUNC(SYSDATE, 'YEAR')) / 7,0) CANTWEEK,
NEXT_DAY(SYSDATE, 'SUNDAY') - 7 FIRSTDAY,
NEXT_DAY(SYSDATE, 'SUNDAY') - 1 LASTDAY
FROM DUAL
and
select
to_char(sysdate - to_char(sysdate, 'd') + 2, 'yyyymmdd') first_day_of_week
, to_char(sysdate - to_char(sysdate, 'd') + 8, 'yyyymmdd') last_day_of_week
from
dual
and
select sysdate AS today,
TRUNC(next_day(sysdate,'MONDAY')-8) as DOMINGO,
TRUNC(next_day(sysdate,'SATURDAY')) as SABADO
from dual
I don't have oracle here so I couldn't test it well but it should solve what you need, anything let me know :)
Here is a way to make the common table expression return a range of dates.
with ranges (dt) as (
select to_date('29-OCT-2012', 'dd-MM-yyyy') as dt
from dual
union all
select ranges.dt+1
from ranges
where ranges.dt < to_date('31-DEC-2016', 'dd-MM-yyyy')
)
Then you can use that to calculate the other values.
SELECT dt "DATE"
, TO_CHAR(NEXT_DAY(TRUNC(dt), 'SUNDAY')) "WEEK END DATE"
, TO_CHAR(TO_DATE(dt,'DD-MON-YYYY'),'WW') + 1 "WEEK NUMBER"
, cast(TO_CHAR(dt, 'WW') as int) +
case when cast(TO_CHAR(dt, 'D') as int) < cast(TO_CHAR(trunc(dt, 'year'), 'D') as int) then 1 else 0 end WeekNumberInYear
FROM ranges ;
If you want to have all of your date calculations done at once, check out an example here: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=35e407af3b5bf711bb7ae53b8cf0e608
I have year and week numbers in a SQL table in one column with a format as VERSION_YEAR_WEEKNUMBER
I can use the SUBSTR function to just get the YEAR_WEEKNUMBER, but I then need to convert that to a date and I'm not sure how to convert a weeknumber to a date.
Ideally the date would be a Thursday, but right now I'd just like to convert a weeknumber to any date that's in that week and year combination.
Sample data would just be "VERSION_YEAR_WEEKNUMBER" e.g. VERSION_2020_10. Meaning, 2020, 10th week (ISO) of the year. Desired result should be one date between 02-MAR-20 and 08-MAR-20
You can extract the year and week numbers separately:
substr(your_column, instr(your_column, '_', -1, 2) + 1, 4)
substr(your_column, instr(your_column, '_', -1, 1) + 1)
Then convert the year number to the first day of the ISO year:
trunc(to_date(substr(your_column, instr(your_column, '_', -1, 2) + 1, 4), 'YYYY'), 'IYYY')
and either add the number of weeks - 1 * 7 days to get the Monday of that week, then add 3 for the Thursday; or add the number of weeks * 7 days to get the Monday of the following week and subtract four for the Thursday of the week you want:
trunc(to_date(substr(your_column, instr(your_column, '_', -1, 2) + 1, 4), 'YYYY'), 'IYYY')
+ (to_number(substr(your_column, instr(your_column, '_', -1, 1) + 1)) * 7)
- 4
YOUR_COLUMN RESULT
--------------- ----------
VERSION_2020_10 2020-03-05
1.2.3_2021_01 2021-01-07
1_2_3_2020_11 2020-03-12
2020_12 2020-03-19
ABC_2020_52 2020-12-24
DEF_2020_53 2020-12-31
GHI_2021_01 2021-01-07
JKL_2021_52 2021-12-30
MNO_2021_53 2022-01-06
db<>fiddle showing the intermediate steps so you can see what's happening.
Converting from ISO-Week to year is not trival, because the ISO year may differ from the actual year.
For example 2018-12-31 was Week 1 of 2019 according to ISO-8601.
I ended up with this function:
FUNCTION ISOWeek2Date(YEAR INTEGER, WEEK INTEGER) RETURN DATE DETERMINISTIC IS
res DATE;
BEGIN
IF WEEK > 53 OR WEEK < 1 THEN
RAISE VALUE_ERROR;
END IF;
res := NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( WEEK - 1 ) * 7;
IF TO_CHAR(res, 'fmIYYY') = YEAR THEN
RETURN res;
ELSE
RAISE VALUE_ERROR;
END IF;
END ISOWeek2Date;
However NEXT_DAY depends on date language of your session, so the solution by #MT0 would be better. The logic is the same.
Extract year and week values with regular expression as proposed by #Alex Poole
Converting to an ISO week is relatively simple as the 4th of January will always be in the first ISO week of the year. So:
Start with the 4th January of your year;
Truncate it back to Monday to be the start of the ISO week;
Add 3 days and you have Thursday of the first ISO week; and then
Add the number of weeks to take it from the 1st week to the required week.
Like this:
SELECT TRUNC( TO_DATE( SUBSTR( value, 9, 4 )||'01-04', 'YYYY-MM-DD' ), 'IW' )
+ INTERVAL '3' DAY
+ INTERVAL '7' DAY * ( SUBSTR( value, 14 ) - 1 )
AS thursday_of_iso_week
FROM table_name
Which, for the sample data:
CREATE TABLE table_name ( value ) AS
SELECT 'VERSION_2020_10' FROM DUAL UNION ALL
SELECT 'VERSION_2019_1' FROM DUAL
Outputs:
| THURSDAY_OF_ISO_WEEK |
| :------------------- |
| 2020-03-05 00:00:00 |
| 2019-01-03 00:00:00 |
If the values do not have fixed character positions then use INSTR to find the underscores:
SELECT TRUNC( TO_DATE( SUBSTR( value, INSTR( value, '_' ) + 1, 4 )||'01-04', 'YYYY-MM-DD' ), 'IW' )
+ INTERVAL '3' DAY
+ INTERVAL '7' DAY * ( SUBSTR( value, INSTR( value, '_', -1 ) + 1 ) - 1 )
AS thursday_of_iso_week
FROM table_name
Which outputs the same as above.
db<>fiddle here
You could join the following CTE to your query
select weekdate, to_char(weekdate, 'IW-yyyy') week_year from (
SELECT LEVEL weeknr, to_date('2020-01-02', 'yyyy-MM-dd') + numtodsinterval((LEVEL-1)*7, 'day') weekdate
FROM dual
CONNECT BY LEVEL <= 53
);
For the join, use the result of your SUBSTR and WEEK_YEAR of the CTE. WEEKDATE would be the date. This CTE generates all the week numbers for all Thursdays of 2020 (January 2nd was the first Thursday in 2020). If you need more dates, just use another limit for "LEVEL". If you have huge amounts of source data or query the data very frequently, I would recommend to put this in a table (to be done one time) by appending "create table ... as" at the beginning and create an index on WEEK_YEAR (and generating enough data so your solution works for a long time).
If I add 1 to extract(month from date), then does the result become 13 or 1 (January of next year)
I have the below code:
(extract(day from sysdate) >=1 and extract(month from sysdate)=12) and (targstartdate >= to_date(((extract(month from sysdate))|| '-1-' || (extract(year from sysdate)+1)) , 'mm-dd-yyyy') and targstartdate <= to_date(((extract(month from sysdate)+1)|| '-1-' ||(extract(year from sysdate)+1)) , 'mm-dd-yyyy')
You can use MOD
MOD(extract(month from sysdate)+1,12) + 1
If you are trying to get the month number of the next month, flip your logic. Instead of extracting the month number and adding 1, add one month to the date then extract the month. See the difference below. The second adds one month, then extracts the month of 1 (for January).
select extract( month from to_date('12/15/2017','MM/DD/YYYY')) + 1 from dual
union all
select extract (month from ADD_MONTHS(to_date('12/15/2017','MM/DD/YYYY'),1)) from dual;
Suppose I enter WeekNo: 14 the query should return the From Date: April 4th 2016, since the week 14 starts from 4th April to 10th April
select to_date('14','iw') FROM dual;
something like this ? (it work for current year) there discard data from another years
with dates as (select to_char(
to_date('1.01.'||extract(year from sysdate),'dd.mm.yyyy' ) + level -1
,'IW') we,
to_date('1.01.'||extract(year from sysdate),'dd.mm.yyyy' ) + level -1 da
from dual
connect by level <= 365 + 10 )
select * from (
select case
when -- we = null if number of week in jan > 1,2,3,4....
((to_number(we) > 40 )
and extract(year from sysdate) = extract(year from da)
and extract(month from da) = '01') or
-- we = null when current year < year of da
(extract(year from sysdate) != extract(year from da))
then
null
else we
end we,
da
from dates
)
where we = 14
and rownum = 1
Dealing with ISO-Weeks is not trivial, for example January, 1st 2016 is week 53 of 2015, see
select to_char(date '2016-01-01', 'iyyy-"W"iw') from dual;
So, providing only the week number without the (ISO-) year is ambiguous - although it is obvious as along as you are not around new-years date.
Some time ago I wrote this function to get the date from ISO-Week.
FUNCTION ISOWeekDate(week INTEGER, YEAR INTEGER) RETURN DATE DETERMINISTIC IS
res DATE;
BEGIN
IF week > 53 OR week < 1 THEN
RAISE VALUE_ERROR;
END IF;
res := NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( week - 1 ) * 7;
IF TO_CHAR(res, 'fmIYYY') = YEAR THEN
RETURN res;
ELSE
RAISE VALUE_ERROR;
END IF;
END ISOWeekDate;
Of course you can just select NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( week - 1 ) * 7;, however this would not be error-safe if somebody uses the wrong year.
If it is not an issue to append the year to the week you are looking for, you can also use this :
SELECT (TRUNC ( TO_DATE (SUBSTR ('201627', 1, 4) || '0131', 'YYYY'|| 'MMDD'), 'IYYY')
+ ( 7 * ( TO_NUMBER (SUBSTR ('201627', 5)) - 1)) ) AS iw_Monday
FROM dual
With in this example 201627 being the YYYYIW you are looking for.
It will return the date of the MONDAY of that week.
Found it on Oracle forums, there are a couple of other solutions there. I found this one to be the most elegant.
The advantage is that you do everything from the SELECT, and you don't need either a function or PL/SQL or a WHERE clause.
Disadvantages : you must append the year and specify your search week 2 times, unless you use a variable
Here is a simple and direct computation, taking advantage of various Oracle date functions. Since it compares to what Oracle already counts as ISO week etc., it shouldn't be subject to any of the difficulties other solutions correctly point to and address with additional code.
The "magic number" 14 in the formula should instead be a bind variable, perhaps :iw, or some other mechanism of inputting the ISO week number into the query.
select trunc(sysdate, 'iw') - 7 * (to_number(to_char(trunc(sysdate), 'iw')) - 14) as dt
from dual;
DT
----------
2016-04-04
1 row selected.
I have dates stored like String in database.
The format is 'yyyy-ww' (example: '2015-43').
I need to get the first day of the week.
I tried to convert this string into date but there is no 'ww' option for the function "to_date".
Do you have an idea to perform this convertion?
EDIT
Test results based on the answers -
Thanks for your anwsers, but I have many problems to apply your solutions to my context:
select
TRUNC ( 2015 + ((43 - 1) * 7), 'IW' )
from dual
==> Error : ORA-01722: invalid number
select
TRUNC(to_date('2015','YYYY')+ to_number('01') *7, 'IW')
from dual
==> 2015-02-02 00:00:00
I waited for a date in january
select
trunc(to_date(regexp_substr('2015-01', '\d+',1,2), 'YYYY') + regexp_substr('2015-01', '\d+') * 7, 'IW') dt2
from dual
==> 0039-09-14 00:00:00
select
regexp_substr('2015-01', '\d+',1,2) as res1,
regexp_substr('2015-01', '\d+') * 7 as res2
from dual
==> res1 = 01
==> res2 = 14105
try to use by truncate
with t as (
select '16-2010' dt from dual
)
--
--
select dt,
trunc(to_date(regexp_substr(dt, '\d+',1,2), 'YYYY') + regexp_substr(dt, '\d+') * 7, 'IW') dt2
from t
I have dates stored like String in database.
You should never do that. It is a bad design. you should store date as DATE and not as a string. For all kinds of requirements for date manipulations Oracle provides the required DATE functions and format models. As and when needed, you could extract/display the way you want.
I need to get the first day of the week.
TRUNC (dt, 'IW') returns the Monday on or before the given date.
Anyway, in your case, you have the literal as YYYY-WW format. You could first extract the year and week number and combine them together to get the date using TRUNC.
TRUNC ( year + ((week_number - 1) * 7)
, 'IW
)
So, the above should give you the Monday of the week number passed for that year.
SQL> WITH DATA AS
2 ( SELECT '2015-43' str FROM dual
3 )
4 SELECT TRUNC(to_date(SUBSTR(str, 1, 4),'YYYY')+ to_number(SUBSTR(str, instr(str, '-',1)+1))*7, 'IW')
5 FROM DATA
6 /
TRUNC(TO_
---------
23-NOV-15
SQL>
Similar to Lalit's, however, I think I've corrected the math (his seemed to be off a bit when I tested .. )
with w_data as (
select sysdate + level +200 d from dual connect by level <= 10
),
w_weeks as (
select d, to_char(d,'yyyy-iw') c
from w_data
)
SELECT d, c, trunc(d,'iw'),
TRUNC(
to_date(SUBSTR(c, 1, 4)||'0101','yyyymmdd')-8+to_char(to_date(SUBSTR(c, 1, 4)||'0101','yyyymmdd'),'d')
+to_number(SUBSTR(c, instr(c, '-',1)+1)-1)*7 ,'IW')
FROM w_weeks;
The extra columns help show the dates before, and after.
I would do the following:
WITH d1 AS (
SELECT '2015-43' AS mydate FROM dual
)
SELECT TRUNC(TRUNC(TO_DATE(REGEXP_SUBSTR(mydate, '^\d{4}'), 'YYYY'), 'YEAR') + (COALESCE(TO_NUMBER(REGEXP_SUBSTR(mydate, '\d+$')), 0)-1) * 7, 'IW')
FROM d1
The first thing the above query does is get the first four digits of the string 2015-43 and truncates that to the closest year (if you convert convert 2015 using TO_DATE() it returns a date within the current month; that is SELECT TO_DATE('2015', 'YYYY') FROM dual returns 01-FEB-2015; we need to truncate this value to the YEAR in order to get 01-JAN-2015). I then add the number of weeks minus one times seven and truncate the whole thing by IW. This returns a date of 01-OCT-2015 (see SQL Fiddle here).
According ISO the 4th of January is always in week 1, so your query should look like
Select
TRUNC(TO_DATE(REGEXP_SUBSTR(your_column, '^\d{4}')||'-01-04', 'YYYY-MM-DD')
+ 7*(REGEXP_SUBSTR(your_column, '\d$')-1), 'IW')
from your_table;
However, there is a problem. ISO year used for Week number can be different than actual year. For example, 1st Jan 2008 was in ISO week number 53 of 2007.
I think a proper working solution you get only when you generate ISO weeks from date value.
WITH w AS
(SELECT TO_CHAR(DATE '2010-01-04' + LEVEL * INTERVAL '7' DAY, 'IYYY-IW') AS week_number,
TRUNC(DATE '2010-01-04' + LEVEL * INTERVAL '7' DAY, 'IW') AS first_day
FROM dual
CONNECT BY DATE '2010-01-04' + LEVEL * INTERVAL '7' DAY < SYSDATE)
SELECT your_Column, first_day
FROM w your_table
JOIN w ON week_number = your_Column;
Your date range must bigger than 2010-01-04 and not bigger than current day.
This is what I used:
select
to_date(substr('2017/01',1,4)||'/'||to_char(to_number(substr('2017/01',6,2)*7)-5),'yyyy/ddd') from dual;