I want to calculate current age of person from DOB(date of birth) field in Oracle table.
Data type of DOB field is varchar and the is date stored in format 'DD-MON-YY'.
when I calculate current age of a person from date like 10-JAN-49 the query will return age in negative. Also, I observed that if date has year 13 to 49 it gives negative result.
Examples
22-NOV-83 -valid result
09-FEB-58 --valid result
05-JUN-49 - Invalid result like -36
Query Executed for reference
select round(MONTHS_BETWEEN(sysdate,to_date(dob,'DD-MON-RR'))/12)||' Yrs'
from birth
Any help is appreciated!
To get round the 21st century problem, just modifying #the_silk's answer slightly:
SELECT
CASE WHEN SUBSTR(dob, -2, 2) > 13
THEN FLOOR
(
MONTHS_BETWEEN
(
SYSDATE
, TO_DATE(SUBSTR(dob, 1, 7) || '19' || SUBSTR(dob, -2, 2), 'DD-MON-YYYY')
) / 12
)
ELSE
FLOOR(MONTHS_BETWEEN(sysdate,TO_DATE(dob,'DD-MON-YY'))/12)
END
FROM
birth
Please be aware though that this assumes that any date year between '00' and '13' is 21st century, so this sql should only be used if you are building a one off throwaway script, otherwise it will become out of date and invalid before long.
The best solution would be to rebuild this table, converting the varchar column into a date column, as alluded to by Ben.
/*
A value between 0-49 will return a 20xx year.
A value between 50-99 will return a 19xx year.
*/
Source: http://www.techonthenet.com/oracle/functions/to_date.php
SELECT FLOOR
(
MONTHS_BETWEEN
(
SYSDATE
, TO_DATE(SUBSTR(d_date, 1, 7) || '19' || SUBSTR(d_date, -2, 2), 'DD-MON-YYYY')
) / 12
)
FROM
(
SELECT '10-JAN-49' d_date FROM DUAL
)
-- The result: 64
SELECT MONTHS_BETWEEN( to_date(sysdate,'dd-mm-rr')
, to_date(to_date(dob,'yymmdd'),'dd-mm-rr')
) / 12 AS Years
FROM birth;
Maybe this works.
My solution would be something like this:
SELECT DATE_FORMAT(CURDATE(),'%Y') - DATE_FORMAT(dob,'%Y') as age
FROM `member_master`
having age >=30 and age <=35
SELECT year,
months,
Floor(SYSDATE - day_1) AS days
FROM (SELECT year,
months,
Add_months(To_date('30-Oct-1980', 'dd-Mon-yyyy'),
12 * year + months)
day_1
FROM (SELECT year,
Floor(Trunc(Months_between(Trunc(SYSDATE),
To_date('30-Oct-1980', 'dd-Mon-yyyy')
))
- year * 12) AS months
FROM (SELECT Floor(Trunc(Months_between(Trunc(SYSDATE),
To_date('30-Oct-1980',
'dd-Mon-yyyy'
)
)) /
12) AS year
FROM dual)));
Try using YY in the Year part of your TO_DATE
RR Like YY, but the two digits are ``rounded'' to a year in the range 1950 to 2049. Thus, 06 is considered 2006 instead of 1906
Related
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).
Given day, month and year as integer columns in the table, calculate the date and weekending date from these values.
I tried the following but it gives me an incorrect result for the derived date as '2022-08-05 00:00:00'
select to_date(2020||03||20,'YYYYMMDD')
Even tried below but results in a string without leading zeros for month and day
select (cast (2020 as varchar)+cast (03 as varchar(2))+cast (02 as varchar(2)))
Result for above is : 202032
The problem is that you have numbers -- so leading zeros are missing. You can try:
select to_date('2020'||'03'||'20', 'YYYYMMDD')
Or, if these have to be numbers:
select to_date( (2020 * 10000 + 03 * 100 + 20)::text, 'YYYYMMDD')
I have a table with data and columns containing a date but separated into several columns :
a column with the year,
a column with the month,
a column with the day.
I would like to keep only the data with a date that is less than 2 months.
I have tried to concat the date but the month and the day does not always have 2 characters. Sometimes it is one number : 1 for january for example.
Could you give some tips to make this request?
Thanks in advance,
select *
from etude
where concat(year,month,day) > NOW()
It is not working as expected
The TIMESTAMP_FORMAT() function should be robust to months/days being either one or two digits:
SELECT *
FROM etude
WHERE TIMESTAMP_FORMAT(CONCAT(year, month, day), 'YYYYMMDD') > NOW();
But note that while this may fix your immediate problem, a much better long term solution would be to stop storing the various components of your date in separate columns. Instead, just maintain a single bona-fide date/timestamp column.
Use lpad function along with timestamp_format function
select *
from etude
where timestamp_format(year || lpad(month,2,'0') || lpad(day,2,'0'), 'YYYYMMDD') > now();
If keep only the data with a date that is less than 2 months means keep only the data with a date that is not earlier than 2 months from the current date, then try this as is:
SELECT dt
FROM
(
SELECT date(digits(dec(year, 4)) || '-' || digits(dec(month, 2))|| '-' || digits(dec(day, 2))) dt
FROM table
(
values
(2019, 1, 1)
, (2019, 7, 13)
) etude (year, month, day)
)
WHERE dt > current date - 2 month;
to combine your 2 questions into 1 answer and using Satya's answer
select *
from etude
where timestamp_format(year || lpad(month,2,'0') || lpad(day,2,'0'), 'YYYYMMDD') > now() - 2 months;
This is another option
select *
from etude
where DATE('0001-12-31') + (year -2) YEARS + month MONTHS + day DAYS > CURRENT DATE
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;