Get the last 4 weeks prior to current week - sql

I have a table that has DATE_VALUE, FISCAL_WEEK, FISCAL_YEAR
DATE_VALUE FISCAL_WEEK FISCAL_YEAR
24-DEC-21 52 2021
25-DEC-21 52 2021
26-DEC-21 52 2021
27-DEC-21 53 2021
28-DEC-21 53 2021
29-DEC-21 53 2021
30-DEC-21 53 2021
31-DEC-21 53 2021
01-JAN-22 53 2021
02-JAN-22 53 2021
03-JAN-22 1 2022
04-JAN-22 1 2022
05-JAN-22 1 2022
06-JAN-22 1 2022
07-JAN-22 1 2022
08-JAN-22 1 2022
09-JAN-22 1 2022
10-JAN-22 2 2022
11-JAN-22 2 2022
12-JAN-22 2 2022
13-JAN-22 2 2022
14-JAN-22 2 2022
The table goes on for the entire FY 2021 & 2022
I want to get the last 4 fiscal weeks (FW) prior to the current week. Let's assume this week is FW20 FY2022, I am able to get this result:
FISCAL_WEEK FISCAL_YEAR
16 2022
17 2022
18 2022
19 2022
The code used to return the above output is:
SELECT
*
FROM
(
WITH t AS (
SELECT
fiscal_week - 1 lastweek,
fiscal_week - 5 week_x,
fiscal_year
FROM
TABLE
WHERE
Trunc(date_value) = Trunc(sysdate)
)
SELECT
DISTINCT fiscal_week,
t.fiscal_year
FROM
TABLE
OUTER JOIN t ON fiscal_week <> week_x
WHERE
to_char(fiscal_week) BETWEEN lastweek - 4
AND lastweek
ORDER BY
fiscal_week
)
But if the current week was FW04 FY2022, the code above is not able to return this desired output.
FISCAL_WEEK FISCAL_YEAR
53 2021
1 2022
2 2022
3 2022
Similarly, if the current was FY03 FY2022, I want the output to be:
FISCAL_WEEK FISCAL_YEAR
52 2021
53 2021
1 2022
2 2022
How do I need to write the code to get this output? Maybe the case statement could work but I'd like to see if there's any other workaround? Any help would be appreciated.
Thank you!

You may put the condition on the date value not the week to get the required output, then use OFFSET 1 to skip the current week and fetch the next 4 rows only. Try the following:
WITH T AS
(
SELECT DISTINCT fiscal_week, fiscal_year
FROM TABLE_NAME
WHERE Trunc(date_value) <= Trunc(SYSDATE)
ORDER BY fiscal_year DESC, fiscal_week DESC
OFFSET 1 ROWS
FETCH NEXT 4 ROWS ONLY
)
SELECT fiscal_week, fiscal_year
FROM T ORDER BY fiscal_year, fiscal_week
See a demo.

Don't use the FISCAL_WEEK and FISCAL_YEAR columns; just use the DATE column and compare it to a range based on the start of the ISO week:
SELECT DISTINCT fiscal_week, fiscal_year
FROM table_name
WHERE "DATE" < TRUNC(SYSDATE, 'IW')
AND "DATE" >= TRUNC(SYSDATE, 'IW') - INTERVAL '28' DAY
ORDER BY fiscal_year, fiscal_week;
Which, for the sample data:
Create Table table_name("DATE", FISCAL_WEEK, FISCAL_YEAR) AS
SELECT DATE '2021-12-24', 52, 2021 FROM DUAL UNION ALL
SELECT DATE '2021-12-25', 52, 2021 FROM DUAL UNION ALL
SELECT DATE '2021-12-26', 52, 2021 FROM DUAL UNION ALL
SELECT DATE '2021-12-27', 53, 2021 FROM DUAL UNION ALL
SELECT DATE '2021-12-28', 53, 2021 FROM DUAL UNION ALL
SELECT DATE '2021-12-29', 53, 2021 FROM DUAL UNION ALL
SELECT DATE '2021-12-30', 53, 2021 FROM DUAL UNION ALL
SELECT DATE '2021-12-31', 53, 2021 FROM DUAL UNION ALL
SELECT DATE '2022-01-01', 53, 2021 FROM DUAL UNION ALL
SELECT DATE '2022-01-02', 53, 2021 FROM DUAL UNION ALL
SELECT DATE '2022-01-03', 1, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-04', 1, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-05', 1, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-06', 1, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-07', 1, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-08', 1, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-09', 1, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-10', 2, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-11', 2, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-12', 2, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-13', 2, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-01-14', 2, 2022 FROM DUAL UNION ALL
SELECT DATE '2022-02-14', 6, 2022 FROM DUAL;
If SYSDATE was 2022-01-17, would output:
FISCAL_WEEK
FISCAL_YEAR
52
2021
53
2021
1
2022
2
2022
fiddle

Related

Generate rows to fill in gaps between years, carry over a value from previous year

I have a table of road condition ratings (roads are rated from 1-20; 20 being good).
with road_inspections
(road_id, year, cond) as (
select 1, 2009, 17 from dual union all
select 1, 2011, 16 from dual union all
select 1, 2015, 14 from dual union all
select 1, 2016, 18.3 from dual union all
select 1, 2019, 18.1 from dual union all
select 2, 2013, 17.5 from dual union all
select 2, 2016, 18 from dual union all
select 2, 2019, 18 from dual union all
select 2, 2022, 18 from dual union all
select 3, 2022, 20 from dual)
select * from road_inspections
ROAD_ID YEAR COND
---------- ---------- ----------
1 2009 17
1 2011 16
1 2015 14
1 2016 18.3
1 2019 18.1
2 2013 17.5
2 2016 18
2 2019 18
2 2022 18
3 2022 20
db<>fiddle
In a query, for each road, I want to generate rows to fill in the gaps between the years.
For a given road, starting at the first row (the earliest inspection), there should be consecutive rows for each year all the way to the current year (the sysdate year; currently 2022).
For the filler rows, I want carry over the condition rating from the last known inspection.
The result would look like this:
ROAD_ID YEAR COND
---------- ---------- ----------
1 2009 17
1 2010 17 *
1 2011 16
1 2012 16 *
1 2013 16 *
1 2014 16 *
1 2015 14
1 2016 18.3
1 2017 18.3 *
1 2018 18.3 *
1 2019 18.1
1 2020 18.1 *
1 2021 18.1 *
1 2022 18.1 *
2 2013 17.5
2 2014 17.5 *
2 2015 17.5 *
2 2016 18
2 2017 18 *
2 2018 18 *
2 2019 18
2 2020 18 *
2 2021 18 *
2 2022 18
3 2022 20
*=filler row
Question:
How can I create those filler rows using an Oracle SQL query?
(My priorities are: simplicity first, performance second.)
You can use the LEAD analytic function with a LATERAL joined hierarchical query to generate the missing rows from each row until the next row:
SELECT r.road_id,
y.year,
r.cond
FROM ( SELECT r.*,
LEAD(year, 1, EXTRACT(YEAR FROM SYSDATE) + 1)
OVER (PARTITION BY road_id ORDER BY year) AS next_year
FROM road_inspections r
) r
CROSS JOIN LATERAL (
SELECT r.year + LEVEL - 1 AS year
FROM DUAL
CONNECT BY r.year + LEVEL - 1 < r.next_year
) y
Which, for the sample data:
CREATE TABLE road_inspections (road_id, year, cond) as
select 1, 2009, 17 from dual union all
select 1, 2011, 16 from dual union all
select 1, 2015, 14 from dual union all
select 1, 2016, 18.3 from dual union all
select 1, 2019, 18.1 from dual union all
select 2, 2013, 17.5 from dual union all
select 2, 2016, 18 from dual union all
select 2, 2019, 18 from dual union all
select 2, 2022, 18 from dual union all
select 3, 2022, 20 from dual;
Outputs:
ROAD_ID
YEAR
COND
1
2009
17
1
2010
17
1
2011
16
1
2012
16
1
2013
16
1
2014
16
1
2015
14
1
2016
18.3
1
2017
18.3
1
2018
18.3
1
2019
18.1
1
2020
18.1
1
2021
18.1
1
2022
18.1
2
2013
17.5
2
2014
17.5
2
2015
17.5
2
2016
18
2
2017
18
2
2018
18
2
2019
18
2
2020
18
2
2021
18
2
2022
18
3
2022
20
db<>fiddle here
with
road_inspections (road_id, year_, cond) as (
select 1, 2009, 17 from dual union all
select 1, 2011, 16 from dual union all
select 1, 2015, 14 from dual union all
select 1, 2016, 18.3 from dual union all
select 1, 2019, 18.1 from dual union all
select 2, 2013, 17.5 from dual union all
select 2, 2016, 18 from dual union all
select 2, 2019, 18 from dual union all
select 2, 2022, 18 from dual union all
select 3, 2022, 20 from dual
)
, prep (road_id, first_year) as (
select road_id, min(year_)
from road_inspections
group by road_id
)
, all_years (road_id, year_) as (
select p.road_id, l.year_
from prep p cross join lateral (
select first_year + level - 1 as year_
from dual
connect by level <= 2022 - first_year + 1
) l
)
select road_id, year_,
last_value(ri.cond ignore nulls) over
(partition by road_id order by year_) as cond
from all_years ay left outer join road_inspections ri using (road_id, year_)
;
The first subquery, prep, finds the first year for each road id. This is used in the all_years subquery to generate all the years relevant for each road id.
Then left-outer-join to the original cata, copy the cond wherever it is available, and use the analytic function last_value with the ignore nulls option to fill in the gaps.
Note that I changed the column name year to year_ (with a trailing underscore); year is an Oracle keyword, not a good choice for a column name.
Output:
ROAD_ID YEAR_ COND
---------- ---------- ----------
1 2009 17
1 2010 17
1 2011 16
1 2012 16
1 2013 16
1 2014 16
1 2015 14
1 2016 18.3
1 2017 18.3
1 2018 18.3
1 2019 18.1
1 2020 18.1
1 2021 18.1
1 2022 18.1
2 2013 17.5
2 2014 17.5
2 2015 17.5
2 2016 18
2 2017 18
2 2018 18
2 2019 18
2 2020 18
2 2021 18
2 2022 18
3 2022 20
Using LEAD function and connect by LEVEL row generator we can achieve the same. The DB FIDDLE here
with r as (
select
*
from
road_inspections
union
select
road_id,
2022,
cond
from
road_inspections
where
(road_id, year) in(
select
road_id,
max(year) over (partition by road_id)
from
road_inspections a
where
not exists (
select
1
from
road_inspections b
where
a.road_id = b.road_id
and b.year = 2022
)
)
),
data as(
SELECT
r.*,
nvl(
lead(year, 1) over (
partition by road_id
order by
year
)- year,
0
) gaps
FROM
r
)
select
road_id,
year + level -1 year,
cond
from
(
select
a.road_id,
year,
cond,
rownum rn,
gaps
from
data a
) connect by level <= gaps
and prior rn = rn
and prior dbms_random.value != 1
order by
road_id,
year + level -1;

How to group sales by month, quarter and year in the same row using case?

I'm trying to return the total number of sales for every month, every quarter, for the year 2016. I want to display annual sales on the first month row, and not on the other rows. Plus, I want to display the quarter sales on the first month of each quarter, and not on the others.
To further explain this, here's what I want to achieve:
MONTH MONTH_SALES QUARTER_SALES YEAR_SALES
1 2183 5917 12505
2 1712 - -
3 1972 - -
4 2230 6588 -
5 2250 - -
6 2108 - -
Here's my SQL query so far:
SELECT
Time.month,
SUM(Sales.sales) AS MONTH_SALES, -- display monthly sales.
CASE
WHEN MOD(Time.month, 3) = 1 THEN ( -- first month of quarter
SELECT
SUM(Sales.sales)
FROM
Sales,
Time
WHERE
Sales.Time_id = Time.Time_id
AND Time.year = 2016
GROUP BY
Time.quarter
FETCH FIRST 1 ROW ONLY
)
END AS QUARTER_SALES,
CASE
WHEN Time.month = 1 THEN ( -- display annual sales.
SELECT
SUM(Sales.sales)
FROM
Sales,
Time
WHERE
Sales.Time_id = Time.Time_id
AND Time.year = 2016
GROUP BY
Time.year
)
END AS YEAR_SALES
FROM
Sales,
Time
WHERE
Sales.Time_id = Time.Time_id
AND Time.year = 2016
GROUP BY
Time.month
ORDER BY
Time.month
I'm almost getting the desired output, but I'm getting the same duplicated 6588 value in quarter sales for the first and fourth month (because I'm fetching the first row that comes from first quarter).
MONTH MONTH_SALES QUARTER_SALES YEAR_SALES
1 2183 6588 12505
2 1712 - -
3 1972 - -
4 2230 6588 -
5 2250 - -
6 2108 - -
I even tried to put WHERE Time.quarter = ((Time.month * 4) / 12) but the month value from the outer query doesn't get passed in the subquery.
Unfortunately I don't have enough experience with CASE WHEN expressions to know how to pass the month row. Any tips would be awesome.
How about this?
Sample data:
SQL> with
2 time (time_id, month, quarter, year) as
3 (select 1, 1, 1, 2016 from dual union all
4 select 2, 2, 1, 2016 from dual union all
5 select 3, 3, 1, 2016 from dual union all
6 select 4, 5, 2, 2016 from dual union all
7 select 5, 7, 3, 2016 from dual union all
8 select 6, 8, 3, 2016 from dual union all
9 select 7, 9, 3, 2016 from dual union all
10 select 8, 10, 4, 2016 from dual union all
11 select 9, 11, 4, 2016 from dual
12 ),
13 sales (time_id, sales) as
14 (select 1, 100 from dual union all
15 select 1, 100 from dual union all
16 select 2, 200 from dual union all
17 select 3, 300 from dual union all
18 select 4, 400 from dual union all
19 select 5, 500 from dual union all
20 select 6, 600 from dual union all
21 select 7, 700 from dual union all
22 select 8, 800 from dual union all
23 select 9, 900 from dual
24 ),
Query begins here; it uses sum aggregate in its analytic form; partition by clause says what to compute. row_number, similarly, sorts rows in each quarter/year - it is later used in CASE expression to decide whether to show quarterly/yearly total or not.
25 temp as
26 (select t.month, t.quarter, t.year, sum(s.sales) month_sales
27 from time t join sales s on s.time_id = t.time_id
28 where t.year = 2016
29 group by t.month, t.quarter, t.year
30 ),
31 temp2 as
32 (select month, quarter, month_sales,
33 sum(month_sales) over (partition by quarter) quarter_sales,
34 sum(month_sales) over (partition by year ) year_sales,
35 row_number() over (partition by quarter order by quarter) rnq,
36 row_number() over (partition by year order by null) rny
37 from temp
38 )
39 select month,
40 month_sales
41 case when rnq = 1 then quarter_sales end month_sales,
42 case when rny = 1 then year_sales end year_sales
43 from temp2
44 order by month;
MONTH MONTH_SALES QUARTER_SALES YEAR_SALES
---------- ---------- ----------- ----------
1 200 700 4600
2 200
3 300
4 400 1500
5 500
6 600
7 700 2400
8 800
9 900
9 rows selected.
SQL>

find gap between months in two consecutive year oracle sql

Need to find record having gap between months in a table if the data is present in two different year.
I have column like id, value,month, year.
Id, value, month,year
1, 123, oct, 2020
1, 128, nov, 2020
1, 127, jan ,2021
2, 121, Dec, 2020
2, 154, jan, 2021
Output I need:
Id 1 as there is a gap in month (Dec is Missing for id=1)
Here's one option. Read comments within code.
SQL> with test (id, value, month, year) as
2 -- sample data; you have that, don't type it
3 (select 1, 123, 'oct', 2020 from dual union all
4 select 1, 128, 'nov', 2020 from dual union all
5 select 1, 127, 'jan', 2021 from dual union all
6 select 2, 121, 'dec', 2020 from dual union all
7 select 2, 154, 'jan', 2021 from dual
8 ),
9 temp as
10 -- "convert" month and year to real date value
11 (select id,
12 value,
13 to_date(month ||' '|| year, 'mon yyyy', 'nls_date_language=english') datum
14 from test
15 ),
16 temp2 as
17 -- select difference in months between DATUM and next month (LEAD!)
18 (select id,
19 months_between
20 (datum,
21 to_date(month ||' '|| year, 'mon yyyy', 'nls_date_language=english') datum
22 ) diff
23 from temp
24 )
25 select distinct id
26 from temp2
27 where abs(diff) > 1;
ID
----------
1
SQL>
It can probably be compressed, but step-by-step CTEs show what's going on.
I would construct a date and use lag():
select t.*
from (select t.*,
lag(dte) over (partition by id order by dte) as prev_dte
from (select t.*,
to_date(year || '-' || month || '-01', 'YYYY-MON-DD') as dte
from t
) t
) t
where prev_dte <> dte - interval '1' month;
Here is a db<>fiddle.
Here is an example using the LAG function and finding rows where where the prior month is not one month behind (or non existent)
WITH
sample_data (Id,
VALUE,
month,
year)
AS
(SELECT 1, 123, 'oct', 2020 FROM DUAL
UNION ALL
SELECT 1, 128, 'nov', 2020 FROM DUAL
UNION ALL
SELECT 1, 127, 'jan', 2021 FROM DUAL
UNION ALL
SELECT 2, 121, 'Dec', 2020 FROM DUAL
UNION ALL
SELECT 2, 154, 'jan', 2021 FROM DUAL)
SELECT DISTINCT id
FROM (SELECT sd.id,
CASE
WHEN ADD_MONTHS (TO_DATE (sd.year || sd.month, 'YYYYMON'), -1) =
TO_DATE (
LAG (sd.year || sd.month)
OVER (
PARTITION BY id
ORDER BY
sd.year, EXTRACT (MONTH FROM TO_DATE (sd.month, 'MON'))),
'YYYYMON')
OR LAG (sd.id)
OVER (
PARTITION BY id
ORDER BY sd.year, EXTRACT (MONTH FROM TO_DATE (sd.month, 'MON')))
IS NULL
THEN
'Y'
ELSE
'N'
END AS valid_prev_month
FROM sample_data sd)
WHERE valid_prev_month = 'N';

Query Optimization for my code in Oracle SQL

Here is my code:
select
(case when c.yr = 2019 and c.mon = 10 then 'October 2019'
when c.yr = 2019 and c.mon = 11 then 'November 2019'
when c.yr =2019 and c.mon = 12 then 'December 2019' end) as dae
from (
select substr(d,-4,4) as yr, substr(d,1,2) as mon
from
(select '10/11/2019' as d from dual) )c;
`
So I don't want to hard code the dates for the next 5 years, Is there a function that makes this easier.
Here is the Sample Input I want to try
10/11/2019
11/11/2019
12/11/2019
01/11/2020
Expected Output
October 2019
November 2019
December 2019
January 2020
You could use to_date() to turn your string to a date, and then convert it back to a string in the desired format with to_char():
to_char(to_date(d, 'mm/dd/yyyy'), 'Month yyyy')
Demo on DB Fiddle:
with t as (
select '10/11/2019' d from dual
union all select '11/11/2019' from dual
union all select '12/11/2019' from dual
union all select '01/11/2020' from dual
)
select to_char(to_date(d, 'mm/dd/yyyy'), 'Month yyyy') new_dt from t
| NEW_DT |
| :------------- |
| October 2019 |
| November 2019 |
| December 2019 |
| January 2020 |
Use connect by to generate as many dates as you want. Here the gen_dates CTE starts with your start_date and returns a total of 4 months per your example. To increase the number of months to generate, increase the number 4 to a higher number.
with gen_dates(date_in) as (
select add_months('11-OCT-2019', level -1) date_in
from dual
connect by level <= 4
)
select date_in, to_char(date_in, 'Month yyyy') date_out
from gen_dates;
DATE_IN DATE_OUT
--------- --------------
11-OCT-19 October 2019
11-NOV-19 November 2019
11-DEC-19 December 2019
11-JAN-20 January 2020
4 rows selected.

oracle month to day

I have data as below in which data is splitted as below where month 1 represent JAN ,Month 2 represent FEB and so on.
MONTH VALUE
1 93
2 56
3 186
4 60
Now I need to calculate sum of value based on number of days.
Ex- If date is between Jan 1st to March 31st then SUM is 335(93+56+186)
If date is between Jan 17th to March 31st then sum is 287(45+56+186) .
Here 45 for month of january is dervied from average per day for month of JAN (93/31==3) .Multiplied by number of days in January for the required period.
Ignore leap year and FEB month can be taken 28 days always.
As one of the approaches, you can turn a month into a list of days(dates) that constitute it (ease filtering operation), and perform calculation as follows:
/* sample of data that you've provided */
with t1(mnth,val) as(
select 1, 93 from dual union all
select 2, 56 from dual union all
select 3, 186 from dual union all
select 4, 60 from dual
),
/*
Generates current year dates
From January 1st 2014 to December 31st 2014
*/
dates(dt) as(
select trunc(sysdate, 'YEAR') - 1 + level
from dual
connect by extract(year from (trunc(sysdate, 'YEAR') - 1 + level)) <=
extract(year from sysdate)
)
/*
The query that performs calculations based on range of dates
*/
select sum(val / extract(day from last_day(dt))) as result
from dates d
join t1
on (extract(month from d.dt) = t1.mnth)
where dt between date '2014-01-17' and -- January 17th 2014 to
date '2014-03-31' -- March 31st 2014
Result:
RESULT
----------
287