View
491
Download
1
Category
Preview:
DESCRIPTION
Using Oracle SQL Analytic functions Case: Picking by FIFO (like my previous presentations) Case: Sales forecasting (Time Series Analysis - new case) As presented on ODTUG Kscope14 conference
Citation preview
Analytic FunctionsAdvanced Cases
Kim Berg Hansen
T. Hansen Gruppen A/S
Picking by FIFO
Sales forecasting
Cases
FIFO – First-In-First-Out principle● Pick oldest items first
Picking route● Don’t drive back and forth through the aisles of the
warehouse
Single SQL● Utilize the power of the database
Case: Picking by FIFO
Warehouses
create table inventory ( item varchar2(10) -- identification of the item , loc varchar2(10) -- identification of the location , qty number -- quantity present at that location , purch date -- date that quantity was purchased);
insert into inventory values('Ale' , '1-A-20', 18, DATE '2014-02-01');insert into inventory values('Ale' , '1-A-31', 12, DATE '2014-02-05');insert into inventory values('Ale' , '1-C-05', 18, DATE '2014-02-03');insert into inventory values('Ale' , '2-A-02', 24, DATE '2014-02-02');insert into inventory values('Ale' , '2-D-07', 9, DATE '2014-02-04');insert into inventory values('Bock', '1-A-02', 18, DATE '2014-02-06');insert into inventory values('Bock', '1-B-11', 4, DATE '2014-02-05');insert into inventory values('Bock', '1-C-04', 12, DATE '2014-02-03');insert into inventory values('Bock', '1-B-15', 2, DATE '2014-02-02');insert into inventory values('Bock', '2-D-23', 1, DATE '2014-02-04');
Inventoryloc = 1-A-201 = warehouseA = aisle20 = position
create table orderline ( ordno number -- id-number of the order , item varchar2(10) -- identification of the item , qty number -- quantity ordered);
insert into orderline values (42, 'Ale' , 24);insert into orderline values (42, 'Bock', 18);
Order
One order24 Ale18 Bock
Join upselect o.item , o.qty ord_qty , i.loc , i.purch , i.qty loc_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42 order by o.item, i.purch, i.loc;
ITEM ORD_QTY LOC PURCH LOC_QTY----- ------- ------- ---------- -------Ale 24 1-A-20 2014-02-01 18Ale 24 2-A-02 2014-02-02 24Ale 24 1-C-05 2014-02-03 18Ale 24 2-D-07 2014-02-04 9Ale 24 1-A-31 2014-02-05 12Bock 18 1-B-15 2014-02-02 2Bock 18 1-C-04 2014-02-03 12Bock 18 2-D-23 2014-02-04 1Bock 18 1-B-11 2014-02-05 4Bock 18 1-A-02 2014-02-06 18
Order locations for each item by purchase dateVisually easy to see what we need to pick18 Ale of the oldest and 6 of the next and so on
select o.item , o.qty ord_qty , i.loc , i.purch , i.qty loc_qty , sum(i.qty) over ( partition by i.item order by i.purch, i.loc rows between unbounded preceding and 1 preceding ) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42 order by o.item, i.purch, i.loc;
Rolling sum of previous
If the sum of all previous rows is greater than or equal to the ordered quantity, we have picked sufficient and can stop
Analytic sumPartition for each itemOrder by dateRolling sum of all previous rows
ITEM ORD_QTY LOC PURCH LOC_QTY SUM_PRV_QTY----- ------- ------- ---------- ------- -----------Ale 24 1-A-20 2014-02-01 18Ale 24 2-A-02 2014-02-02 24 18Ale 24 1-C-05 2014-02-03 18 42Ale 24 2-D-07 2014-02-04 9 60Ale 24 1-A-31 2014-02-05 12 69Bock 18 1-B-15 2014-02-02 2Bock 18 1-C-04 2014-02-03 12 2Bock 18 2-D-23 2014-02-04 1 14Bock 18 1-B-11 2014-02-05 4 15Bock 18 1-A-02 2014-02-06 18 19
Rolling sum of previous
Each row can now evaluate if sufficient has been pickedIf the sum of all previous rows is less than the ordered quantity we still need to pick something and the row is needed
select s.* , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item , o.qty ord_qty , i.loc , i.purch , i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.purch, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42
) s where s.sum_prv_qty < s.ord_qty order by s.item, s.purch, s.loc;
Filter on previous
Keep only rows where we still need something to pick
Pick location quantity or what is still needed, whichever is smallest
Set NULL in first row of partition to 0 otherwise predicate will fail
ITEM ORD_QTY LOC PURCH LOC_QTY SUM_PRV_QTY PICK_QTY----- ------- ------- ---------- ------- ----------- --------Ale 24 1-A-20 2014-02-01 18 0 18Ale 24 2-A-02 2014-02-02 24 18 6Bock 18 1-B-15 2014-02-02 2 0 2Bock 18 1-C-04 2014-02-03 12 2 12Bock 18 2-D-23 2014-02-04 1 14 1Bock 18 1-B-11 2014-02-05 4 15 3
Filter on previous
We have now selected the necessary inventory quantities to fulfil the order and pick the oldest items first (FIFO)
Picklist – FIFOselect s.loc , s.item , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item , o.qty ord_qty , i.loc , i.purch , i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.purch, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42 ) s where s.sum_prv_qty < s.ord_qty order by s.loc;
LOC ITEM PICK_QTY------- ----- --------1-A-20 Ale 181-B-11 Bock 31-B-15 Bock 21-C-04 Bock 122-A-02 Ale 62-D-23 Bock 1
Simple FIFO picklistItem and quantity to pickBy location order
Picklist – Shortest routeselect s.loc , s.item , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item , o.qty ord_qty , i.loc , i.purch , i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.loc -- << only line changed rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42 ) s where s.sum_prv_qty < s.ord_qty order by s.loc;
LOC ITEM PICK_QTY------- ----- --------1-A-02 Bock 181-A-20 Ale 181-A-31 Ale 6
Switch picking strategy =Switch analytic order byKeep "outer" order by
Picklist – Least number of picksselect s.loc , s.item , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item , o.qty ord_qty , i.loc , i.purch , i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.qty desc, i.loc -- << only line changed rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42 ) s where s.sum_prv_qty < s.ord_qty order by s.loc;
LOC ITEM PICK_QTY------- ----- --------1-A-02 Bock 182-A-02 Ale 24
Switch picking strategy =Switch analytic order byKeep "outer" order by
Picklist – Clean out small quantitiesselect s.loc , s.item , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item , o.qty ord_qty , i.loc , i.purch , i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.qty, i.loc -- << only line changed rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42 ) s where s.sum_prv_qty < s.ord_qty order by s.loc;
LOC ITEM PICK_QTY------- ----- --------1-A-20 Ale 31-A-31 Ale 121-B-11 Bock 41-B-15 Bock 21-C-04 Bock 112-D-07 Ale 92-D-23 Bock 1
Switch picking strategy =Switch analytic order byKeep "outer" order by
Not the greatest picking route
Strategy "Clean out small quantities" give most number of picksWe use that for picking route demonstration
select to_number(substr(s.loc,1,1)) warehouse , substr(s.loc,3,1) aisle , to_number(substr(s.loc,5,2)) position , s.loc , s.item , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item , o.qty ord_qty , i.loc , i.purch , i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.qty, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42
) s where s.sum_prv_qty < s.ord_qty order by s.loc;
Warehouse, aisle and position
Split location in parts- Warehouse- Aisle- Position
WAREHOUSE AISLE POSITION LOC ITEM PICK_QTY--------- ----- -------- ------- ----- -------- 1 A 20 1-A-20 Ale 3 1 A 31 1-A-31 Ale 12 1 B 11 1-B-11 Bock 4 1 B 15 1-B-15 Bock 2 1 C 4 1-C-04 Bock 11 2 D 7 2-D-07 Ale 9 2 D 23 2-D-23 Bock 1
Warehouse, aisle and position
Warehouse, aisle and position might be from lookup tables instead – here is simple substr for demonstration purposes
select to_number(substr(s.loc,1,1)) warehouse , substr(s.loc,3,1) aisle , dense_rank() over ( order by to_number(substr(s.loc,1,1)) -- warehouse , substr(s.loc,3,1) -- aisle ) aisle_no , to_number(substr(s.loc,5,2)) position , s.loc , s.item , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item, o.qty ord_qty, i.loc, i.purch, i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.qty, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42
) s
where s.sum_prv_qty < s.ord_qty order by s.loc;
Consecutive numbering of aisles
Dense rank gives equal rank to rows with same values in the order by and each rank is one higher than the previous
WAREHOUSE AISLE AISLE_NO POSITION LOC ITEM PICK_QTY--------- ----- -------- -------- ------- ----- -------- 1 A 1 20 1-A-20 Ale 3 1 A 1 31 1-A-31 Ale 12 1 B 2 11 1-B-11 Bock 4 1 B 2 15 1-B-15 Bock 2 1 C 3 4 1-C-04 Bock 11 2 D 4 7 2-D-07 Ale 9 2 D 4 23 2-D-23 Bock 1
Consecutive numbering of aisles
Aisles get consecutive numbering in the order they are visited
select s2.warehouse, s2.aisle, s2.aisle_no, s2.position , s2.loc, s2.item, s2.pick_qty from ( select to_number(substr(s.loc,1,1)) warehouse , substr(s.loc,3,1) aisle , dense_rank() over ( order by to_number(substr(s.loc,1,1)) -- warehouse , substr(s.loc,3,1) -- aisle ) aisle_no , to_number(substr(s.loc,5,2)) position , s.loc, s.item , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item, o.qty ord_qty, i.loc, i.purch, i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.qty, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42 ) s where s.sum_prv_qty < s.ord_qty
) s2 order by s2.warehouse , s2.aisle_no , case when mod(s2.aisle_no,2) = 1 then s2.position else -s2.position end;
Odd / even ordering
We order the positions in "odd" aisles "upward" and in "even" aisles "downward"
WAREHOUSE AISLE AISLE_NO POSITION LOC ITEM PICK_QTY--------- ----- -------- -------- ------- ----- -------- 1 A 1 20 1-A-20 Ale 3 1 A 1 31 1-A-31 Ale 12 1 B 2 15 1-B-15 Bock 2 1 B 2 11 1-B-11 Bock 4 1 C 3 4 1-C-04 Bock 11 2 D 4 23 2-D-23 Bock 1 2 D 4 7 2-D-07 Ale 9
Odd / even ordering
The desired ordering – the first aisle by position ascending – the second aisle by position descending – and so on
Much better picking route
select s2.warehouse, s2.aisle, s2.aisle_no, s2.position , s2.loc, s2.item, s2.pick_qty from ( select to_number(substr(s.loc,1,1)) warehouse , substr(s.loc,3,1) aisle , dense_rank() over ( partition by to_number(substr(s.loc,1,1)) -- warehouse order by substr(s.loc,3,1) -- aisle ) aisle_no , to_number(substr(s.loc,5,2)) position , s.loc, s.item , least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item, o.qty ord_qty, i.loc, i.purch, i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.qty, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderline o join inventory i on i.item = o.item where o.ordno = 42 ) s where s.sum_prv_qty < s.ord_qty ) s2 order by s2.warehouse , s2.aisle_no , case when mod(s2.aisle_no,2) = 1 then s2.position else -s2.position end;
Restart count if only one door
Partition by warehouse
WAREHOUSE AISLE AISLE_NO POSITION LOC ITEM PICK_QTY--------- ----- -------- -------- ------- ----- -------- 1 A 1 20 1-A-20 Ale 3 1 A 1 31 1-A-31 Ale 12 1 B 2 15 1-B-15 Bock 2 1 B 2 11 1-B-11 Bock 4 1 C 3 4 1-C-04 Bock 11 2 D 1 7 2-D-07 Ale 9 2 D 1 23 2-D-23 Bock 1
Restart count if only one door
Warehouse change restarts the aisle_no counterSo the first aisle in each warehouse starts by 1and therefore is odd and positions ordered ascending
Restart count if only one door
delete orderline;
insert into orderline values (51, 'Ale' , 24);insert into orderline values (51, 'Bock', 18);insert into orderline values (62, 'Ale' , 8);insert into orderline values (73, 'Ale' , 16);insert into orderline values (73, 'Bock', 6);
Batch pick multiple orders
Get rid of the first test order and insert three orders of various beers
with orderbatch as ( select o.item , sum(o.qty) qty from orderline o where o.ordno in (51, 62, 73) group by o.item)select s.loc, s.item, least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty from ( select o.item, o.qty ord_qty, i.loc, i.purch, i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.purch, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty from orderbatch o join inventory i on i.item = o.item ) s where s.sum_prv_qty < s.ord_qty order by s.loc;
Aggregate orders by item
Named subquery that is the sum of ordered quantities by item
Use subquery in FIFO query instead of orderline table
LOC ITEM PICK_QTY------- ----- --------1-A-02 Bock 51-A-20 Ale 181-B-11 Bock 41-B-15 Bock 21-C-04 Bock 121-C-05 Ale 62-A-02 Ale 242-D-23 Bock 1
Aggregate orders by item
Gets us a nice FIFO picklist picking the total quantities needed by the three orders
But…We can't see how much is for each order?
with orderbatch as ( select o.item, sum(o.qty) qty from orderline o where o.ordno in (51, 62, 73) group by o.item)
select s.loc, s.item, least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty , sum_prv_qty + 1 from_qty, least(sum_qty, ord_qty) to_qty from ( select o.item, o.qty ord_qty, i.loc, i.purch, i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.purch, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty , nvl(sum(i.qty) over ( partition by i.item order by i.purch, i.loc rows between unbounded preceding and current row ),0) sum_qty from orderbatch o join inventory i on i.item = o.item ) s where s.sum_prv_qty < s.ord_qty order by s.item, s.purch, s.loc;
Pick quantity intervals
Both rolling sum of previous rows only as well as rolling sum including current row
Calculate from and to quantity of each pick
LOC ITEM PICK_QTY FROM_QTY TO_QTY------- ----- -------- -------- ------1-A-20 Ale 18 1 182-A-02 Ale 24 19 421-C-05 Ale 6 43 481-B-15 Bock 2 1 21-C-04 Bock 12 3 142-D-23 Bock 1 15 151-B-11 Bock 4 16 191-A-02 Bock 5 20 24
Pick quantity intervals
The 24 Ale picked at 2-A-02 is number 19-42 of the total 48 Ale we are picking
select o.ordno, o.item, o.qty , nvl(sum(o.qty) over ( partition by o.item order by o.ordno rows between unbounded preceding and 1 preceding ),0) + 1 from_qty , nvl(sum(o.qty) over ( partition by o.item order by o.ordno rows between unbounded preceding and current row ),0) to_qty from orderline o where ordno in (51, 62, 73) order by o.item, o.ordno;
Order quantity intervals
Similarly calculate from and to quantity of the orderlines
ORDNO ITEM QTY FROM_QTY TO_QTY----- ----- ---- -------- ------ 51 Ale 24 1 24 62 Ale 8 25 32 73 Ale 16 33 48 51 Bock 18 1 18 73 Bock 6 19 24
Order quantity intervals
The 8 Ale from order no 62 is number 25-32 of the total 48 Ale ordered
with orderlines as ( select o.ordno, o.item, o.qty , nvl(sum(o.qty) over ( partition by o.item order by o.ordno rows between unbounded preceding and 1 preceding ),0) + 1 from_qty , nvl(sum(o.qty) over ( partition by o.item order by o.ordno rows between unbounded preceding and current row ),0) to_qty from orderline o where ordno in (51, 62, 73)), orderbatch as ( select o.item, sum(o.qty) qty from orderlines o group by o.item...
Join on overlapping intervals
Named subquery with the orderlines and their intervals
Named subquery with the aggregate sums by item
>>>
...
), fifo as ( select s.loc, s.item, s.purch, least(s.loc_qty, s.ord_qty - s.sum_prv_qty) pick_qty , sum_prv_qty + 1 from_qty, least(sum_qty, ord_qty) to_qty from ( select o.item, o.qty ord_qty, i.loc, i.purch, i.qty loc_qty , nvl(sum(i.qty) over ( partition by i.item order by i.purch, i.loc rows between unbounded preceding and 1 preceding ),0) sum_prv_qty , nvl(sum(i.qty) over ( partition by i.item order by i.purch, i.loc rows between unbounded preceding and current row ),0) sum_qty from orderbatch o join inventory i on i.item = o.item ) s where s.sum_prv_qty < s.ord_qty...
Join on overlapping intervals
Named subquery with FIFO pick of the sums with quantity intervals
>>>
...
)select f.loc, f.item, f.purch, f.pick_qty, f.from_qty, f.to_qty , o.ordno, o.qty, o.from_qty, o.to_qty from fifo f join orderlines o on o.item = f.item and o.to_qty >= f.from_qty and o.from_qty <= f.to_qty order by f.item, f.purch, o.ordno;
Join on overlapping intervals
Join the fifo subquery with the orderlines subquery on item and overlapping quantity intervals
LOC ITEM PURCH PICK_QTY FROM_QTY TO_QTY ORDNO QTY FROM_QTY TO_QTY------- ----- ---------- -------- -------- ------ ----- ---- -------- ------1-A-20 Ale 2014-02-01 18 1 18 51 24 1 242-A-02 Ale 2014-02-02 24 19 42 51 24 1 242-A-02 Ale 2014-02-02 24 19 42 62 8 25 322-A-02 Ale 2014-02-02 24 19 42 73 16 33 481-C-05 Ale 2014-02-03 6 43 48 73 16 33 481-B-15 Bock 2014-02-02 2 1 2 51 18 1 181-C-04 Bock 2014-02-03 12 3 14 51 18 1 182-D-23 Bock 2014-02-04 1 15 15 51 18 1 181-B-11 Bock 2014-02-05 4 16 19 51 18 1 181-B-11 Bock 2014-02-05 4 16 19 73 6 19 241-A-02 Bock 2014-02-06 5 20 24 73 6 19 24
Join on overlapping intervals
At location 2-A-02 we pick number 19-42 out of 48 AleThat overlaps with all three orders, as they get respectively number 1-24, 25-32 and 33-48 of the 48 Ale
with orderlines as (...
), orderbatch as (...
), fifo as (...
)select f.loc, f.item, f.purch, f.pick_qty, f.from_qty, f.to_qty , o.ordno, o.qty, o.from_qty, o.to_qty , least( f.loc_qty , least(o.to_qty, f.to_qty) - greatest(o.from_qty, f.from_qty) + 1 ) pick_ord_qty from fifo f join orderlines o on o.item = f.item and o.to_qty >= f.from_qty and o.from_qty <= f.to_qty order by f.item, f.purch, o.ordno;
How much to pick
Each row gets either the "size of the overlap" or the quantity on the location, whichever is smallest
LOC ITEM PURCH PICK_QTY FROM_QTY TO_QTY ORDNO QTY FROM_QTY TO_QTY PICK_ORD_QTY------- ----- ---------- -------- -------- ------ ----- ---- -------- ------ ------------1-A-20 Ale 2014-02-01 18 1 18 51 24 1 24 182-A-02 Ale 2014-02-02 24 19 42 51 24 1 24 62-A-02 Ale 2014-02-02 24 19 42 62 8 25 32 82-A-02 Ale 2014-02-02 24 19 42 73 16 33 48 101-C-05 Ale 2014-02-03 6 43 48 73 16 33 48 61-B-15 Bock 2014-02-02 2 1 2 51 18 1 18 21-C-04 Bock 2014-02-03 12 3 14 51 18 1 18 122-D-23 Bock 2014-02-04 1 15 15 51 18 1 18 11-B-11 Bock 2014-02-05 4 16 19 51 18 1 18 31-B-11 Bock 2014-02-05 4 16 19 73 6 19 24 11-A-02 Bock 2014-02-06 5 20 24 73 6 19 24 5
How much to pick
At 2-A-02 we pick 6 Ale to order 51, 8 Ale to order 62 and 10 Ale to order 73 – total 24 Ale from that location
Batch picklist – FIFOwith orderlines as (...
), orderbatch as (...
), fifo as (...
)select f.loc, f.item , f.pick_qty pick_at_loc, o.ordno , least( f.loc_qty , least(o.to_qty, f.to_qty) - greatest(o.from_qty, f.from_qty) +
1 ) qty_for_ord from fifo f join orderlines o on o.item = f.item and o.to_qty >= f.from_qty and o.from_qty <= f.to_qty order by f.loc, o.ordno;
LOC ITEM PICK_AT_LOC ORDNO QTY_FOR_ORD
------- ----- ----------- ----- -----------
1-A-02 Bock 5 73 5
1-A-20 Ale 18 51 18
1-B-11 Bock 4 51 3
1-B-11 Bock 4 73 1
1-B-15 Bock 2 51 2
1-C-04 Bock 12 51 12
1-C-05 Ale 6 73 6
2-A-02 Ale 24 51 6
2-A-02 Ale 24 62 8
2-A-02 Ale 24 73 10
2-D-23 Bock 1 51 1
Clean up query and keep what's needed for picking operator
with orderlines as (...), orderbatch as (...), fifo as (...
), pick as ( select to_number(substr(f.loc,1,1)) warehouse , substr(f.loc,3,1) aisle , dense_rank() over ( order by to_number(substr(f.loc,1,1)) -- warehouse , substr(f.loc,3,1) -- aisle ) aisle_no , to_number(substr(f.loc,5,2)) position , f.loc, f.item, f.pick_qty pick_at_loc, o.ordno , least( f.loc_qty , least(o.to_qty, f.to_qty) - greatest(o.from_qty, f.from_qty) + 1 ) qty_for_ord from fifo f join orderlines o on o.item = f.item and o.to_qty >= f.from_qty and o.from_qty <= f.to_qty...
Batch picklist FIFO with picking routeNamed subquery with batch picklist adding warehouse, aisle, position and dense rank
>>>
Batch picklist FIFO with picking route...
)select p.loc, p.item, p.pick_at_loc , p.ordno, p.qty_for_ord from pick p order by p.warehouse , p.aisle_no , case when mod(p.aisle_no,2) = 1 then p.position else -p.position end;
LOC ITEM PICK_AT_LOC ORDNO QTY_FOR_ORD
------- ----- ----------- ----- -----------
1-A-02 Bock 5 73 5
1-A-20 Ale 18 51 18
1-B-15 Bock 2 51 2
1-B-11 Bock 4 51 3
1-B-11 Bock 4 73 1
1-C-04 Bock 12 51 12
1-C-05 Ale 6 73 6
2-A-02 Ale 24 51 6
2-A-02 Ale 24 73 10
2-A-02 Ale 24 62 8
2-D-23 Bock 1 51 1
Select from the pick subqueryOrdering with odd / even logic
Batch picklist by FIFO with picking route in single SQL finished
FIFO principle● Or other principles by changing one line of code
Picking route● Up and down alternate aisles
Batch picking● Multiple orders simultaneously
Single SQL picking Done
Case closed
Picking by FIFO
Sales forecasting
Cases
Sales forecasting● Seasonal items (summer / winter)● Trending upwards or downwards over time
Regression● Simple model ”transposing graph”● Datascientists model ”Time Series Analysis”
Single SQL● Utilize the power of the database
Case: Sales forecasting
create table sales ( item varchar2(10) , mth date , qty number);
insert into sales values ('Snowchain', date '2011-01-01', 79);insert into sales values ('Snowchain', date '2011-02-01', 133);insert into sales values ('Snowchain', date '2011-03-01', 24);...
insert into sales values ('Snowchain', date '2013-10-01', 1);insert into sales values ('Snowchain', date '2013-11-01', 73);insert into sales values ('Snowchain', date '2013-12-01', 160);insert into sales values ('Sunshade' , date '2011-01-01', 4);insert into sales values ('Sunshade' , date '2011-02-01', 6);insert into sales values ('Sunshade' , date '2011-03-01', 32);...
insert into sales values ('Sunshade' , date '2013-10-01', 11);insert into sales values ('Sunshade' , date '2013-11-01', 3);insert into sales values ('Sunshade' , date '2013-12-01', 5);
Sales 2011 – 2013
Monthly sales2011 - 2013
Snowchain and Sunshade
Snowchain peaks wintertime and trends upwardsSunshade peaks summertime and trends downwards
select sales.item, sales.mth, sales.qty , regr_slope( sales.qty , extract(year from sales.mth) * 12 + extract(month from sales.mth) ) over ( partition by sales.item order by sales.mth range between interval '23' month preceding and current row ) slope from sales order by sales.item, sales.mth;
Moving slope
Calculate slope of linear regression of a graph with qty on the Y axis and month as number with unit 1=month on the X axis"Rolling" slope of 24 points on the graph (= 2 years)
Moving slopeITEM MTH QTY SLOPE---------- ------- ---- -------Snowchain 2011-01 79Snowchain 2011-02 133 54.000Snowchain 2011-03 24 -27.500Snowchain 2011-04 1 -34.300Snowchain 2011-05 0 -29.000Snowchain 2011-06 0 -23.343...
Snowchain 2013-01 167 1.776Snowchain 2013-02 247 4.821Snowchain 2013-03 42 4.533Snowchain 2013-04 0 3.558Snowchain 2013-05 0 2.574Snowchain 2013-06 0 1.590Snowchain 2013-07 0 .605Snowchain 2013-08 1 -.369Snowchain 2013-09 0 -1.343Snowchain 2013-10 1 -2.274Snowchain 2013-11 73 -2.363Snowchain 2013-12 160 -.991
ITEM MTH QTY SLOPE---------- ------- ---- -------Sunshade 2011-01 4Sunshade 2011-02 6 2.000Sunshade 2011-03 32 14.000Sunshade 2011-04 45 14.900Sunshade 2011-05 62 15.500Sunshade 2011-06 58 12.886...
Sunshade 2013-01 2 -1.135Sunshade 2013-02 8 -1.595Sunshade 2013-03 28 -1.574Sunshade 2013-04 26 -1.428Sunshade 2013-05 23 -1.111Sunshade 2013-06 46 -.574Sunshade 2013-07 73 .537Sunshade 2013-08 25 .560Sunshade 2013-09 13 .421Sunshade 2013-10 11 .217Sunshade 2013-11 3 -.200Sunshade 2013-12 5 -.574
Slopes in 2011 not very useful
Slopes in 2013 based on 2 years data
Slope of each month is not the same
select item, mth, qty , qty + 12 * slope qty_next_year from ( select sales.item, sales.mth, sales.qty , regr_slope( sales.qty , extract(year from sales.mth) * 12 + extract(month from sales.mth) ) over ( partition by sales.item order by sales.mth range between interval '23' month preceding and current row ) slope from sales ) where mth >= date '2013-01-01' order by item, mth;
Transpose 12 months
Filter 2013 with the useful slopes
Slope is Y-increment per month12 * Slope is Y-increment per yearAdd 12 * Slope to Qty is forecast
Transpose 12 monthsITEM MTH QTY QTY_NEXT_YEAR---------- ------- ---- -------------Snowchain 2013-01 167 188.3130Snowchain 2013-02 247 304.8557Snowchain 2013-03 42 96.3913Snowchain 2013-04 0 42.6991Snowchain 2013-05 0 30.8870Snowchain 2013-06 0 19.0748Snowchain 2013-07 0 7.2626Snowchain 2013-08 1 -3.4296Snowchain 2013-09 0 -16.1217Snowchain 2013-10 1 -26.2922Snowchain 2013-11 73 44.6435Snowchain 2013-12 160 148.1096
ITEM MTH QTY QTY_NEXT_YEAR---------- ------- ---- -------------Sunshade 2013-01 2 -11.6174Sunshade 2013-02 8 -11.1374Sunshade 2013-03 28 9.1130Sunshade 2013-04 26 8.8609Sunshade 2013-05 23 9.6643Sunshade 2013-06 46 39.1130Sunshade 2013-07 73 79.4487Sunshade 2013-08 25 31.7148Sunshade 2013-09 13 18.0504Sunshade 2013-10 11 13.6087Sunshade 2013-11 3 .5948Sunshade 2013-12 5 -1.8870
Each month is transposed 12 months into the future by the slope of the previous 24 months
select item , add_months(mth, 12) mth , greatest(round(qty + 12 * slope), 0) forecast from ( select sales.item, sales.mth, sales.qty , regr_slope( sales.qty , extract(year from sales.mth) * 12 + extract(month from sales.mth) ) over ( partition by sales.item order by sales.mth range between interval '23' month preceding and current row ) slope from sales ) where mth >= date '2013-01-01' order by item, mth;
Rounded forecast
Rather than "qty_next_year", add 12 months to show the month of the forecastRound off to whole quantities and assume negative forecast is a zero sale
Rounded forecastITEM MTH FORECAST---------- ------- --------Snowchain 2014-01 188Snowchain 2014-02 305Snowchain 2014-03 96Snowchain 2014-04 43Snowchain 2014-05 31Snowchain 2014-06 19Snowchain 2014-07 7Snowchain 2014-08 0Snowchain 2014-09 0Snowchain 2014-10 0Snowchain 2014-11 45Snowchain 2014-12 148
ITEM MTH FORECAST---------- ------- --------Sunshade 2014-01 0Sunshade 2014-02 0Sunshade 2014-03 9Sunshade 2014-04 9Sunshade 2014-05 10Sunshade 2014-06 39Sunshade 2014-07 79Sunshade 2014-08 32Sunshade 2014-09 18Sunshade 2014-10 14Sunshade 2014-11 1Sunshade 2014-12 0
Simple forecast with nice numbers
select item, mth, qty, type , sum(qty) over (partition by item, extract(year from mth)) qty_yr from ( select sales.item, sales.mth, sales.qty, 'Actual' type from sales union all select item, add_months(mth, 12) mth , greatest(round(qty + 12 * slope), 0) qty, 'Forecast' type from ( select sales.item, sales.mth, sales.qty , regr_slope( sales.qty , extract(year from sales.mth) * 12 + extract(month from sales.mth) ) over ( partition by sales.item order by sales.mth range between interval '23' month preceding and current row ) slope from sales ) where mth >= date '2013-01-01' ) order by item, mth;
Sales and forecast
Union actual sales with forecast
Show year totals for comparison
Sales and forecastITEM MTH QTY TYPE QTY_YR---------- ------- ---- -------- ------Snowchain 2011-01 79 Actual 331...
Snowchain 2011-12 74 Actual 331Snowchain 2012-01 148 Actual 582...
Snowchain 2012-12 172 Actual 582Snowchain 2013-01 167 Actual 691...
Snowchain 2013-12 160 Actual 691Snowchain 2014-01 188 Forecast 882Snowchain 2014-02 305 Forecast 882Snowchain 2014-03 96 Forecast 882Snowchain 2014-04 43 Forecast 882Snowchain 2014-05 31 Forecast 882Snowchain 2014-06 19 Forecast 882Snowchain 2014-07 7 Forecast 882Snowchain 2014-08 0 Forecast 882Snowchain 2014-09 0 Forecast 882Snowchain 2014-10 0 Forecast 882Snowchain 2014-11 45 Forecast 882Snowchain 2014-12 148 Forecast 882
ITEM MTH QTY TYPE QTY_YR---------- ------- ---- -------- ------Sunshade 2011-01 4 Actual 377...
Sunshade 2011-12 8 Actual 377Sunshade 2012-01 2 Actual 321...
Sunshade 2012-12 3 Actual 321Sunshade 2013-01 2 Actual 263...
Sunshade 2013-12 5 Actual 263Sunshade 2014-01 0 Forecast 211Sunshade 2014-02 0 Forecast 211Sunshade 2014-03 9 Forecast 211Sunshade 2014-04 9 Forecast 211Sunshade 2014-05 10 Forecast 211Sunshade 2014-06 39 Forecast 211Sunshade 2014-07 79 Forecast 211Sunshade 2014-08 32 Forecast 211Sunshade 2014-09 18 Forecast 211Sunshade 2014-10 14 Forecast 211Sunshade 2014-11 1 Forecast 211Sunshade 2014-12 0 Forecast 211
Year totals easy to see trend Snowchain upward, Sunshade downward
Forecast follow trend up or down but keep graph shape (seasons)
Our dataanalyst/scientist did a model in Excel● Centered Moving Average● Seasonality● Deseasonalize● Regression trend● Reseasonalize
http://people.duke.edu/~rnau/411outbd.htm OK, I can do that in a SQL statement…
Time Series Analysis
select sales.item , mths.ts , mths.mth , extract(year from mths.mth) yr , extract(month from mths.mth) mthno , sales.qty from ( select add_months(date '2011-01-01', level-1) mth , level ts --time serie from dual connect by level <= 48 ) mths left outer join sales partition by (sales.item) on sales.mth = mths.mth order by sales.item, mths.mth;
Time Series
Create 48 month time series for each item – sales 2011-13 and forecast 2014
Partitioned outer join gives rows for 2014 for each item with null qty
Time SeriesITEM TS MTH YR MTHNO QTY---------- --- ------- ----- ----- ----Snowchain 1 2011-01 2011 1 79Snowchain 2 2011-02 2011 2 133Snowchain 3 2011-03 2011 3 24...
Snowchain 34 2013-10 2013 10 1Snowchain 35 2013-11 2013 11 73Snowchain 36 2013-12 2013 12 160Snowchain 37 2014-01 2014 1Snowchain 38 2014-02 2014 2Snowchain 39 2014-03 2014 3Snowchain 40 2014-04 2014 4Snowchain 41 2014-05 2014 5Snowchain 42 2014-06 2014 6Snowchain 43 2014-07 2014 7Snowchain 44 2014-08 2014 8Snowchain 45 2014-09 2014 9Snowchain 46 2014-10 2014 10Snowchain 47 2014-11 2014 11Snowchain 48 2014-12 2014 12
ITEM TS MTH YR MTHNO QTY---------- --- ------- ----- ----- ----Sunshade 1 2011-01 2011 1 4Sunshade 2 2011-02 2011 2 6Sunshade 3 2011-03 2011 3 32...
Sunshade 34 2013-10 2013 10 11Sunshade 35 2013-11 2013 11 3Sunshade 36 2013-12 2013 12 5Sunshade 37 2014-01 2014 1Sunshade 38 2014-02 2014 2Sunshade 39 2014-03 2014 3Sunshade 40 2014-04 2014 4Sunshade 41 2014-05 2014 5Sunshade 42 2014-06 2014 6Sunshade 43 2014-07 2014 7Sunshade 44 2014-08 2014 8Sunshade 45 2014-09 2014 9Sunshade 46 2014-10 2014 10Sunshade 47 2014-11 2014 11Sunshade 48 2014-12 2014 12
Sales in 2011-13, null qty in 2014
with s1 as (...
)select s1.* , case when ts between 7 and 30 then (nvl(avg(qty) over ( partition by item order by ts rows between 5 preceding and 6 following ),0) + nvl(avg(qty) over ( partition by item order by ts rows between 6 preceding and 5 following ),0)) / 2 else null end cma -- centered moving average from s1 order by item, ts;
Centered Moving Average
• Rolling average -5 to +6 months• Rolling average -6 to +5 monthsAverage of those two is CMADo this only for those months(ts 7-30) where there is 12 months data
Centered Moving AverageITEM TS MTH YR MTHNO QTY CMA---------- --- ------- ----- ----- ---- -------Snowchain 1 2011-01 2011 1 79Snowchain 2 2011-02 2011 2 133Snowchain 3 2011-03 2011 3 24Snowchain 4 2011-04 2011 4 1Snowchain 5 2011-05 2011 5 0Snowchain 6 2011-06 2011 6 0Snowchain 7 2011-07 2011 7 0 30.458Snowchain 8 2011-08 2011 8 0 36.500Snowchain 9 2011-09 2011 9 1 39.917Snowchain 10 2011-10 2011 10 4 40.208Snowchain 11 2011-11 2011 11 15 40.250Snowchain 12 2011-12 2011 12 74 40.250Snowchain 13 2012-01 2012 1 148 40.250Snowchain 14 2012-02 2012 2 209 40.292Snowchain 15 2012-03 2012 3 30 40.292...
Snowchain 29 2013-05 2013 5 0 56.250Snowchain 30 2013-06 2013 6 0 58.083Snowchain 31 2013-07 2013 7 0Snowchain 32 2013-08 2013 8 1Snowchain 33 2013-09 2013 9 0...
ITEM TS MTH YR MTHNO QTY CMA---------- --- ------- ----- ----- ---- -------Sunshade 1 2011-01 2011 1 4Sunshade 2 2011-02 2011 2 6Sunshade 3 2011-03 2011 3 32Sunshade 4 2011-04 2011 4 45Sunshade 5 2011-05 2011 5 62Sunshade 6 2011-06 2011 6 58Sunshade 7 2011-07 2011 7 85 31.333Sunshade 8 2011-08 2011 8 28 31.542Sunshade 9 2011-09 2011 9 24 31.708Sunshade 10 2011-10 2011 10 19 32.208Sunshade 11 2011-11 2011 11 6 31.458Sunshade 12 2011-12 2011 12 8 30.917Sunshade 13 2012-01 2012 1 2 30.542Sunshade 14 2012-02 2012 2 13 29.083Sunshade 15 2012-03 2012 3 29 28.292...
Sunshade 29 2013-05 2013 5 23 21.833Sunshade 30 2013-06 2013 6 46 21.833Sunshade 31 2013-07 2013 7 73Sunshade 32 2013-08 2013 8 25Sunshade 33 2013-09 2013 9 13...
CMA for 24 months 2011-07 to 2013-06
with s1 as (...
), s2 as (...
)select s2.* , nvl(avg( case qty when 0 then 0.0001 else qty end / nullif(cma,0) ) over ( partition by item, mthno ),0) s -- seasonality from s2 order by item, ts;
Seasonality factor
Qty divided by CMA factor
Average factor of the month is seasonalityPartition makes seasonality same for all january, all february, etc. (by item)
Seasonality factorITEM TS MTH YR MTHNO QTY CMA S---------- --- ------- ----- ----- ---- ------- -------...
Snowchain 9 2011-09 2011 9 1 39.917 .0125Snowchain 10 2011-10 2011 10 4 40.208 .0774Snowchain 11 2011-11 2011 11 15 40.250 .3435Snowchain 12 2011-12 2011 12 74 40.250 2.5094Snowchain 13 2012-01 2012 1 148 40.250 3.3824Snowchain 14 2012-02 2012 2 209 40.292 4.8771Snowchain 15 2012-03 2012 3 30 40.292 .7606Snowchain 16 2012-04 2012 4 2 40.208 .0249Snowchain 17 2012-05 2012 5 0 40.250 .0000Snowchain 18 2012-06 2012 6 0 44.417 .0000Snowchain 19 2012-07 2012 7 0 49.292 .0000Snowchain 20 2012-08 2012 8 1 51.667 .0097Snowchain 21 2012-09 2012 9 0 53.750 .0125Snowchain 22 2012-10 2012 10 3 54.167 .0774Snowchain 23 2012-11 2012 11 17 54.083 .3435Snowchain 24 2012-12 2012 12 172 54.083 2.5094Snowchain 25 2013-01 2013 1 167 54.083 3.3824Snowchain 26 2013-02 2013 2 247 54.083 4.8771Snowchain 27 2013-03 2013 3 42 54.083 .7606Snowchain 28 2013-04 2013 4 0 54.000 .0249...
ITEM TS MTH YR MTHNO QTY CMA S---------- --- ------- ----- ----- ---- ------- -------...
Sunshade 9 2011-09 2011 9 24 31.708 .5876Sunshade 10 2011-10 2011 10 19 32.208 .5567Sunshade 11 2011-11 2011 11 6 31.458 .2033Sunshade 12 2011-12 2011 12 8 30.917 .1989Sunshade 13 2012-01 2012 1 2 30.542 .0805Sunshade 14 2012-02 2012 2 13 29.083 .4071Sunshade 15 2012-03 2012 3 29 28.292 1.1489Sunshade 16 2012-04 2012 4 60 27.500 1.6818Sunshade 17 2012-05 2012 5 29 27.208 1.0596Sunshade 18 2012-06 2012 6 78 26.958 2.5001Sunshade 19 2012-07 2012 7 56 26.750 2.4031Sunshade 20 2012-08 2012 8 22 26.542 .8583Sunshade 21 2012-09 2012 9 11 26.292 .5876Sunshade 22 2012-10 2012 10 13 24.833 .5567Sunshade 23 2012-11 2012 11 5 23.167 .2033Sunshade 24 2012-12 2012 12 3 21.583 .1989Sunshade 25 2013-01 2013 1 2 20.958 .0805Sunshade 26 2013-02 2013 2 8 21.792 .4071Sunshade 27 2013-03 2013 3 28 22.000 1.1489Sunshade 28 2013-04 2013 4 26 22.000 1.6818...
S calculated for each monthPartition makes it repeat
with s1 as (...
), s2 as (...
), s3 as (...
)select s3.* , case when ts <= 36 then nvl(case qty when 0 then 0.0001 else qty end / nullif(s,0), 0) end des -- deseasonalized from s3 order by item, ts;
Deseasonalized quantity
Divide each individual qty by the seasonality factor
Deseasonalized quantityITEM TS MTH QTY CMA S DES---------- --- ------- ---- ------- ------- --------Snowchain 1 2011-01 79 3.3824 23.356Snowchain 2 2011-02 133 4.8771 27.270Snowchain 3 2011-03 24 .7606 31.555...
Snowchain 22 2012-10 3 54.167 .0774 38.743Snowchain 23 2012-11 17 54.083 .3435 49.490Snowchain 24 2012-12 172 54.083 2.5094 68.542Snowchain 25 2013-01 167 54.083 3.3824 49.373Snowchain 26 2013-02 247 54.083 4.8771 50.645Snowchain 27 2013-03 42 54.083 .7606 55.221Snowchain 28 2013-04 0 54.000 .0249 .004Snowchain 29 2013-05 0 56.250 .0000 46.924Snowchain 30 2013-06 0 58.083 .0000 50.339Snowchain 31 2013-07 0 .0000 37.651Snowchain 32 2013-08 1 .0097 103.319Snowchain 33 2013-09 0 .0125 .008Snowchain 34 2013-10 1 .0774 12.914Snowchain 35 2013-11 73 .3435 212.518Snowchain 36 2013-12 160 2.5094 63.760...
ITEM TS MTH QTY CMA S DES---------- --- ------- ---- ------- ------- --------Sunshade 1 2011-01 4 .0805 49.717Sunshade 2 2011-02 6 .4071 14.740Sunshade 3 2011-03 32 1.1489 27.853...
Sunshade 22 2012-10 13 24.833 .5567 23.352Sunshade 23 2012-11 5 23.167 .2033 24.597Sunshade 24 2012-12 3 21.583 .1989 15.085Sunshade 25 2013-01 2 20.958 .0805 24.858Sunshade 26 2013-02 8 21.792 .4071 19.654Sunshade 27 2013-03 28 22.000 1.1489 24.372Sunshade 28 2013-04 26 22.000 1.6818 15.459Sunshade 29 2013-05 23 21.833 1.0596 21.705Sunshade 30 2013-06 46 21.833 2.5001 18.399Sunshade 31 2013-07 73 2.4031 30.377Sunshade 32 2013-08 25 .8583 29.127Sunshade 33 2013-09 13 .5876 22.122Sunshade 34 2013-10 11 .5567 19.759Sunshade 35 2013-11 3 .2033 14.758Sunshade 36 2013-12 5 .1989 25.141...
DES show trend more clear without seasonal up/down
Snowchain deseasonalized
with s1 as (...
), s2 as (...
), s3 as (...
), s4 as (...
)select s4.* , regr_intercept(des,ts) over (partition by item) + ts*regr_slope(des,ts) over (partition by item) t -- trend from s4 order by item, ts;
Trend (regression)
Linear regression of deseasonalized qty gives the trend line
Intercept is the point where the line intersects Y axisAdd slope*time series (month) and get Y value of trend line
Trend (regression)ITEM TS MTH QTY CMA S DES T---------- --- ------- ---- ------- ------- -------- -------Snowchain 1 2011-01 79 3.3824 23.356 32.163Snowchain 2 2011-02 133 4.8771 27.270 33.096Snowchain 3 2011-03 24 .7606 31.555 34.030...Snowchain 34 2013-10 1 .0774 12.914 62.976Snowchain 35 2013-11 73 .3435 212.518 63.910Snowchain 36 2013-12 160 2.5094 63.760 64.844Snowchain 37 2014-01 3.3824 65.777Snowchain 38 2014-02 4.8771 66.711Snowchain 39 2014-03 .7606 67.645Snowchain 40 2014-04 .0249 68.579Snowchain 41 2014-05 .0000 69.512Snowchain 42 2014-06 .0000 70.446Snowchain 43 2014-07 .0000 71.380Snowchain 44 2014-08 .0097 72.314Snowchain 45 2014-09 .0125 73.247Snowchain 46 2014-10 .0774 74.181Snowchain 47 2014-11 .3435 75.115Snowchain 48 2014-12 2.5094 76.049
ITEM TS MTH QTY CMA S DES T---------- --- ------- ---- ------- ------- -------- -------Sunshade 1 2011-01 4 .0805 49.717 35.860Sunshade 2 2011-02 6 .4071 14.740 35.376Sunshade 3 2011-03 32 1.1489 27.853 34.892...Sunshade 34 2013-10 11 .5567 19.759 19.895Sunshade 35 2013-11 3 .2033 14.758 19.412Sunshade 36 2013-12 5 .1989 25.141 18.928Sunshade 37 2014-01 .0805 18.444Sunshade 38 2014-02 .4071 17.960Sunshade 39 2014-03 1.1489 17.477Sunshade 40 2014-04 1.6818 16.993Sunshade 41 2014-05 1.0596 16.509Sunshade 42 2014-06 2.5001 16.025Sunshade 43 2014-07 2.4031 15.542Sunshade 44 2014-08 .8583 15.058Sunshade 45 2014-09 .5876 14.574Sunshade 46 2014-10 .5567 14.090Sunshade 47 2014-11 .2033 13.606Sunshade 48 2014-12 .1989 13.123
Linear trend line from 2011 to 2014
Snowchain trend line
with s1 as (...), s2 as (...), s3 as (...), s4 as (...), s5 as (...)
select s5.* , t * s forecast --reseasonalized from s5 order by item, ts;
Reseasonalize (forecast)
Multiply trend line by seasonality factor and get the qty forecasted by the model
Reseasonalize (forecast)ITEM TS MTH QTY S DES T FORECAST---------- --- ------- ---- ------- -------- ------- --------Snowchain 1 2011-01 79 3.3824 23.356 32.163 108.788Snowchain 2 2011-02 133 4.8771 27.270 33.096 161.414Snowchain 3 2011-03 24 .7606 31.555 34.030 25.882Snowchain 4 2011-04 1 .0249 40.207 34.964 .870...Snowchain 33 2013-09 0 .0125 .008 62.042 .777Snowchain 34 2013-10 1 .0774 12.914 62.976 4.876Snowchain 35 2013-11 73 .3435 212.518 63.910 21.953Snowchain 36 2013-12 160 2.5094 63.760 64.844 162.718Snowchain 37 2014-01 3.3824 65.777 222.487Snowchain 38 2014-02 4.8771 66.711 325.357Snowchain 39 2014-03 .7606 67.645 51.449Snowchain 40 2014-04 .0249 68.579 1.706Snowchain 41 2014-05 .0000 69.512 .000Snowchain 42 2014-06 .0000 70.446 .000Snowchain 43 2014-07 .0000 71.380 .000Snowchain 44 2014-08 .0097 72.314 .700Snowchain 45 2014-09 .0125 73.247 .918Snowchain 46 2014-10 .0774 74.181 5.744Snowchain 47 2014-11 .3435 75.115 25.802Snowchain 48 2014-12 2.5094 76.049 190.836
ITEM TS MTH QTY S DES T FORECAST---------- --- ------- ---- ------- -------- ------- --------Sunshade 1 2011-01 4 .0805 49.717 35.860 2.885Sunshade 2 2011-02 6 .4071 14.740 35.376 14.400Sunshade 3 2011-03 32 1.1489 27.853 34.892 40.087Sunshade 4 2011-04 45 1.6818 26.757 34.409 57.869...Sunshade 33 2013-09 13 .5876 22.122 20.379 11.976Sunshade 34 2013-10 11 .5567 19.759 19.895 11.076Sunshade 35 2013-11 3 .2033 14.758 19.412 3.946Sunshade 36 2013-12 5 .1989 25.141 18.928 3.764Sunshade 37 2014-01 .0805 18.444 1.484Sunshade 38 2014-02 .4071 17.960 7.311Sunshade 39 2014-03 1.1489 17.477 20.079Sunshade 40 2014-04 1.6818 16.993 28.579Sunshade 41 2014-05 1.0596 16.509 17.494Sunshade 42 2014-06 2.5001 16.025 40.065Sunshade 43 2014-07 2.4031 15.542 37.348Sunshade 44 2014-08 .8583 15.058 12.924Sunshade 45 2014-09 .5876 14.574 8.564Sunshade 46 2014-10 .5567 14.090 7.844Sunshade 47 2014-11 .2033 13.606 2.766Sunshade 48 2014-12 .1989 13.123 2.610
Trend line linearApply seasonality = forecast
Snowchain reseasonalized forecastCompare to actual data 2011-13
Sunshade reseasonalized forecastCompare to actual data 2011-13
with s1 as (...), s2 as (...), s3 as (...), s4 as (...), s5 as (...)
select item , mth , qty , t * s forecast --reseasonalized , sum(qty) over (partition by item, yr) qty_yr , sum(t * s) over (partition by item, yr) fc_yr from s5 order by item, ts;
Model describes reality?
2011-13 qty and forecast can be compared to see how well model described reality
ITEM MTH QTY FORECAST QTY_YR FC_YR---------- ------- ---- -------- ------ -------Snowchain 2011-01 79 108.788 331 421.70...
Snowchain 2012-01 148 146.687 582 556.14...
Snowchain 2013-01 167 184.587 691 690.57Snowchain 2013-02 247 270.710 691 690.57Snowchain 2013-03 42 42.927 691 690.57Snowchain 2013-04 0 1.427 691 690.57...
Snowchain 2013-09 0 .777 691 690.57Snowchain 2013-10 1 4.876 691 690.57Snowchain 2013-11 73 21.953 691 690.57Snowchain 2013-12 160 162.718 691 690.57Snowchain 2014-01 222.487 825.00Snowchain 2014-02 325.357 825.00Snowchain 2014-03 51.449 825.00Snowchain 2014-04 1.706 825.00Snowchain 2014-05 .000 825.00Snowchain 2014-06 .000 825.00Snowchain 2014-07 .000 825.00Snowchain 2014-08 .700 825.00Snowchain 2014-09 .918 825.00Snowchain 2014-10 5.744 825.00Snowchain 2014-11 25.802 825.00Snowchain 2014-12 190.836 825.00
ITEM MTH QTY FORECAST QTY_YR FC_YR---------- ------- ---- -------- ------ -------Sunshade 2011-01 4 2.885 377 390.59...
Sunshade 2012-01 2 2.418 321 322.75...
Sunshade 2013-01 2 1.951 263 254.91...
Sunshade 2013-04 26 38.342 263 254.91Sunshade 2013-05 23 23.645 263 254.91Sunshade 2013-06 46 54.579 263 254.91Sunshade 2013-07 73 51.299 263 254.91Sunshade 2013-08 25 17.907 263 254.91...
Sunshade 2013-11 3 3.946 263 254.91Sunshade 2013-12 5 3.764 263 254.91Sunshade 2014-01 1.484 187.07Sunshade 2014-02 7.311 187.07Sunshade 2014-03 20.079 187.07Sunshade 2014-04 28.579 187.07Sunshade 2014-05 17.494 187.07Sunshade 2014-06 40.065 187.07Sunshade 2014-07 37.348 187.07Sunshade 2014-08 12.924 187.07Sunshade 2014-09 8.564 187.07Sunshade 2014-10 7.844 187.07Sunshade 2014-11 2.766 187.07Sunshade 2014-12 2.610 187.07
Compare numbers and see if model matches reality
with s1 as (...), s2 as (...), s3 as (...), s4 as (...), s5 as (...)
select item, mth , case when ts <= 36 then qty else round(t * s) end qty , case when ts <= 36 then 'Actual' else 'Forecast' end type , sum( case when ts <= 36 then qty else round(t * s) end ) over ( partition by item, extract(year from mth) ) qty_yr from s5 order by item, ts;
Sales and forecast
Output of Actual and Forecast like we did with simple model
Sales and forecastITEM MTH QTY TYPE QTY_YR---------- ------- ---- -------- ------Snowchain 2011-01 79 Actual 331...
Snowchain 2011-12 74 Actual 331Snowchain 2012-01 148 Actual 582...
Snowchain 2012-12 172 Actual 582Snowchain 2013-01 167 Actual 691...
Snowchain 2013-12 160 Actual 691Snowchain 2014-01 222 Forecast 825Snowchain 2014-02 325 Forecast 825Snowchain 2014-03 51 Forecast 825Snowchain 2014-04 2 Forecast 825Snowchain 2014-05 0 Forecast 825Snowchain 2014-06 0 Forecast 825Snowchain 2014-07 0 Forecast 825Snowchain 2014-08 1 Forecast 825Snowchain 2014-09 1 Forecast 825Snowchain 2014-10 6 Forecast 825Snowchain 2014-11 26 Forecast 825Snowchain 2014-12 191 Forecast 825
ITEM MTH QTY TYPE QTY_YR---------- ------- ---- -------- ------Sunshade 2011-01 4 Actual 377...
Sunshade 2011-12 8 Actual 377Sunshade 2012-01 2 Actual 321...
Sunshade 2012-12 3 Actual 321Sunshade 2013-01 2 Actual 263...
Sunshade 2013-12 5 Actual 263Sunshade 2014-01 1 Forecast 187Sunshade 2014-02 7 Forecast 187Sunshade 2014-03 20 Forecast 187Sunshade 2014-04 29 Forecast 187Sunshade 2014-05 17 Forecast 187Sunshade 2014-06 40 Forecast 187Sunshade 2014-07 37 Forecast 187Sunshade 2014-08 13 Forecast 187Sunshade 2014-09 9 Forecast 187Sunshade 2014-10 8 Forecast 187Sunshade 2014-11 3 Forecast 187Sunshade 2014-12 3 Forecast 187
This model also follows up/down trends
Time series slightly more conservative…
…probably due to negative forecasts in simple model
…than simple transpose model…
Data analyst develops predictive Time Series Analysis model of reality in Excel
Recreate that model in SQL for repeated application to larger datasets
Query where model fits reality or not Single SQL forecasting Done
Case closed
Danish geek Oracle SQL Evangelist Oracle PL/SQL Developer Likes to cook Reads sci-fi Member of
Danish Beer Enthusiasts
Questions ?
http://dspsd.blogspot.com
http://dk.linkedin.com/in/kibeha/
@kibeha
Kim Berg Hansen
http://goo.gl/q1YJRL
for this presentation
and scripts
Recommended