Forecasting Time series data in Oracle/SQL - 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

Related

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.

Pivot a Date Range into multiple rows

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

Ways to find Percentage increase in sales Month over Month

I want to find the percentage increase in Month over month sales amount using SQL Server. I want to find % MoM increase in sales by using self join and also using partition with rows unbounded preceding. I do not want to use lag(). Can anyone let me know about the the ways to generate this solution.
Here is my table.
create table growth_new(slno bigint,mon varchar(30),sales_amount bigint)
insert into growth_new values(1, 'Jan', 5000)
insert into growth_new values(2, 'Feb', 12000)
insert into growth_new values(3, 'Mar', 32000)
insert into growth_new values(4, 'Apr', 20000)
Slno Mon sales_amount
1 Jan 5000
2 Feb 12000
3 Mar 32000
4 Apr 20000
You can use lag(). If slno orders the rows, then:
select gn.*,
(gn.sales_amount * 1.0 / lag(gn.sales_amount) over (order by slno)) - 1 as increase
from growth_new gn;
A self-join doesn't really make sense for this problem. But if you really needed to with this data structure:
with gn as (
select gn.*, convert(date, month + ' 2000') as mm
from growth_new
)
select gn.*,
(gn.sales_amount * 1.0 / gnprev.sales_amount) - 1
from gn left join
gn gnprev
on gnprev.mm = dateadd(month, -1, gn.mm);
You should, however, really fix the data so the month is in a reasonable format.
If you don't want to use the LEAD or LAG, you can use the following:
I assumed that you can compare using the ids, otherwise, you can have a table to store the months Ids
selecT g.*, growth = 100*cast(iif(p.sales_amount is null,0,(g.sales_amount-p.sales_amount)*1.0/p.sales_amount) as money)
from growth_new g
left join growth_new p on p.slno=g.slno-1
the output is:
slno mon sales_amount growth
1 Jan 5000 0.00
2 Feb 12000 140.00
3 Mar 32000 166.67
4 Apr 20000 -37.50
Hope this helps you
You could use the lag function really unless you want to try other alternatives. Also as mentioned above your month format is not ideal and not scalable at all.
WITH growth_new(slno ,mon ,sales_amount)
AS (SELECT 1, 'Jan', 5000 UNION
SELECT 2, 'Feb', 12000 UNION
SELECT 3, 'Mar', 32000 UNION
SELECT 4, 'Apr', 20000
)
SELECT cur.*, prev.mon as prev_month,
ISNULL(prev.sales_amount,0) AS prev_month_sales_amount,
[%MoM Change] = ((cur.sales_amount -
ISNULL(prev.sales_amount,0))/CAST(prev.sales_amount as float))*100
FROM growth_new cur
LEFT JOIN growth_new prev ON prev.slno = cur.slno - 1
slno mon sales_amount prev_month prev_month_sales_amount %MoM Change
1 Jan 5000 NULL 0 NULL
2 Feb 12000 Jan 5000 140
3 Mar 32000 Feb 12000 166.666666666667
4 Apr 20000 Mar 32000 -37.5

Querying an SQL table to get all new values in a column according to filter in another column

Sorry I couldn't make the question clearer, let me explain it here.
I have a table that has two columns:
year, ID
------------
2013, 01
2013, 27
2013, 33
2014, 22
2014, 33
2014, 01
2014, 13
2015, 45
2015, 13
2015, 22
What I want to do is the following: check all new IDs that appeared in 2014 from 2013, and all new IDs that appeared in 2015 from 2014, and so on and so forth...
So, from the example above, the expected answer to my query should be a table like:
year, new
-------------
2014, 2
2015, 1
Since in 2014 there are two new IDs in relation to 2013 (22 and 13), and in 2015 there is one new ID in relation to 2014 (45).
Sorry for the horrible formatting, I'm new to this site. Any help is appreciated, thanks.
If I understand correctly, you want lag():
select year, count(*)
from (select t.*, lag(year) over (partition by id order by year) as prev_year
from t
) t
where prev_year is null or prev_year < year - 1
group by year;
You need to find the all codes that didn't exist with a year y - 1 that shower at year y:
with cte as
(
select cast(2013 as int) as year, '01' as id
union select 2013, 27
union select 2013, 33
union select 2014, 22
union select 2014, 33
union select 2014, '01'
union select 2014, 13
union select 2015, 45
union select 2015, 13
union select 2015, 22
)
select year, count(*) from cte a where not exists(select 1 from cte b where a.year -1 = b.year and a.id = b.id)
and a.year -1 in (select distinct year from cte)
group by year

Convert columns data into rows in PostgreSQL

I have data in the following format.
order_no rate jan feb mar ....
1 1200 2 4
2 1000 1 5
3 2400 14 3
Now I want to transpose this table to get the following output.
order_no rate month unit
1 1200 feb 2
1 1200 mar 4
2 1000 jan 1
2 2400 mar 5 and so on..
How can I do this?
You can create a "temporary" normalized view on the data using a cross join:
select o.order_no, o.rate, v.*
from orders o
cross join lateral (
values
('jan', jan),
('feb', feb),
('mar', mar),
...
('dec', dec)
) as v(month, unit)
If you want to exclude the months with no values, you can add
where v.unit is not null
to the query
Online example: http://rextester.com/PBP46544
One simple approach uses UNION:
SELECT order_no, rate, 'jan' AS month, jan AS unit UNION ALL
SELECT order_no, rate, 'feb', feb UNION ALL
...
SELECT order_no, rate, 'dec', dec
ORDER BY order_no;
Postgres also has CROSSTAB capabilities. But to use that, you have to be really good at SQL, which I'm not.
Try this
Select order_no, rate, 'jan' as month, jan as unit
from tbl
where jan is not null
union all
Select order_no, rate, 'feb' as month, feb as unit
from tbl
where feb is not null
union all
Select order_no, rate, 'mar' as month, mar as unit
from tbl
where mar is not null
order by order_no