Pivot a Date Range into multiple rows - sql

I have a table T in this format:
ClientName
StartMonth
EndingMonth
X
Dec 2018
Jan 2021
I want the output of my query to be:
ClientName
MonthRange
Year #
X
Dec 2018-Nov 2019
1
X
Dec 2019-Nov 2020
2
X
Dec 2020-Nov 2021
3
Can someone help me what is the best way to tackle this problem?

Try this:
WITH
indata(clientname,startmonth,endmonth) AS(
SELECT 'x',DATE '2018-12-01', DATE '2021-01-01'
)
,
-- a series of at least 3 integers - no other way ...
y(y) AS (
SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
)
SELECT
clientname
, TO_CHAR(ADD_MONTHS(startmonth,(y-1)*12),'Mon-YYYY')
||'-'
||TO_CHAR(ADD_MONTHS(startmonth,(y-1)*12+11),'Mon-YYYY') AS monthrange
, y AS "year#"
FROM indata CROSS JOIN y
WHERE ADD_MONTHS(startmonth,(y-1)*12) <= endmonth
ORDER BY y;
clientname|monthrange |year#
x |Dec-2018-Nov-2019| 1
x |Dec-2019-Nov-2020| 2
x |Dec-2020-Nov-2021| 3

Related

SQL - Splitting a row with week range into multiple rows

I have the following table structure and data in the database table:
ID
Year
StartWeek
EndWeek
AllocationPercent
5
2021
34
35
50
6
2021
1
3
5
I need to split the multi-week rows into multiple single-week rows, and the end result should be:
ID
Year
StartWeek
EndWeek
AllocationPercent
5
2021
34
34
50
5
2021
35
35
50
6
2021
1
1
5
6
2021
2
2
5
6
2021
3
3
5
Any help with this would be highly appreciated! There are a lot of threads regarding splitting date ranges into multiple rows but I cannot seem to modify those to fit my use case. I know that most likely I need a tally table with the week numbers (which I already have).
Another way to think about this is, because we know the max weeknumber is 53, to generate the set of all possible week numbers, then outer join to that set each week in any source row that is within that range.
;WITH n(n) AS
(
SELECT 0 UNION ALL SELECT n+1 FROM n WHERE n <= 53
)
SELECT w.ID,
w.Year,
StartWeek = n.n,
EndWeek = n.n,
w.AllocationPercent
FROM n
INNER JOIN dbo.TableName AS w
ON n.n BETWEEN w.StartWeek AND w.EndWeek
ORDER BY w.ID, w.Year, n.n;
Results:
ID
Year
StartWeek
EndWeek
AllocationPercent
5
2021
34
34
50
5
2021
35
35
50
6
2021
1
1
5
6
2021
2
2
5
6
2021
3
3
5
Example db<>fiddle
You can use recursive cte :
;with cte as (
select t.id, t.year, t.startweek, t.endweek, t.AllocationPercent
from t
union all
select id, year, startweek + 1, endweek, AllocationPercent
from cte c
where startweek < endweek
)
select id, year, startweek, startweek as endweek, AllocationPercent
from cte
order by id, startweek, endweek;
db fiddle

Generate a range of records depending on from-to dates

I have a table of records like this:
Item
From
To
A
2018-01-03
2018-03-16
B
2021-05-25
2021-11-10
The output of select should look like:
Item
Month
Year
A
01
2018
A
02
2018
A
03
2018
B
05
2021
B
06
2021
B
07
2021
B
08
2021
Also the range should not exceed the current month. In example above we are asuming current day is 2021-08-01.
I am trying to do something similar to THIS with CONNECT BY LEVEL but as soon as I also select my table next to dual and try to order the records the selection never completes. I also have to join few other tables to the selection but I don't think that would make a difference.
I would very much appreciate your help.
Row generator it is, but not as you did it; most probably you're missing lines #11 - 16 in my query (or their alternative).
SQL> with test (item, date_from, date_to) as
2 -- sample data
3 (select 'A', date '2018-01-03', date '2018-03-16' from dual union all
4 select 'B', date '2021-05-25', date '2021-11-10' from dual
5 )
6 -- query that returns desired result
7 select item,
8 extract(month from (add_months(date_from, column_value - 1))) month,
9 extract(year from (add_months(date_from, column_value - 1))) year
10 from test cross join
11 table(cast(multiset
12 (select level
13 from dual
14 connect by level <=
15 months_between(trunc(least(sysdate, date_to), 'mm'), trunc(date_from, 'mm')) + 1
16 ) as sys.odcinumberlist))
17 order by item, year, month;
ITEM MONTH YEAR
----- ---------- ----------
A 1 2018
A 2 2018
A 3 2018
B 5 2021
B 6 2021
B 7 2021
B 8 2021
7 rows selected.
SQL>
Recursive CTEs are the standard SQL approach to this type of problem. In Oracle, this looks like:
with cte(item, fromd, tod) as (
select item, fromd, tod
from t
union all
select item, add_months(fromd, 1), tod
from cte
where add_months(fromd, 1) < last_day(tod)
)
select item, extract(year from fromd) as year, extract(month from fromd) as month
from cte
order by item, fromd;
Here is a db<>fiddle.

Forecasting Time series data in Oracle/SQL

Is there a way we can generate a time series forecasting for a data set using an Oracle analytical functions? How do we perform extrapolation in SQL/ORACLE.
Below is my need
I have data data set like below and I wanted to forecast/extrapolate for next year
Cust_id Year Revnue
1 2016 679862
1 2017 705365
1 2018 ?
2 2016 51074
2 2017 50611
2 2018 ?
3 2016 190706
3 2017 90393
3 2018 ?
4 2016 31649
4 2017 19566
4 2018 ?
You can create a simple forecast using the REGR linear regression functions.
--Ordinary least squares forecast for each customer for the next year.
select
cust_id,
max(year) +1 forecast_year,
-- y = mx+b
regr_slope(revenue, year)
* (max(year) + 1)
+ regr_intercept(revenue, year) forecasted_revenue
from customer_data
group by cust_id;
CUST_ID FORECAST_YEAR FORECASTED_REVENUE
------- ------------- ------------------
1 2018 730868
2 2018 50148
4 2018 7483
3 2018 -9920
Below is the sample schema. Or you can use this SQLFiddle.
create table customer_data
(
cust_id number,
year number,
revenue number
);
insert into customer_data
select 1, 2016, 679862 from dual union all
select 1, 2017, 705365 from dual union all
select 2, 2016, 51074 from dual union all
select 2, 2017, 50611 from dual union all
select 3, 2016, 190706 from dual union all
select 3, 2017, 90393 from dual union all
select 4, 2016, 31649 from dual union all
select 4, 2017, 19566 from dual;
The REGR function deals with number pairs, it doesn't understand business rules like "revenue can't be below 0". If you want to restrict the forecasts to always stay at or above 0, a CASE expression may help:
--Forecasted revenue, with minimum forecast of 0.
select cust_id, forecast_year,
case when forecasted_revenue < 0 then 0 else forecasted_revenue end forecasted_revenue
from
(
--Ordinary least squares forecast for each customer for the next year.
select
cust_id,
max(year) +1 forecast_year,
-- y = mx+b
regr_slope(revenue, year)
* (max(year) + 1)
+ regr_intercept(revenue, year) forecasted_revenue
from customer_data
group by cust_id
);
CUST_ID FORECAST_YEAR FORECASTED_REVENUE
------- ------------- ------------------
1 2018 730868
2 2018 50148
4 2018 7483
3 2018 0

Create a year/quarter table in SQL without a loop

I have a start year and an end year, say 2017 and 2019 for example.
I'd like to create a table with columns year and quarter (eg, 1, 2, 3, 4) between my stated startYear and endYear, and have quarter for the final, endYear, to stop at 2 (it's always forward looking).
Sample desired output below.
year quarter
2017 1
2017 2
2017 3
2017 4
2018 1
2018 2
2018 3
2018 4
2019 1
2019 2
Seems like it should be simple, nothing occurs to me except somewhat clunky methods relying on a loop or UNION or simply inserting values manually into the table.
Just another option... an ad-hoc tally table in concert with a Cross Join
Example
Declare #Y1 int = 2017
Declare #Y2 int = 2019
Select *
From ( Select Top (#Y2-#Y1+1) Year=#Y1-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1 ) A
Cross Join (values (1),(2),(3),(4)) B([Quarter])
Returns
Year Quarter
2017 1
2017 2
2017 3
2017 4
2018 1
2018 2
2018 3
2018 4
2019 1
2019 2
2019 3
2019 4
Use a recursive CTE:
with yq as (
select 2017 as yyyy, 1 as qq
union all
select (case when qq = 4 then yyyy + 1 else yyyy end),
(case when qq = 4 then 1 else qq + 1 end)
from yq
where yyyy < 2019 or yyyy = 2019 and qq < 2
)
select *
from yq;
If the table will have more than 100 rows, you will also need option (maxrecursion 0).
Here is a db<>fiddle.
This solution is very similar to the one by John, but it doesn't depend on a system table.
Declare #Y1 int = 2017;
Declare #Y2 int = 2019;
WITH
E(n) AS(
SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
SELECT a.n FROM E a, E b
),
E4(n) AS(
SELECT a.n FROM E2 a, E2 b
),
cteYears([Year]) AS(
SELECT TOP (#Y2-#Y1+1)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + #Y1 - 1 AS [Year]
FROM E4
)
SELECT [Year], [Quarter]
FROM cteYears
CROSS JOIN (VALUES (1),(2),(3),(4)) Q([Quarter]);
Let me to propose a recursve query for you:
WITH prepare AS
(
SELECT tbl.year
FROM (VALUES (2017) ) AS tbl(year) -- for example, start year is 2k17
UNION ALL
SELECT year + 1
FROM prepare
WHERE year < 2030 -- and last year is 2030
)
SELECT
year, quarter
FROM prepare
CROSS JOIN ( VALUES (1), (2), (3), (4) ) AS tbl (quarter)

How to use lead lag function in oracle

I have written some query to get my resultant result as below :
Note: I have months starting from jan-2016 to jan-2018.
There are two types, either 'hist' or 'future'
Resultant dataset :
In this example : let consider combination of id1+id2+id3 as 1,2,3
type month id1 id2 id3 value
hist jan-17 1 2 3 10
hist feb-17 1 2 3 20
future jan-17 1 2 3 15
future feb-17 1 2 3 1
hist mar-17 1 2 3 2
future apr-17 1 2 3 5
My calculation logic depends on the quarter number of month .
For eg . for month of january(first month of quarter) i want the value to be : future of jan + future value of feb + future value of march .
so for jan-17 , output should be : 15+1 + 0(for march there is no corresponding future value)
for the month of feb (2nd month of quarter), value should be : hist of jan + future of feb + future of march i.e 10+1+0(future of march is not available)
Similarly for the month of march , value should be : history of jan + history of feb + future of march i.e 10+20+0(frecast of march no present) .
similarly for april,may.june(depending on quarter number of month)
I am aware of the lead lag function , but I am not able to apply it here
Can someone please help
I would not mess with lag, this can all be done with a group by if you convert your dates to quarters:
WITH
dset
AS
(SELECT DATE '2017-01-17' month, 5 VALUE
FROM DUAL
UNION ALL
SELECT DATE '2017-02-17' month, 6 VALUE
FROM DUAL
UNION ALL
SELECT DATE '2017-03-25' month, 7 VALUE
FROM DUAL
UNION ALL
SELECT DATE '2017-05-25' month, 4 VALUE
FROM DUAL)
SELECT SUM (VALUE) value_sum, TO_CHAR (month, 'q') quarter, TO_CHAR (month, 'YYYY') year
FROM dset
GROUP BY TO_CHAR (month, 'q'), TO_CHAR (month, 'YYYY');
This results in:
VALUE_SUM QUARTER YEAR
18 1 2017
4 2 2017
We can use an analytic function if you need the result on each record:
SELECT SUM (VALUE) OVER (PARTITION BY TO_CHAR (month, 'q'), TO_CHAR (month, 'YYYY')) quarter_sum, month, VALUE
FROM dset
This results in:
QUARTER_SUM MONTH VALUE
18 1/17/2017 5
18 2/17/2017 6
18 3/25/2017 7
4 5/25/2017 4
Make certain you include year, you don't want to combine quarters from different years.
Well, as said in one of the comments.. the trick lies in another question of yours & the corresponding answer. Well... it goes somewhat like this..
with
x as
(select 'hist' type, To_Date('JAN-2017','MON-YYYY') ym , 10 value from dual union all
select 'future' type, To_Date('JAN-2017','MON-YYYY'), 15 value from dual union all
select 'future' type, To_Date('FEB-2017','MON-YYYY'), 1 value from dual),
y as
(select * from x Pivot(Sum(Value) For Type in ('hist' as h,'future' as f))),
/* Pivot for easy lag,lead query instead of working with rows..*/
z as
(
select ym,sum(h) H,sum(f) F from (
Select y.ym,y.H,y.F from y
union all
select add_months(to_Date('01-JAN-2017','DD-MON-YYYY'),rownum-1) ym, 0 H, 0 F
from dual connect by rownum <=3 /* depends on how many months you are querying...
so this dual adds the corresponding missing 0 records...*/
) group by ym
)
select
ym,
Case
When MOD(Extract(Month from YM),3) = 1
Then F + Lead(F,1) Over(Order by ym) + Lead(F,2) Over(Order by ym)
When MOD(Extract(Month from YM),3) = 2
Then Lag(H,1) Over(Order by ym) + F + Lead(F,1) Over(Order by ym)
When MOD(Extract(Month from YM),3) = 3
Then Lag(H,2) Over(Order by ym) + Lag(H,1) Over(Order by ym) + F
End Required_Value
from z