Date and quarter SQL ORACLE - sql

I am currently working on oracle sql and have some issues managing dates and quarters.
The first thing I try to do is given a quarter and a year I would like to know the number of days of the quarter. For example, quarter 1 in 2013 had 90 days but in 2012 had 91 days.
The second thing I would like to do is convert a date dd/qq/yyyy to a date
dd/mm/yyyy.
For instance, 60/2/2013 gives 30/5/2013. I am a beginner in Oracle so any help or function names will be highly appreciated.
Thanks

this gives the number of days in the current quarter, playing around with the add_months should allow you to find the length of other quarters
select (add_months(trunc(sysdate,'q'),3) - 1) - trunc(sysdate,'q')
from dual;

For your first question, hopefully this will give you some idea of what to do:
with sample_data as (select 2012 yr, 1 quarter from dual union all
select 2012 yr, 2 quarter from dual union all
select 2012 yr, 3 quarter from dual union all
select 2012 yr, 4 quarter from dual union all
select 2013 yr, 1 quarter from dual union all
select 2013 yr, 2 quarter from dual)
---- end of mimicking a table called "sample_data"; see query below:
select yr,
quarter,
add_months(to_date('01/01/'||yr, 'dd/mm/yyyy'), (quarter - 1)*3) qtr_st,
add_months(to_date('01/01/'||yr, 'dd/mm/yyyy'), quarter * 3) - 1 qtr_end,
add_months(to_date('01/01/'||yr, 'dd/mm/yyyy'), quarter * 3) - add_months(to_date('01/01/'||yr, 'dd/mm/yyyy'), (quarter - 1)*3) diff
from sample_data;
YR QUARTER QTR_ST QTR_END DIFF
---------- ---------- ---------- ---------- ----------
2012 1 01/01/2012 31/03/2012 91
2012 2 01/04/2012 30/06/2012 91
2012 3 01/07/2012 30/09/2012 92
2012 4 01/10/2012 31/12/2012 92
2013 1 01/01/2013 31/03/2013 90
2013 2 01/04/2013 30/06/2013 91
N.B. Because you're including the day of the start_date in the count, the difference is effectively how many days between the 1st of the quarter and the 1st of the next quarter, or qtr_end - qtr_st + 1 from my query above.
For your second question, here's one way:
with sample_data as (select '60/2/2013' dy_qtr_fmt from dual union all
select '60/02/2013' dy_qtr_fmt from dual union all
select '01/1/2013' dy_qtr_fmt from dual union all
select '1/1/2013' dy_qtr_fmt from dual)
---- end of mimicking a table called "sample_data"; see query below:
select dy_qtr_fmt,
add_months(year_st, (qtr-1)*3) + num_days_in_qtr - 1 dt
from (select dy_qtr_fmt,
to_date('01/01/'||substr(dy_qtr_fmt, -4), 'dd/mm/yyyy') year_st,
to_number(substr(dy_qtr_fmt, instr(dy_qtr_fmt, '/', 1, 1) + 1, instr(dy_qtr_fmt, '/', 1, 2) - instr(dy_qtr_fmt, '/', 1, 1) -1)) qtr,
to_number(substr(dy_qtr_fmt, 1, instr(dy_qtr_fmt, '/', 1, 1) - 1)) num_days_in_qtr
from sample_data);
DY_QTR_FMT DT
---------- ----------
60/2/2013 30/05/2013
60/02/2013 30/05/2013
01/1/2013 01/01/2013
1/1/2013 01/01/2013

Related

How to get first date and last date of all twelve months for given year in Oracle PLSQL?

If i give input year like '2021' i need result as below
Month Start Date End Date
1 1/1/2021 31/01/2021
2 1/2/2021 28/01/2021
.
.
.
.
.
.
.
.
.
12 1/12/2021 31/12/2021
Basically, it is about the row generator technique; there are plenty of them, pick any you want. (Have a look at OraFAQ).
For example:
SQL> with mon as
2 (select add_months(trunc(to_date(&par_year, 'yyyy'), 'yyyy'), level - 1) val
3 from dual
4 connect by level <= 12
5 )
6 select to_char(val, 'mm') mon,
7 val start_date,
8 last_day(val) end_date
9 from mon
10 order by 1;
Enter value for par_year: 2021
MO START_DATE END_DATE
-- ---------- ----------
01 01/01/2021 31/01/2021
02 01/02/2021 28/02/2021
03 01/03/2021 31/03/2021
04 01/04/2021 30/04/2021
05 01/05/2021 31/05/2021
06 01/06/2021 30/06/2021
07 01/07/2021 31/07/2021
08 01/08/2021 31/08/2021
09 01/09/2021 30/09/2021
10 01/10/2021 31/10/2021
11 01/11/2021 30/11/2021
12 01/12/2021 31/12/2021
12 rows selected.
SQL>
You could also use directly the model clause for that purpose.
SELECT n
, TO_DATE(&the_year||lpad(f, 2, '0'), 'YYYYMM') start_dt
, last_day(TO_DATE(&the_year||lpad(f, 2, '0'), 'YYYYMM')) end_dt
FROM DUAL
MODEL
DIMENSION by (1 as n)
MEASURES (1 as f)
RULES (
f[FOR n FROM 1 TO 12 INCREMENT 1 ] = cv(n)
)
;
The advantage of the model clause is if you later want to get the every other month, you just need to change the increment from 1 to 2. Or if you are looking for the quarter months of the year (January, April, Jully, October), you just need to change increment from 1 to 3, and so on...
Just replace 2021 in the query for your year
with months (m) as (
select 1 from dual union all
select m + 1 from months where m < 12
)
select
to_date('2021' || '-' || to_char(m) || '-01', 'YYYY-MM-DD') as first_day,
last_day(to_date('2021' || '-' || to_char(m) || '-01', 'YYYY-MM-DD')) as last_day
from months
You can try on this db<>fiddle
Try below query
WITH cte_date as(
SELECT
LEVEL Month_No,
to_date(to_char(LEVEL||'-2021'),'MM-YYYY') Start_Date FROM dual
CONNECT BY LEVEL <=12
)
SELECT Month_No, Start_Date, LAST_DAY(Start_Date) End_Date
FROM cte_date;
I'm a fan of recursive CTEs because they are part of Standard SQL. I would phrase this as:
with months (month, startdate) as (
select 1 as month, date '2021-01-01'
from dual
union all
select month + 1, add_months(startdate, 1)
from months
where month < 12
)
select month, startdate, last_day(startdate) as enddate
from months;
If you need an input year, there are different ways to accomplish it. But a simple way is to change the second line to:
select 1 as month, to_date(:year || '0101', 'YYYYMMDD')

How do I subtract multiple dates?

An image of the Discharge date and admission date in which i am supposed to subtract as (discharge date – admitted
date) +1 .
As you said - subtract and sum. Sample data in lines #1 - 5, query begins at line #6.
SQL> with test (who, admission_date, discharge_date) as
2 (select 'Leslie', date '2010-09-13', date '2010-09-25' from dual union all
3 select 'Leslie', date '2014-09-03', date '2014-09-21' from dual union all
4 select 'Leslie', date '2015-12-03', date '2015-12-14' from dual
5 )
6 select who, sum(discharge_date - admission_date + 1) total_days
7 from test
8 group by who;
WHO TOTAL_DAYS
------ ----------
Leslie 44
SQL>

Need SQL to generate multiple rows from subquery

I have a query that selects a month and year from a complex heirachy of tables. To simplify this question, we can use this query:
select 2 as month, 2018 as year from dual;
I need SQL that will use that query as a subquery to output 3 row: that month as well as with the preceeding 2 months (including years). So the output needed for that specific case would be:
Month Year
2 2018
1 2018
12 2017
I have no idea how to proceed. Ideas anyone?
If you have the month/year already in a DATE column, it would be one less conversion, but if they are separate columns like you have provided, you can use a query like the one below.
Query
WITH sample_data AS (SELECT 2 AS month, 2018 AS year FROM DUAL)
SELECT TO_CHAR (ADD_MONTHS (TO_DATE (month || '-' || year, 'MM-YYYY'), ((LEVEL * -1) + 1)), 'MM')
AS month,
TO_CHAR (ADD_MONTHS (TO_DATE (month || '-' || year, 'MM-YYYY'), ((LEVEL * -1) + 1)), 'YYYY')
AS year
FROM sample_data
CONNECT BY LEVEL <= 3;
Result
MONTH YEAR
________ _______
02 2018
01 2018
12 2017
For Oracle 12+:
select *
from
(select 2 as month, 2018 as year
from dual) complex_query
,lateral(
select
extract(month from add_months(dt, 1-level)) as month,
extract(year from add_months(dt, 1-level)) as year
from (
select
to_date(complex_query.year||'-'||complex_query.month||'-01', 'yyyy-mm-dd') as dt
from dual
)
connect by level<=3
);
For previous versions:
select
complex_query.*
,extract(year from (to_date(year||'-'||month,'yyyy-mm')-delta)) year_2
,extract(month from (to_date(year||'-'||month,'yyyy-mm')-delta)) month_2
from
(select 2 as month, 2018 as year
from dual) complex_query
,(select
NUMTOYMINTERVAL(level-1,'month') delta
from dual
connect by level<=3
) v
Results:
MONTH YEAR YEAR_2 MONTH_2
---------- ---------- ---------- ----------
2 2018 2018 1
2 2018 2017 12
2 2018 2017 11
Would union work?
with primary_month as (
select 2 as month, 2018 as year from dual
)
select (case when month = 1 then 13 else month end) - 1 as month , case when month = 1 then year-1 else year end as year from primary_month
union
select month, year from primary_month
union
select month + 1 as month, year from primary_month
;
This example won't handle boundary cases like if the primary month is January, since there is no month 0, but I'm not sure if that's what you're going for and it depends on the real query and schema (e.g. are you using timestamp columns?)

Convert financial year and financial quarter into year month

I've got 2 columns in a table, Financial_Year and Financial_Quarter. An example of the data is as follows:
Financial_Year Financial_Quarter
2018/2019 2
I want to produce a third column called 'Year_month' which is the last month of the quarter within each financial year, so in the above example I want the year_month column to read 201809.
Has anyone got any hints how to do this in Oracle SQL?
Assuming the FINANCIAL_YEAR column is a string (two numbers separated by slash) and the financial year 2018/2019 begins on 1 April 2018 and ends on 31 March 2019, you could do something like this:
with
test_data (financial_year, financial_quarter) as (
select '2018/2019', 1 from dual union all
select '2018/2019', 2 from dual union all
select '2018/2019', 3 from dual union all
select '2018/2019', 4 from dual
)
select financial_year, financial_quarter,
to_char(add_months(to_date(substr(financial_year, 1, 4) || '03', 'yyyymm'),
3 * financial_quarter), 'yyyymm') as year_month
from test_data
;
FINANCIAL_YEAR FINANCIAL_QUARTER YEAR_MONTH
-------------- ----------------- ----------
2018/2019 1 201806
2018/2019 2 201809
2018/2019 3 201812
2018/2019 4 201903
Also, just for fun, here is a different solution that doesn't use any date computations - it's all string based.
select financial_year, financial_quarter,
substr(financial_year, decode(financial_quarter, 4, 6, 1), 4) ||
decode(financial_quarter, 1, '06', 2, '09', 3, '12', 4, '03') as year_month
from test_data
;
I guess there are many ways to skin this cat, here is one
SELECT CASE WHEN financial_quarter = '4' THEN SUBSTR(financial_year, 6, 4) || '03'
ELSE SUBSTR(financial_year, 1, 4) || LPAD((financial_quarter + 1) * 3, 2, '0')
END
FROM some_table
Of course '4' needs to be replaced with 4 if financial_quarter is numeric

ORA-01848: day of year must be between 1 and 365 (366 for leap year) error

I have existing data with day of the year (1-366) stored in DDD format.
Now, when I am trying to query the data and pull out report in MM/DD/YYYY format, I am getting
ORA-01848: day of year must be between 1 and 365 (366 for leap year) for the below query
select to_CHAR(TO_DATE(MyColumn, 'DDD'),'MM/DD/YYYY') from MyTable;
How to retrieve date in MM/DD/YYYY format when the year is leap year?
Your current query:
select to_CHAR(TO_DATE(MyColumn, 'DDD'),'MM/DD/YYYY') from MyTable;
is defaulting to the current year. All of the converted values will show 2018:
-- CTE for dummy values (<= 365)
with mytable(mycolumn) as (
select 1 from dual
union all select 60 from dual
union all select 365 from dual
)
select to_CHAR(TO_DATE(MyColumn, 'DDD'),'MM/DD/YYYY') from MyTable;
TO_CHAR(TO
----------
01/01/2018
03/01/2018
12/31/2018
As 2018 isn't a leap year, day 366 isn't valid. You could make it use an arbitrary hard-coded leap year:
select to_CHAR(TO_DATE('2000' || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') from MyTable;
Demo:
-- CTE for dummy values
with mytable(mycolumn) as (
select 1 from dual
union all select 60 from dual
union all select 365 from dual
union all select 366 from dual
)
select to_CHAR(TO_DATE('2000' || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') from MyTable;
TO_CHAR(TO
----------
01/01/2000
02/29/2000
12/30/2000
12/31/2000
But if the original date wasn't from a leap year then the values will be out by a day - as well as being shown against the wrong year, of course.
You could filter out values with values > 365 and stick to the current year, but again you're likely to get unrealistic/unhelpful converted dates. Or you could use 12c's default ... on conversion error syntax to get say a null result when it won't convert, but again other dates will be inconsistent.
Unless you know the year each DDD values represents you can't get an accurate conversion.
If you have another column that holds the year then concatenate them together, e.g. if that year column is called MyYear:
select to_CHAR(TO_DATE(MyYear || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') from MyTable;
Demo showing varying resulsts:
-- CTE for dummy values
with mytable(mycolumn, myyear) as (
select 1, 2018 from dual
union all select 60, 2016 from dual
union all select 60, 2017 from dual
union all select 365, 2016 from dual
union all select 366, 2016 from dual
union all select 365, 2017 from dual
)
select MyColumn, MyYear,
to_CHAR(TO_DATE(MyColumn default null on conversion error, 'DDD'),'MM/DD/YYYY') as Y2018,
to_CHAR(TO_DATE('2000' || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') as Y2000,
to_CHAR(TO_DATE(MyYear || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') as OK
from MyTable;
MYCOLUMN MYYEAR Y2018 Y2000 OK
---------- ---------- ---------- ---------- ----------
1 2018 01/01/2018 01/01/2000 01/01/2018
60 2016 03/01/2018 02/29/2000 02/29/2016
60 2017 03/01/2018 02/29/2000 03/01/2017
365 2016 12/31/2018 12/30/2000 12/30/2016
366 2016 12/31/2000 12/31/2016
365 2017 12/31/2018 12/30/2000 12/31/2017
I created a logic which would calculate the date as per the input day. When its not a leap year, it would display the date. Incase of leap year it would display the last day if day number is 366. If it's not a leap year, it would display the last day of the year if day number is 366, hence it would never result in the error you faced.
with tb(col1) as (Select level
from dual
connect by level < 367)
-- Actual Query ..Col1 is the day number being passed.
Select Case when col1 < 366 then
to_char(TO_DATE(col1, 'DDD'),'MM/DD/YYYY')
else
to_char(LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE , 'Year'),11)),'MM/DD/YYYY')
end col
from tb ;
DEMO