Oracle changing a date of birth to age - oracle

I need to convert a date of birth to current age in years using a column Dob in date format and system date but populate the result in the dob column.
The date format is dd-mm-yy.
I have tried using to_char and to_number functions to subtract from sys date

Assuming that you only need years and that your dob column is a date, you may need:
select dob, floor(months_between(sysdate, dob) / 12) age
from yourTable
For example:
with yourTable(dob) as (
select date '2000-02-01' from dual union all
select date '2000-02-02' from dual union all
select date '2000-01-31' from dual
)
select dob, floor(months_between(sysdate, dob) / 12) age
from yourTable
gives:
DOB AGE
---------- ----------
2000-02-01 18
2000-02-02 17
2000-01-31 18
3 rows selected.

If you need years, months and days for the date_of_birth column. You can do the below.
select sysdate,
date_of_birth,
trunc(months_between(sysdate,date_of_birth) / 12) as years,
trunc(months_between(sysdate,date_of_birth) -
(trunc(months_between(sysdate,date_of_birth) / 12) * 12)) as months,
trunc(sysdate) - add_months(date_of_birth, trunc(months_between(sysdate,date_of_birth))) as days
from table_name;

Related

Oracle: Return the specific records based on one column date

I have a database structure as below.
period
month
start_date
1
April
2022-04-01
2
May
2022-05-07
3
June
2022-06-04
4
July
2022-07-02
5
August
2022-08-06
6
September
2022-09-03
7
October
2022-10-01
8
November
2022-11-05
9
December
2022-12-03
10
January
2023-01-01
11
February
2023-02-04
12
March
2023-03-04
End date of the year is 2023-03-31.
Based on current_date, how do I select the query to return where the current date falls under Period 6.
My current query as below.
SELECT period FROM table1 as a
WHERE
a.start_date = (SELECT MAX(start_date) FROM table1 as b WHERE
b.start_date <=current_date) and ROWNUM <= 1
Is there anyway to improve the current query which to avoid using subquery?
Today is September 22nd, so - would this do?
Some sample data:
SQL> with test (period, month, start_date) as
2 (select 1, 'april' , date '2022-04-01' from dual union all
3 select 5, 'august' , date '2022-08-06' from dual union all
4 select 6, 'september', date '2022-09-03' from dual union all
5 select 7, 'october' , date '2022-10-01' from dual union all
6 select 10, 'january' , date '2023-01-01' from dual union all
7 select 12, 'march' , date '2023-03-04' from dual
8 ),
Query begins here:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= sysdate
14 )
15 select period
16 from temp
17 where rn = 1
18 /
PERIOD
----------
6
SQL>
It still uses a subquery (or a CTE, as in my example), but - as opposed to your approach, it selects from the source table only once, so performance should be improved.
A few more tests: instead of sysdate (line #13), presume that today is September 2nd (which means that it is in period #5):
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-09-02'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Or, if today were August 7th:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-08-07'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Your rule for the start_date appears to be:
If the month is January (first month of the calendar year) or April (typically, first month of the financial year) then use the 1st of that month;
Otherwise use the 1st Saturday of the month.
If that is the case then you can calculate the start date of the next month and use the query:
SELECT *
FROM table1
WHERE start_date <= SYSDATE
AND SYSDATE < CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(start_date, 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN TRUNC(ADD_MONTHS(start_date, 1), 'MM')
ELSE NEXT_DAY(TRUNC(ADD_MONTHS(start_date, 1), 'MM') - 1, 'SATURDAY')
END
Then, for your sample data:
CREATE TABLE table1 (
period NUMBER(2,0),
month VARCHAR2(9)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(start_date, 'FXMonth', 'NLS_DATE_LANGUAGE=English')
AS VARCHAR2(9)
)
),
start_date DATE
);
INSERT INTO table1 (period, start_date)
SELECT LEVEL,
CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(DATE '2022-04-01', LEVEL - 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN ADD_MONTHS(DATE '2022-04-01', LEVEL - 1)
ELSE NEXT_DAY(ADD_MONTHS(DATE '2022-04-01', LEVEL - 1) - 1, 'SATURDAY')
END
FROM DUAL
CONNECT BY LEVEL <= 12;
Outputs:
PERIOD
MONTH
START_DATE
6
September
2022-09-03 00:00:00
fiddle

How to get last workday before holiday in Oracle [duplicate]

This question already has answers here:
How to get the previous working day from Oracle?
(4 answers)
Closed 1 year ago.
need help for some oracle stuff ..
I need to get Day-1 from sysdate, holiday and weekend will be excluded .
And for holiday, we need to get the range to get the last workday before holiday.
The start date and end date will coming from my holiday table.
ex :
Holiday Table
HolidayName
Start_date
End_Date
holiday1
5th Aug'21
6th Aug'21
condition :
this query run on 9th Aug 2021
expected result :
4th Aug'21
I've tried some query and function but I just can't get what I need.
Thanks a lot for help!
Here's one way to do it.
select max(d) as last_workday
from (select trunc(sysdate)-level as d from dual connect by level < 30) prior_month
where to_char(d, 'DY') not in ('SAT','SUN')
and not exists (select holidayname from holiday_table
where prior_month.d between start_date and end_date)
;
Without seeing your Holiday table, it's hard to say how many days back you would need to look to find the last workday. If you have a holiday that lasts for more than 30 days, you'll need to change the 30 to a larger number.
You can use a simple case expression to determine what day of the week the start of your holiday is, then subtract a number of days based on that.
WITH
holiday (holidayname, start_date, end_date)
AS
(SELECT 'holiday1', DATE '2021-8-5', DATE '2021-8-6' FROM DUAL
UNION ALL
SELECT 'Christmas', DATE '2021-12-25', DATE '2021-12-26' FROM DUAL
UNION ALL
SELECT 'July 4th', DATE '2021-7-4', DATE '2021-7-5' FROM DUAL)
SELECT holidayname,
start_date,
end_date,
start_date - CASE TO_CHAR (start_date, 'Dy') WHEN 'Mon' THEN 3 WHEN 'Sun' THEN 2 ELSE 1 END AS prior_business_day
FROM holiday;
HOLIDAYNAME START_DATE END_DATE PRIOR_BUSINESS_DAY
______________ _____________ ____________ _____________________
holiday1 05-AUG-21 06-AUG-21 04-AUG-21
Christmas 25-DEC-21 26-DEC-21 24-DEC-21
July 4th 04-JUL-21 05-JUL-21 02-JUL-21
You can use a recursive sub-query factoring clause from this answer:
WITH start_date (dt) AS (
SELECT DATE '2021-05-02' FROM DUAL
),
days ( dt, day, found ) AS (
SELECT dt,
TRUNC(dt) - TRUNC(dt, 'IW'),
0
FROM start_date
UNION ALL
SELECT dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END,
CASE WHEN day IN (0, 6, 5) THEN 4 ELSE day - 1 END,
CASE WHEN h.start_date IS NULL THEN 1 ELSE 0 END
FROM days d
LEFT OUTER JOIN holidays h
ON ( dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END
BETWEEN h.start_date AND h.end_date )
WHERE found = 0
)
SELECT dt
FROM days
WHERE found = 1;
Which, for the sample data:
CREATE TABLE holidays (HolidayName, Start_date, End_Date) AS
SELECT 'holiday1', DATE '2021-08-05', DATE '2021-08-06' FROM DUAL;
Outputs:
DT
2021-08-04 00:00:00
db<>fiddle here
Don't know if it's very efficient. Did it just for fun
create table holidays (
holiday_name varchar2(100) primary key,
start_date date not null,
end_date date not null
)
/
Table created
insert into holidays (holiday_name, start_date, end_date)
values ('holiday1', date '2021-08-05', date '2021-08-06');
1 row inserted
with days_before(day, wrk_day) as
(select trunc(sysdate - 1) d,
case
when h.holiday_name is not null then 0
when to_char(trunc(sysdate - 1), 'D') in ('6', '7') then 0
else 1
end work_day
from dual
left join holidays h
on trunc(sysdate - 1) between h.start_date and h.end_date
union all
select db.day - 1,
case
when h.holiday_name is not null then 0
when to_char(db.day - 1, 'D') in ('6', '7') then 0
else 1
end work_day
from days_before db
left join holidays h
on db.day - 1 between h.start_date and h.end_date
where db.wrk_day = 0) search depth first by day set order_no
select day from days_before where wrk_day = 1;
DAY
-----------
04.08.2021

DATE DIFF IN YEARS

How to find date diff in years in 'YYYY-MM-DD' format .
I am using below two querues:
SELECT TRUNC(TO_NUMBER(SYSDATE - TO_DATE('1994-08-13')) / 365.25) AS AGE FROM DUAL;
ORA-01861: literal does not match format string
SELECT (TO_DATE(SYSDATE, 'YYYY-MM-DD') - TO_NUMBER('1994-08-13', 'YYYY-MM-DD')) FROM DUAL;
O/P-: -727738
Desired o/p: 26
I suggest to use "months_between" function because it takes leap years into account (months_between wants 2 dates as parameters):
select months_between(sysdate, to_date('1994-08-13', 'YYYY-MM-DD'))/12 from dual;
26,4729751904122
of course, if you need to truncate:
select trunc(months_between(sysdate, to_date('1994-08-13', 'YYYY-MM-DD'))/12) from dual;
26
Just subtract one date from the other:
sysdate - date '1994-08-13'
Or
sysdate - to_date('1994-08-13', 'yyyy-mm-dd')
returns the number of days between the two dates.
So rewrite your first query to:
select (sysdate - date '1994-08-13') / 365.25 as age
from dual
So, extract years from these dates and subtract them:
SQL> select extract (year from sysdate) - extract(year from date '1994-08-13') diff
2 from dual;
DIFF
----------
27
SQL>
Because,
SQL> select 2021 - 1994 diff from dual;
DIFF
----------
27
SQL>

Using a case when to know if date format is right

I want to migrate a table which contains some columns with dates. The issue is my dates are often in dd/mm/yyyyy HH24:MM:YYYY format. But sometimes it appears that the format is only dd/mm/yyyy, or blank.
I guess that's why I'm getting ORA-01830 when I'm trying to migrate the datas.
I tried
CASE
WHEN TO_DATE(MYDATE,'DD/MM/YYYY')
then TO_DATE(MYDATE,'DD/MM/YYYY 00:00:00')
END AS MYDATE
But I'm not sure if it is possible to test the date format (and ofcourse it's not working).
Thank you
TO_DATE cannot test date format, but you can do it. If Lalit's answer would not be enough, try something like
select
case when my_date like '__/__/__' then to_date(my_date, 'dd/mm/yy')
when my_date like '__-__-__' then to_date(my_date, 'dd-mm-yy')
...
end
So you have the data type issue. DATE is stored as string literal. As you have mentioned that the date model has the DD/MM/YYYY part same, just that the time portion is either missing for some rows or the entire value is NULL.
For example, let's say your table have the values like -
SQL> WITH dates AS(
2 SELECT 1 num, '29/12/2014 16:38:57' dt FROM dual UNION ALL
3 SELECT 2, '29/12/2014' FROM dual UNION ALL
4 SELECT 3, NULL FROM dual
5 )
6 SELECT num, dt
7 FROM dates
8 /
NUM DT
---------- -------------------
1 29/12/2014 16:38:57
2 29/12/2014
3
SQL>
TO_DATE with proper format model should do the trick.
Let's stick to a format model first.
SQL> alter session set nls_date_format='dd/mm/yyyy hh24:mi:ss';
Session altered.
Now, let's use TO_DATE to explicitly convert the string literal to date.
SQL> WITH dates AS(
2 SELECT 1 num, '29/12/2014 16:38:57' dt FROM dual UNION ALL
3 SELECT 2, '29/12/2014' FROM dual UNION ALL
4 SELECT 3, NULL FROM dual
5 )
6 SELECT num, to_date(dt, 'dd/mm/yyyy hh24:mi:ss') dt
7 FROM dates
8 /
NUM DT
---------- -------------------
1 29/12/2014 16:38:57
2 29/12/2014 00:00:00
3
SQL>

Unique rows in oracle 11g

I have a query which returns a set of records as like the one below:-
Date Dept commission
5-Apr Sales 20
4-Apr Sales 21
1-Jan Marketing 35
case 1: If i run a query between 1 Jan and 5 april I should get
Date Dept commission
5 April Sales 76
case 2: and when I run the query between jan 1 and jan 31 should get the output as
Date Dept commission
1 Jan Marketing 35
Case 2 is simple as when i put hte date range getting the required results , but not sure how to handle case 1 to show the max / latest date , the Dept for that date and a sum of the commission for that Dept , date for the selected date range . The output will be a single row with the latest date and department with a sum(commission) for the selected date range.
SELECT
MAX(Date) AS Date
, ( SELECT tt.Dept
FROM tableX tt
WHERE tt.Date = MAX(t.Date)
) AS Dept
, SUM(Commission) AS Commission
FROM
tableX t
WHERE
Date BETWEEN StartDate AND EndDate
The above works in SQL-Server, MySQL, Postgres as the sql-fiddle, test-1 shows, however it does NOT work in Oracle 11g R2 !
This works though (sql-fiddle, test-2):
SELECT
MAX(t.Date) AS Date
, MIN(tt.Dept) AS Dept --- MIN, MAX irrelevant
, SUM(t.Commission) AS Commission
FROM
( SELECT
MAX(Date) AS Date
, SUM(Commission) AS Commission
FROM
tableX
WHERE
Date BETWEEN StartDate AND EndDate
) t
JOIN
tableX tt
ON tt.Date = t.Date
The MIN(tt.Dept) is used to take care of the case you have more than row with the maximum date, say one row with Sales and one with Marketing, both in Apr-5
This works, too, using the LAST_VALUE analytic function (sql-fiddle, test-3):
SELECT
MAX(Date) AS Date
, MIN(Dept) AS Dept
, SUM(Commission) AS Commission
FROM
( SELECT
Date AS Date
, LAST_VALUE(Dept) OVER( ORDER BY Date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS Dept
, Commission AS Commission
FROM
tableX
WHERE
Date BETWEEN StartDate AND EndDate
) t

Resources