ORACLE CONVERT CHAR FORMAT "YYYY-WW" TO DATE FORMAT - sql

I am trying to convert char date format "YYYY-WW" in ORACLE to date for calculating by week in year but have a trouble with error message format code cannot appear in date input format
If i write
TO_DATE(TO_CHAR(TO_DATE('1970-01-01 07:00:00', 'YYYY-MM-DD HH:MI:SS'),'YYYY-MM'),'YYYY-MM')
It will be normal
But if i write
TO_DATE(TO_CHAR(TO_DATE('1970-01-01 07:00:00', 'YYYY-MM-DD HH:MI:SS'),'YYYY-WW'),'YYYY-WW')
The message error format code cannot appear in date input format appear. I don't have no idea how to convert it right
Thanks for any advice

You can use:
TO_DATE( year || '-01-01', 'YYYY-MM-DD' ) + INTERVAL '7' DAY * (week - 1)
For your code:
SELECT TO_DATE( TO_CHAR(dt, 'YYYY') || '-01-01', 'YYYY-MM-DD' )
+ INTERVAL '7' DAY * (TO_CHAR(dt, 'WW') - 1) AS week_start
FROM (
SELECT TO_DATE('1970-01-01 07:00:00', 'YYYY-MM-DD HH24:MI:SS') AS dt
FROM DUAL
);
If you want to use ISO weeks (which always start on a Monday, rather than counting from the 1st January as WW does) then:
TRUNC( TO_DATE( iso_year || '-01-04', 'YYYY-MM-DD' ), 'IW')
+ INTERVAL '7' DAY * (iso_week - 1)
db<>fiddle here

You can have a look at this for guidance. https://asktom.oracle.com/pls/apex/asktom.search?tag=week-of-year-in-sql-confusing

There isn't a built-in way to convert a week number back to a date, as the result would be a bit arbitrary - which of the (up to) seven days in the week should it return?
If you want the first day of the week, as would be found by truncating the original date to WW precision (which is based on the day of the week of the first day of the year - docs), then you can reverse that process by getting the first day of the year and then adding the number of days in the specified number of weeks.
To do that you need to split your string into the two component parts:
substr('1970-01', 1, 4)
substr('1970-01', 6, 2)
and convert the first part to a date:
to_date(substr('1970-01', 1, 4) || '-01-01', 'YYYY-MM-DD')
and the second part to a number of weeks:
to_number(substr('1970-01', 6, 2))
then subtract one from that number of weeks, multiply by seven, and add it to the base date:
select to_date(substr('1970-01', 1, 4) || '-01-01', 'YYYY-MM-DD')
+ (7 * (to_number(substr('1970-01', 6, 2)) - 1))
from dual
1970-01-01
It's bit hard to tell that has been adjusted, so trying with today's date, which is week 30 of 2022:
select to_date(substr('2022-30', 1, 4) || '-01-01', 'YYYY-MM-DD')
+ (7 * (to_number(substr('2022-30', 6, 2)) - 1))
from dual
2022-07-23
which matches what trunc(sysdate, 'WW') gives.
db<>fiddle
If you want a specific day of the week then you'll need to figure out how much to adjust that, based on what day of the week the first day of the year was.

Select
Extract (year from TO_DATE('1970-01-01 07:00:00', 'YYYY-MM-DD HH:MI:SS')) || '-' ||
TO_CHAR(TO_DATE('1970-01-01 07:00:00', 'YYYY-MM-DD HH:MI:SS'),'WW')
From dual

If you are starting from a string like '1970-01-01 07:00:00' and want the first day of the corresponding week, you don't need to convert to and from strings and dates repeatedly; you just need to convert that string to a date once, and truncate it:
trunc(to_date('1970-01-01 07:00:00'), 'WW')
or if you want the ISO week:
trunc(to_date('1970-01-01 07:00:00'), 'IW')
For your example that would give:
select to_date('1970-01-01 07:00:00') as dt,
trunc(to_date('1970-01-01 07:00:00'), 'WW') as ww,
trunc(to_date('1970-01-01 07:00:00'), 'IW') as iw
from dual;
DT
WW
IW
1970-01-01 07:00:00
1970-01-01 00:00:00
1969-12-29 00:00:00
And for today it would give:
select to_date('2022-07-28 07:00:00') as dt,
trunc(to_date('2022-07-28 07:00:00'), 'WW') as ww,
trunc(to_date('2022-07-28 07:00:00'), 'IW') as iw
from dual;
DT
WW
IW
2022-07-28 07:00:00
2022-07-23 00:00:00
2022-07-25 00:00:00
db<>fiddle

Related

Is there a way to use SYSDATE with a weekly date?

So I've been trying to fetch some daily data with SYSDATE on a date type YYYYMMDD as following:
SELECT dates, trunc(calendar_date, 'DD') calendar_dates, weekday_nbr
FROM db.date
WHERE dates BETWEEN to_char(TRUNC(SYSDATE)-2, 'YYYYMMDD') AND to_char(TRUNC(SYSDATE)-1, 'YYYYMMDD')
But now I'm trying to use the same but on a YYYY+MM+Week date type with not much success
I tried using:
SELECT T time, period, fiscal_week
FROM db.time
WHERE time BETWEEN to_char(TRUNC(SYSDATE)-2, 'W') AND to_char(TRUNC(SYSDATE)-1,'W')
With time as a 7 digit number, and period and fiscal week as a 2 digit number
Knowing that there's no way I can truncate such date type, how can TRUNC SYSDATE YYYY+MM+Week to get the data on the last 2 weeks?
Also I was thinking about maybe getting the totals from a set day and then dropping all but the last 2 weeks, but on the long run maybe that would be time consuming.
Knowing that there's no way I can truncate such date type, how can TRUNC SYSDATE YYYY+MM+Week to get the data on the last 2 weeks?
Assuming that your fiscal weeks are from Monday-Sunday then you can truncate to the start of the ISO week (which is always Midnight on Monday) and use that for the basis of the comparison:
SELECT *
FROM db.time
WHERE dates >= TRUNC(SYSDATE, 'IW') - INTERVAL '14' DAY
AND dates < TRUNC(SYSDATE, 'IW')
If you have a column that is for weeks then you should still use a DATE data type and add a CHECK constraint (and can use virtual columns to generate the week and the year):
CREATE TABLE time (
dt DATE
CHECK (dt = TRUNC(dt, 'IW')),
year NUMBER(4,0)
GENERATED ALWAYS AS (EXTRACT(YEAR FROM dt)),
month NUMBER(2,0)
GENERATED ALWAYS AS (EXTRACT(MONTH FROM dt)),
week NUMBER(1,0)
GENERATED ALWAYS AS (FLOOR((dt - TRUNC(dt, 'MM'))/7) + 1),
time VARCHAR2(7)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(dt, 'YYYYMM')
|| (FLOOR((dt - TRUNC(dt, 'MM'))/7) + 1)
AS VARCHAR2(7)
)
)
-- ...
);
fiddle
Then you can use the logic above on the date column.
If you do not have a DATE column then you will need to convert your YYYYMMW number into a DATE and then use the logic above.
For example, if the logic for your fiscal weeks (which you have not described) is that the first week of each month starts on the first Monday of the month then you can convert the YYYYMMW number to a DATE using:
SELECT NEXT_DAY(
TO_DATE(SUBSTR(time, 1, 6), 'YYYYMM') - INTERVAL '1' DAY,
'MONDAY'
) + INTERVAL '7' DAY * (SUBSTR(time, 7, 1) - 1) AS week_start
FROM db.time
and then could use it to filter the table using:
SELECT *
FROM (
SELECT t.*,
NEXT_DAY(
TO_DATE(SUBSTR(time, 1, 6), 'YYYYMM') - INTERVAL '1' DAY,
'MONDAY'
) + INTERVAL '7' DAY * (SUBSTR(time, 7, 1) - 1) AS week_start
FROM db.time t
)
WHERE week_start >= TRUNC(SYSDATE, 'IW') - INTERVAL '14' DAY
AND week_start < TRUNC(SYSDATE, 'IW')
If you have different logic for calculating when fiscal weeks start then you will need to apply that logic to the conversion.

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;

Oracle - fetch first 3 days record of the current month

I need to fetch first 3 days record of the current month from Oracle database. Something like below,
Select * from test.purchase where create_ts=( first 3 days of the current month)
Select *
from test.purchase
where create_ts between trunc(sysdate,'mm') and trunc(sysdate,'mm') + 3
You can get the first day of the current month with the trunc(date) function, using the MM date format element.
select to_char(trunc(sysdate, 'MM'), 'YYYY-MM-DD Hh24:MI:SS') from dual;
TO_CHAR(TRUNC(SYSDA
-------------------
2017-06-01 00:00:00
You can then use date arithmetic to either add a number of days or an interval representing that number to get the fourth day of the month:
select to_char(trunc(sysdate, 'MM') + 3, 'YYYY-MM-DD Hh24:MI:SS') from dual;
TO_CHAR(TRUNC(SYSDA
-------------------
2017-06-04 00:00:00
If you want data up to the start of that fourth day, i.e. up to 23:59:59 on the 3rd, you can look for values less than midnight on the 4th:
select * from test.purchase
where create_ts >= trunc(sysdate, 'MM')
and create_ts < trunc(sysdate, 'MM') + 3;
You could potentially use between, but as that is inclusive you would need to specify the absolute latest time on the 3rd - checking whether the column is a date or a timestamp, which might change, and can be a little confusing. If you used between trunc(sysdate, 'MM') and trunc(sysdate, 'MM') + 3 then you would include any records at exactly midnight on the 4th, which isn't what you want. I find using >= and < clearer and less ambiguous, even if it is a little more typing.
If the column is actually a timestamp then you can cast the calculated dates to timestamp too, and/or an use interval for the upper bound:
select * from test.purchase
where create_ts >= cast(trunc(sysdate, 'MM') as timestamp)
and create_ts < cast(trunc(sysdate, 'MM') + 3 as timestamp);
... or:
...
and create_ts < cast(trunc(sysdate, 'MM') as timestamp) + interval '3' day;

Current Financial Year to sysdate

I can't seem to find a straightforward sql without delving into PL SQL for always bringing current financial year in which case 01-04-2015 to sysdate. I want this to always update automatically so when it comes next financial year in 01/04/2016 it will bring whatever is held from that date to whenever the report is being run.
If anyone can please shed some light for me. thanks
sql is:
SELECT
PROPERTY.PRO_MANAGINGCOMPANY_DESCR,
PROPERTY.PRO_SCHEME_DESCR,
PROPERTY.PRO_SCHEME,
SUM(REPAIR_CURRENT.REP_ESTIMATED_COST) as "Estimated Cost",
nvl(SUM(REPAIR_CURRENT.REP_INVOICED_COST),SUM(REPAIR_CURRENT.REP_ESTIMATED_COST)) as "Estimated Cost Invoiced",
SUM(REPAIR_CURRENT.REP_INVOICED_COST) as "Invoice Cost",
to_char(REPAIR_CURRENT.REP_RAISED_DATE,'Mon') as "Month",
to_number(to_char(to_date(REPAIR_CURRENT.REP_RAISED_DATE,'dd-mon-yy'),'mm')) as "Month No."
FROM
PROPERTY,
REPAIR_CURRENT,
SERVICE_REQUEST
WHERE
( SERVICE_REQUEST.SRQ_PRO_REFNO=PROPERTY.PRO_REFNO )
AND ( REPAIR_CURRENT.REP_SRQ_NO=SERVICE_REQUEST.SRQ_NO )
AND
(
--PROPERTY.PRO_SCHEME = ( '00054' )
--AND
REPAIR_CURRENT.REP_RAISED_DATE BETWEEN '01-APR-2015' AND sysdate
AND
REPAIR_CURRENT.REP_STATUS <> 'CAN'
)
GROUP BY
PROPERTY.PRO_MANAGINGCOMPANY_DESCR,
PROPERTY.PRO_SCHEME_DESCR,
PROPERTY.PRO_SCHEME,
to_char(REPAIR_CURRENT.REP_RAISED_DATE,'Mon'),
to_number(to_char(to_date(REPAIR_CURRENT.REP_RAISED_DATE,'dd-mon-yy'),'mm'))
If you just want to get the beginning of the fiscal year for the current date:
SELECT TO_DATE('01-04' || CASE
WHEN EXTRACT(MONTH FROM SYSDATE) > 4 THEN
EXTRACT(YEAR FROM SYSDATE)
ELSE
EXTRACT(YEAR FROM SYSDATE)-1
END, 'DD-MM-RRRR') FISCAL_YEAR
FROM DUAL
This works for any date:
REPAIR_CURRENT.REP_RAISED_DATE
BETWEEN Add_Months(Trunc(Add_Months(sysdate,-3),'YYYY'),3)
AND Sysdate
Basically, subtract three months, truncate to the year, and add three months back on.
To just get the financial year for a date, use:
Extract(Year from Add_Months(Trunc(Add_Months(sysdate,-3),'YYYY'),3))
SELECT *
FROM your_table
WHERE datetime >= CASE
WHEN SYSDATE < TRUNC( SYSDATE, 'YEAR' ) + INTERVAL '3' MONTH
THEN TRUNC( SYSDATE, 'YEAR' ) - INTERVAL '9' MONTH
ELSE TRUNC( SYSDATE, 'YEAR' ) + INTERVAL '3' MONTH
END;
Thank you, the following worked! add_months(trunc(sysdate,'year'),3) AND sysdate
thank you all for your input :)
REPAIR_CURRENT.REP_RAISED_DATE BETWEEN '01-APR-2015' AND sysdate
Firstly, '01-APR-2015' is not a DATE it is a string. You must always use TO_DATE along with proper format model to explicitly convert the string into DATE. Or, use the ANSI Date literal as you are not concerned with the time portion. It uses a fixed format 'YYYY-MM-DD'.
Now, coming to your date arithmetic, you could use a CASE expression to evaluate the financial date depending on the year.
REP_RAISED_DATE
BETWEEN
CASE
WHEN
SYSDATE < ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3)
THEN
ADD_MONTHS(TRUNC(SYSDATE, 'YEAR') , -9)
ELSE
ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3)
END
AND SYSDATE
Basically, SYSDATE >= ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3) is to check whether SYSDATE is greater than 1-APR of current year. And, SYSDATE < ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 15) is to check whether it is between JAN and MARCH of next year.
For example,
SQL> SELECT
2 CASE
3 WHEN
4 SYSDATE < ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3)
5 THEN
6 ADD_MONTHS(TRUNC(SYSDATE, 'YEAR') ,-9)
7 ELSE
8 ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3)
9 END FINANCIAL_YEAR
10 FROM dual;
FINANCIAL
---------
01-APR-15
For date between JAN and MAR of next year:
SQL> SELECT
2 CASE
3 WHEN
4 DATE '2016-02-01' < ADD_MONTHS(TRUNC(DATE '2016-02-01', 'YEAR'), 3)
5 THEN
6 ADD_MONTHS(TRUNC(DATE '2016-02-01', 'YEAR') ,-9)
7 ELSE
8 ADD_MONTHS(TRUNC(DATE '2016-02-01', 'YEAR'), 3)
9 END FINANCIAL_YEAR
10 FROM dual;
FINANCIAL
---------
01-APR-15
Following SQLreturns start and end date for Financial Year of current date.
SELECT
TO_DATE('01-04' || EXTRACT(YEAR FROM add_months(sysdate, -3)),'DD-MM-RRRR') from_dt ,
TO_DATE('31-03' || EXTRACT(YEAR FROM add_months(sysdate, 9)),'DD-MM-RRRR') to_dt from dual;
For any random date, you can use the following SQL: example for 01-04-2020
SELECT
TO_DATE('01-04' || EXTRACT(YEAR FROM add_months(to_date('01-04-2020','DD-MM-RRRR'), -3)),'DD-MM-RRRR') from_dt ,
TO_DATE('31-03' || EXTRACT(YEAR FROM add_months(to_date('01-04-2020','DD-MM-RRRR'), 9)),'DD-MM-RRRR') to_dt from dual;

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;