Need SQL to generate multiple rows from subquery - sql

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

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

Assign year to months of past 6 months

From below data, how do i assign year in column DS_YEAR where Year is not assigned based on DS_MONTH.
DS_MONTH| DS_DAY| DS_YEAR
--------|---------|--------
Mar | 2 | 2019
Jan | 4 | 2020
Apr | 2 | 07:43
Sep | 1 | 06:00
Jul | 2 | 05:00
Dec | 4 | 2019
Feb | 7 | 2020
Nov | 9 | 2019
From the above data; any data that is between now and past 6 months has a TIME instead of YEAR. However i want to write a query to attach the respective year instead of time.
I have below query which will attach the current year, however in case our data will lie in the months transitioning previous year it wont be accurate.
SELECT
DS_MONTH || '-' || DS_DAY || '-' ||
CASE
WHEN DS_YEAR LIKE '%:%' THEN TO_CHAR(sysdate, 'YYYY')
ELSE DS_YEAR
END dsf
FROM
MY_TABLE
How do i check the month whether it lies in current year or previous year, so that i can assign the correct year?
Example: if today was FEBRUARY. and in my data i have Sep | 1 | 06:00 then my query should return Sep-1-2019
and if today was OCTOBER it should return Sep-1-2020
Is this what you want ?
WITH sampledata (mon, d, yt) AS
(
SELECT 'Mar','2','2019' FROM DUAL UNION
SELECT 'Jan','4','2020' FROM DUAL UNION
SELECT 'Apr','2','07:43' FROM DUAL UNION
SELECT 'Sep','1','06:00' FROM DUAL UNION
SELECT 'Jul','2','05:00' FROM DUAL UNION
SELECT 'Dec','4','2019' FROM DUAL UNION
SELECT 'Feb','7','2020' FROM DUAL UNION
SELECT 'Nov','9','2019' FROM DUAL
),rundate (dt) AS
(
SELECT DATE'2021-05-30' FROM DUAL
)
SELECT s.mon, s.d, s.yt,
CASE WHEN TO_DATE(s.mon||'-'||s.d||'-'|| extract(year from r.dt),'Mon-dd-YYYY') > r.dt THEN TO_DATE(s.mon||'-'||s.d||'-'|| extract(year from ADD_MONTHS(r.dt,-12)),'Mon-dd-YYYY')
ELSE TO_DATE(s.mon||'-'||s.d||'-'|| extract(year from r.dt),'Mon-dd-YYYY')
END
FROM sampledata s CROSS JOIN rundate r
WHERE INSTR(s.yt,':') > 0
UNION
SELECT s.mon, s.d, s.yt, TO_DATE(s.mon||'-'||s.d||'-'|| s.yt,'Mon-dd-YYYY')
FROM sampledata s
WHERE INSTR(s.yt,':') = 0;
UPDATE: Added the 'rundate' CTE so you can test with any date, not just sysdate. Also added the case expression to check ensure the date is in the past.
This is just pseudo code, may require few changes in date/month conversion as per db syntax
SELECT
DS_MONTH || '-' || DS_DAY || '-' ||
CASE
WHEN DS_YEAR LIKE '%:%' and Month(sysdate)>6 THEN TO_CHAR(sysdate, 'YYYY')
WHEN DS_YEAR LIKE '%:%' and Month(cast(DS_MONTH+'1 2015' as datetime)) < 6 THEN TO_CHAR(sysdate, 'YYYY')
WHEN DS_YEAR LIKE '%:%' and Month(cast(DS_MONTH+'1 2015' as datetime)) > 6 THEN TO_CHAR(DATEADD(YEAR, -1, GETDATE()), 'YYYY')
ELSE DS_YEAR
END dsf
FROM
MY_TABLE

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

Date and quarter SQL ORACLE

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

SSRS PIVOT TABLE QUERY to return days absent by month

I have an SQL query with
startdate 23/09/2013
enddate 06/10/2013
days_absent 14
and I need to show the days _absent in a pivot table as Sep 7 and Oct 7
instead of Sep 14
Month Days_Absent
------------------------
SEP 7
OCT 7
Here is the query to populate the report:
SELECT TO_CHAR(ACCINJ_VIEW.STARTDATEOFABSENCE,'DD/MM/YYYY') AS "Start Date of Absence" ,
TO_CHAR(ACCINJ_VIEW.ENDDATEOFABSENCE,'DD/MM/YYYY') AS "End Date of Absence" , ACCINJ_VIEW.DAYSABSENT AS "Days Absent" ,
FROM ACCINJ_VIEW ACCINJ_VIEW,
WHERE ACCINJ_VIEW.IDPERSONUNIQUEKEY = INJG_VIEW.IPERSONUNIQUEKEY(+) AND ov_VIEW.ACCDATE BETWEEN :ACCDATESTART AND :ACCDATEEND
I think this might work
WITH all_days AS
(
SELECT first_day + LEVEL - 1 AS a_day
FROM (
SELECT TO_DATE ( '05/08/2011' , 'DD/MM/YYYY') AS first_day, -- Start Date
TO_DATE ( '16/10/2011' , 'DD/MM/YYYY') AS last_day -- End Date
FROM dual
)
CONNECT BY LEVEL <= last_day + 1 - first_day
)
SELECT TO_CHAR (TRUNC(a_day,'MONTH'),'FMMonth YYYY') AS month,
COUNT (*) AS day
FROM all_days
GROUP BY TRUNC (a_day, 'MONTH')
ORDER BY TRUNC (a_day, 'MONTH')
/
Result:
MONTH DAY
-------------- ----------
August 2011 27
September 2011 30
October 2011 16