Oracle SQL Get date from year and week number - sql

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).

Related

I need Dynamic query in BigQuery to find on which Date the Day Saturday has occurred in First week of Year 2021

I need Dynamic query in BigQuery to find Date of Day Saturday has occurred in First week of Year 2021.
Consider the Day on date of 2022-01-01 is Saturday.
Therefore, I want to extract the date of last Year first week on which the Saturday was occurred.
Try the following:
with sample_dates as (
select date
from unnest(generate_date_array('2022-01-01','2022-12-31')) as date
)
select
date
,(select last_year_date
from unnest(generate_date_array(date_sub(date, INTERVAL 1 YEAR), date-1)) as last_year_date
where extract(week from date) = extract(week from last_year_date)
and extract(dayofweek from date) = extract(dayofweek from last_year_date)
) as last_year_date
from sample_dates
;
Given all of 2022 dates, this provides the same day of week for the same week in the previous year.
For example 2022-01-01 results in 2022-01-02 which is the first Saturday of the first week of last year.
If you're only looking for a single date the main portion would be the generating the appropriate date range with the generate_date_array function and then filtering based on the same week number and dayofweek value.
Use below - should work for any year - just replace 2021 with whatever year you need
select date(2021, 1, 8 - extract(dayofweek from date(2021, 1, 1)))
I think this code can help you!
You can find the first Saturday with a weekly cycle
declare
i number := 0;
date1 date := date '2022-01-01';
d varchar2(20);
begin
while i < 7 loop
SELECT case
when TO_CHAR(date1 + i, 'fmDay') = 'Saturday' then
date1 + i
end "Day"
into d
FROM DUAL;
i := i + 1;
dbms_output.put_line(d);
end loop;
end;

Find Occurence of Day with respect to month Oracle

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;

Get last 5 day record excluding Weekend days?

I'm trying to extract records from table inserted in last 5 working days on Oracle SQL
I'm unable to exclude Sunday and Saturday from this(as they're coming into consideration)
Select * from xyz where xdate=:businessDate - 5
Here businessDate (yyyymmdd format) is value taken as a parameter
Any pointers would greatly  be valued and appreciated .
SELECT *
FROM xyz
WHERE
xdate >= TRUNC(SYSDATE) - 7 --last 7 days AND
TRUNC (xdate) - TRUNC (xdate, 'IW') NOT IN (5,6) --not sat or sun
Take the last 7 days data, and remove any data from saturday or sunday.
TRUNC (xdate) - TRUNC (xdate, 'IW') returns a value between 0 and 6 inclusive. 0 is monday. This is not affected by NLS date settings because it uses the same region setting in both truncs, so regardless if the week starts on sunday or monday in your country, the result of the calc is the same
In any given 7 day period you have 5 week days and 2 weekend days
Note that this query assumes the table has no future dated data. If it does put a restriction that xdate must be less than sysdate
I am not an oracle expert but this could also work
SELECT *
FROM xyz
WHERE xdate IN (:businessDate - 1, :businessDate - 2, :businessDate - 3, :businessDate - 4, :businessDate - 5, :businessDate - 6, :businessDate - 7) AND MOD(TO_CHAR(xdate, 'J'), 7) + 1 NOT IN (6, 7);
I would do:
select * from xyz
where xdate >= TO_DATE( :businessDate, 'yyyymmdd' ) - 7
and to_char( xdate, 'DAY' ) not in ( 'SAT', 'SUN' )
That's assuming you're working in an English database. That should give you the past 7 days worth, but exclude weekends.
You'll need to do something like the following:
WITH cteData AS (SELECT TO_DATE(:BUSINESSDATE, 'YYYYMMDD') AS BUSINESS_DATE FROM DUAL)
SELECT *
FROM XYZ x
CROSS JOIN cteData d
WHERE TRUNC(x.XDATE) BETWEEN d.BUSINESS_DATE - 7
AND d.BUSINESS_DATE AND
TRUNC(x.XDATE) - TRUNC(x.XDATE, 'IW') <= 4
Find out the weekend day i.e. Sunday or Saturday by using 'DAY' format in format_date function e.g.
IF FORMAT_DATE(Date,'DAY')='SATURDAY' then date-1
IF FORMAT_DATE(Date,'DAY')='SUNDAY' then date-2
You would get only 5 days record

How do I convert a Week Number to From date of the week in oracle

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.

SQL Oracle How to convert String Week into date

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;