Oracle SQL, calculating next order qty based on order history - sql

I am using the following script to get the order history of a particular manufacturing order;
select ds.status, ds.catnr, ds.part_no, ds.print_type, ds.nr_discs, ds.qty, ds.ship_date
from
(select 'Open Order' status, gb.catnr, gb.part_no, decode(gb.tec_criteria,'XX','SCREEN','OF','OFFSET','PI','OFFSET','MC','OFFSET') print_type, sp.nrunits nr_discs, sum(gb.or_menge_fd) qty, min(trunc(gb.shd_date)) ship_date
from gps_beweg gb, oes_customer oc, scm_packtyp sp
where gb.part_no = 'A0101628358-VV92-1900'
and gb.uebergabe_oes = '1'
and gb.pwerk_disc = 'W'
and gb.cunr = oc.cunr
and gb.packtyp = sp.packtyp
group by gb.cunr, oc.name, gb.part_no, sp.nrunits, gb.tec_criteria, gb.catnr, gb.prodtyp, gb.packtyp
UNION ALL
select unique 'Shipped Order' status,
null catnr, null part_no, null print_type, null nr_discs,
(select sum(ds1.planqty) from oes_delsegview ds1 where ds.ordnr = ds1.ordnr and ds.catnr = ds1.catnr and ds.prodtyp = ds1.prodtyp and ds.packtyp = ds1.packtyp) qty,
(select trunc(max(ds1.gps_planshpdate)) from oes_delsegview ds1 where ds.ordnr = ds1.ordnr and ds.catnr = ds1.catnr and ds.prodtyp = ds1.prodtyp and ds.packtyp = ds1.packtyp) ship_date
from part_description pd1, oes_delsegview ds
where pd1.part_no =
(select max(gb.part_no)
from gps_beweg gb
where gb.part_no = 'A0101628358-VV92-1900'
and gb.uebergabe_oes = '1'
and gb.pwerk_disc = 'W')
and pd1.catnr = ds.catnr
and pd1.prodtyp = ds.prodtyp
and pd1.packtyp = ds.packtyp
and ds.ord_o_status in ('7','9')
order by status, ship_date desc) ds
where rownum <=5
The result for this script looks like this...
I would like to use the data in the QTY and SHIP_DATE column to predict the next qty and date. I can do this in Excel using the TREND function. Is there a way of doing this in SQL? Will it be in line with the REGR_SLOPE function (I can't seem to get my head around how this works!?!).

As mentioned, as far as I know Oracle's SQL has no built-in trend functions to help you here. What you could do, though, is to to play around with analytic functions and come up with some algorithm.
ship_date - LAG(ship_date) OVER (ORDER BY ship_date) gives you the days between last and current order for instance. You'd have to weight these values, however, say multiply them with ROW_NUMBER() OVER (ORDER BY ship_date). Then divide to get the value to add to MAX(ship_date).
Here is the according query. A little hard to read and understand, but still an option in my opinion. The query retrieves all rows from yours plus a trend date and quantity for each row. So you can see what would have been forcast at some time and what shipment really followed. The last row gives you the current forecast.
select
status, catnr, part_no, print_type, nr_discs, qty, ship_date,
round(qty + sum(qty_diff_weighted) over (order by rn) /
(sum(rn) over (order by rn) - 1)) as trend_qty,
round(ship_date + sum(date_diff_weighted) over (order by rn) /
(sum(rn) over (order by rn) - 1)) as trend_date
from
(
select
status, catnr, part_no, print_type, nr_discs, qty, ship_date,
row_number() over (order by ship_date) *
(qty - lag(qty) over (order by ship_date)) as qty_diff_weighted,
row_number() over (order by ship_date) *
(ship_date - lag(ship_date) over (order by ship_date)) as date_diff_weighted,
row_number() over (order by ship_date) as rn
from (your query)
)
order by ship_date;
Result:
STATUS CATNR ... QTY SHIP_DATE TREND_QTY TREND_DATE
Shipped Order 500 06.06.2014
Shipped Order 500 17.11.2014 500 30.04.2015
Shipped Order 300 21.09.2015 180 28.05.2016
Shipped Order 300 16.08.2016 233 29.05.2017
Open Order PPD168 300 24.03.2017 257 11.12.2017
This shows the technique. You may come up with a completely different algorithm that suits you better, of course.

Related

Use last value when current row is null , for PostgreSQL timeseries table

I come across a problem that I could not find an optimal solution. So the idea is to get the price at each given time for a list of products from a list of shops but because the price are registered at different time I get some nulls when grouping by time and also an array o values. Therefore it requires to couple of steps in order to obtain what I need. I am wondering if someone know a better, faster way to achieve this. Bellow is my initial PostgreSQL table of course this is just a snippet of it to get the idea:
Initial Table
Desired results (intermediate table and final one)
And bellow is the PostgreSQL sql code that give the result I want but it seems very costly:
SELECT times,
first_value(price_yami_egg) OVER (PARTITION BY partition_price_yami_egg order by time) as price_yami_egg
first_value(price_yami_salt) OVER (PARTITION BY partition_price_yami_salt order by time) as price_yami_salt
first_value(price_dobl_egg) OVER (PARTITION BY partition_price_dobl_egg order by time) as price_dobl_egg
first_value(price_dobl_salt) OVER (PARTITION BY partition_price_dobl_salt order by time) as price_dobl_salt
FROM(
SELECT time,
min(price_yami_egg) as price_yami_egg,
sum(case when min(price_yami_egg) is not null then 1 end) over (order by times) as partition_price_yami_egg
min(price_yami_salt) as price_yami_salt,
sum(case when min(price_yami_salt) is not null then 1 end) over (order by times) as partition_price_yami_salt
min(price_dobl_egg) as price_dobl_egg,
sum(case when min(price_dobl_egg) is not null then 1 end) over (order by times) as partition_price_dobl_egg
min(price_dobl_salt) as price_dobl_salt,
sum(case when min(price_dobl_salt) is not null then 1 end) over (order by times) as partition_price_dobl_salt
FROM (
SELECT "time" AS times,
CASE WHEN shop_name::text = 'yami'::text AND product_name::text = 'egg'::text THEN price END AS price_yami_egg
CASE WHEN shop_name::text = 'yami'::text AND product_name::text = 'salt'::text THEN price END AS price_yami_salt
CASE WHEN shop_name::text = 'dobl'::text AND product_name::text = 'egg'::text THEN price END AS price_dobl_egg
CASE WHEN shop_name::text = 'dobl'::text AND product_name::text = 'salt'::text THEN price END AS price_dobl_salt
FROM shop sh
) S
GROUP BY time
ORDER BY time) SS
Do you just want aggregation?
select time,
min(price) filter (where shop_name = 'Yami' and product_name = 'EGG'),
min(price) filter (where shop_name = 'Yami' and product_name = 'SALT'),
min(price) filter (where shop_name = 'Dobl' and product_name = 'EGG'),
min(price) filter (where shop_name = 'Dobl' and product_name = 'SALT')
from shop s
group by time;
If. your concern is NULL values in the result, then you can fill them in. This is a little tricky, but the idea is:
with t as (
select time,
min(price) filter (where shop_name = 'Yami' and product_name = 'EGG') as yami_egg,
min(price) filter (where shop_name = 'Yami' and product_name = 'SALT') as yami_salt,
min(price) filter (where shop_name = 'Dobl' and product_name = 'EGG') as dobl_egg,
min(price) filter (where shop_name = 'Dobl' and product_name = 'SALT') as dobl_salt
from shop s
group by time
)
select s.*,
max(yaml_egg) over (yaml_egg_grp) as imputed_yaml_egg,
max(yaml_salt) over (yaml_egg_grp) as imputed_yaml_salt,
max(dobl_egg) over (yaml_egg_grp) as imputed_dobl_egg,
max(dobl_salt) over (yaml_egg_grp) as imputed_dobl_salt
from (select s.*,
count(yaml_egg) over (order by time) as yaml_egg_grp,
count(yaml_salt) over (order by time) as yaml_egg_grp,
count(dobl_egg) over (order by time) as dobl_egg_grp,
count(dobl_salt) over (order by time) as dobl_salt_grp
from s
) s

How to get the validity date range of a price from individual daily prices in SQL

I have some prices for the month of January.
Date,Price
1,100
2,100
3,115
4,120
5,120
6,100
7,100
8,120
9,120
10,120
Now, the o/p I need is a non-overlapping date range for each price.
price,from,To
100,1,2
115,3,3
120,4,5
100,6,7
120,8,10
I need to do this using SQL only.
For now, if I simply group by and take min and max dates, I get the below, which is an overlapping range:
price,from,to
100,1,7
115,3,3
120,4,10
This is a gaps-and-islands problem. The simplest solution is the difference of row numbers:
select price, min(date), max(date)
from (select t.*,
row_number() over (order by date) as seqnum,
row_number() over (partition by price, order by date) as seqnum2
from t
) t
group by price, (seqnum - seqnum2)
order by min(date);
Why this works is a little hard to explain. But if you look at the results of the subquery, you will see how the adjacent rows are identified by the difference in the two values.
SELECT Lag.price,Lag.[date] AS [From], MIN(Lead.[date]-Lag.[date])+Lag.[date] AS [to]
FROM
(
SELECT [date],[Price]
FROM
(
SELECT [date],[Price],LAG(Price) OVER (ORDER BY DATE,Price) AS LagID FROM #table1 A
)B
WHERE CASE WHEN Price <> ISNULL(LagID,1) THEN 1 ELSE 0 END = 1
)Lag
JOIN
(
SELECT [date],[Price]
FROM
(
SELECT [date],Price,LEAD(Price) OVER (ORDER BY DATE,Price) AS LeadID FROM [#table1] A
)B
WHERE CASE WHEN Price <> ISNULL(LeadID,1) THEN 1 ELSE 0 END = 1
)Lead
ON Lag.[Price] = Lead.[Price]
WHERE Lead.[date]-Lag.[date] >= 0
GROUP BY Lag.[date],Lag.[price]
ORDER BY Lag.[date]
Another method using ROWS UNBOUNDED PRECEDING
SELECT price, MIN([date]) AS [from], [end_date] AS [To]
FROM
(
SELECT *, MIN([abc]) OVER (ORDER BY DATE DESC ROWS UNBOUNDED PRECEDING ) end_date
FROM
(
SELECT *, CASE WHEN price = next_price THEN NULL ELSE DATE END AS abc
FROM
(
SELECT a.* , b.[date] AS next_date, b.price AS next_price
FROM #table1 a
LEFT JOIN #table1 b
ON a.[date] = b.[date]-1
)AA
)BB
)CC
GROUP BY price, end_date

getting difference between two invoices by ranking and subtracting one from the other

Trying to grab difference in invoices
Attempted using cte's for ranks 1 and 2, but they have a subquery in them and cant be done!
the second query looks the same, but with rank=2.
select *
from (
SELECT i.id, i.subtotal/100 as subtotal, i.created_at, i.paid_at
,RANK() OVER (PARTITION BY i.subscription_id ORDER BY i.created_at DESC) AS Rank
From Invoices i
) as r
where r.rank = 1
order by r.created_at desc;
Following the path that you are on (using row_number()/rank()), you can use conditional aggregation. Assuming you want the difference of the subtotal, then:
select sum(case when seqnum = 1 then subtotal
else - subtotal
end) as difference
from (select i.*, i.subtotal/100 as subtotal,
row_number() over (partition by i.subscription_id order by i.created_at desc) as seqnum
from Invoices i
) i
where seqnum in (1, 2)
order by r.created_at desc;

Calculating the weighted average cost in firebird sql

I have the same problem in this question but in Firebird 2.5
Calculating the Weighted Average Cost of products stock
And this answer didn't work (as Firebird 2.5 doesn't have row_number)
with recursive
stock_temp as (
select
*,
row_number() over(partition by product_id order by row_num) as rn
from
stock_table
)
,cte as (
select
document_type, document_date,
product_id, qty_out, qty_in, price,
row_num, stock_balance, rn,
price as wac
from
stock_temp where document_type = 'SI'
union all
select
sub.document_type, sub.document_date,
sub.product_id, sub.qty_out, sub.qty_in, sub.price,
sub.row_num, sub.stock_balance, sub.rn,
case when sub.qty_in = 0 then main.wac else
((sub.stock_balance - sub.qty_in) * main.wac + sub.qty_in * sub.price)
/ ((sub.stock_balance - sub.qty_in) + sub.qty_in) end as wac
from
cte as main
join stock_temp as sub
on (main.product_id = sub.product_id and main.rn + 1 = sub.rn)
)
select * from cte

SQL query for Pricing analysis

Facing issue to find the Min and Max pricing status on the column YearMonth,
Below is my table data
YearMonth STATE ProductGroup LocaProdname Price
201407 MH AIRTEL AIRTEL-3G 10,000
201208 GJ IDEA IDEA-3G 1,200
201406 WB AIRCEL AIRCEL PERPAID 5,866
201407 DL TATA DOCOMA TATA LANDLINE 8,955
201207 KAR VODAFONE VODAFONE-3G 7,899
201312 MH AIRTEL AIRTEL-3G 15,000
201408 GJ IDEA IDEA-3G 25,000
I require below output:
YearMonth STATE ProductGroup LocaProdname Price Indictor-YEAR
201407 MH AIRTEL AIRTEL-3G 10,000 MAX
201312 MH AIRTEL AIRTEL-3G 15,000 MIN
201408 GJ IDEA IDEA-3G 25,000 MAX
201208 GJ IDEA IDEA-3G 1,200 MIN
I need the Max yearmonth and min Year values values.
If I understand correctly, you can do this with row_number():
select YearMonth, STATE, ProductGroup, LocaProdname, Price,
(case when seqnum_asc = 1 then 'MIN' else 'MAX' end) as Indicator
from (select d.*,
row_number() over (partition by state, productgroup, localprodname
order by price asc) as seqnum_asc,
row_number() over (partition by state, productgroup, localprodname
order by pricedesc) as seqnum_desc
from data
) d
where seqnum_asc = 1 or seqnum_desc = 1;
EDIT:
Does this do what you want?
select YearMonth, STATE, ProductGroup, LocaProdname, Price,
(case when seqnum_asc = 1 then 'MIN' else 'MAX' end) as Indicator
from (select d.*,
row_number() over (partition by YearMonth
order by price asc) as seqnum_asc,
row_number() over (partition by YearMOnth
order by pricedesc) as seqnum_desc
from data
) d
where seqnum_asc = 1 or seqnum_desc = 1;
Please use Row_number with partition BY and remove unwanted code as per your need,
SELECT yearmonth,state,productgroup,locaprodname,price,operation
FROM (
SELECT * FROM (SELECT p.yearmonth,p.state,p.productgroup,p.locaprodname,p.price,'MAX' AS Operation,
Row_number() OVER( partition BY p.productgroup, p.locaprodname
ORDER BY p.price DESC) AS Row
FROM pricingtest p) AS Maxx
WHERE Maxx.row = 1
UNION ALL
SELECT * FROM (SELECT p.yearmonth,p.state,p.productgroup,p.locaprodname,p.price,'MIN' AS Operation,
Row_number() OVER( partition BY p.productgroup, p.locaprodname
ORDER BY p.price ASC) AS Row
FROM pricingtest p) AS Minn
WHERE Minn.row = 1
) AS whole
ORDER BY yearmonth,productgroup
This can be done by finding the MAX/MIN values associated with the LocaProdname,ProductGroup and State then joining in on the table where everything matches. See below, or view the fiddle at http://sqlfiddle.com/#!3/4d6bd/2
NOTE: I've added in HAVING COUNT(*) > 1 as you seem to only want ones which have changed price. (Ie. Have more than 1 entry)
SELECT p.YearMonth
,p.State
,p.ProductGroup
,p.LocaProdname
,p.Price
,CASE
WHEN p.Price = a.MaxPrice
THEN 'MAX'
WHEN p.Price = a.MinPrice
THEN 'MIN'
END AS [Indicator-YEAR]
FROM PricingTest p
INNER JOIN (
SELECT LocaProdname
,ProductGroup
,State
,MAX(Price) AS MaxPrice
,MIN(Price) AS MinPrice
FROM pricingTest
GROUP BY LocaProdname
,ProductGroup
,State
HAVING COUNT(*) > 1
) a ON a.LocaProdname = p.LocaProdname
AND a.ProductGroup = p.ProductGroup
AND a.State= p.State
AND (
a.MaxPrice = p.Price
OR a.MinPrice = p.Price
)
ORDER BY LocaProdname
EDIT: Or I just noticed it's the max/min YearMonth the user might be looking, if this is the case check out http://sqlfiddle.com/#!3/4d6bd/4 It is basically just replacing all references to Price to YearMonth.
Once you get the last and first record you can UNION results:
SELECT t.*, 'MIN' AS Indicator
FROM
myTable t LEFT JOIN
myTable t2 ON t.YearMonth = t2.YearMonth AND t2.price < t.price
WHERE t2.YearMonth IS NULL
UNION
SELECT t.*, 'MAX' AS Indicator
FROM
myTable t LEFT JOIN
myTable t2 ON t.YearMonth = t2.YearMonth AND t2.price > t.price
WHERE t2.YearMonth IS NULL
If you have several records with same highest price, above query will return all of them. Also if you only have one record in a month, it will be returned twice as both MIN and MAX.