### Analytic FIFO multiplied - part 2

This is part 2 of a three part posting on analytic FIFO picking of multiple orders. Part 2 shows an alternative way of doing the same thing as part 1 did - but this time using recursive subquery factoring in Oracle v. 11.2.

We use the same tables and same data as part 1, so read part 1 for the setup.

And just to recap - here's the picking list developed in part 1:

```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
), fifo as (
select s.loc
, s.item
, s.loc_qty
, 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.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
)
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
/
```

Yielding this output:

```LOC        ITEM       PICK_AT_LOC      ORDNO QTY_FOR_ORD
---------- ---------- ----------- ---------- -----------
1-A-02     B1                   5         73           5
1-A-20     A1                  18         51          18
1-B-11     B1                   4         51           3
1-B-11     B1                   4         73           1
1-B-15     B1                   2         51           2
1-C-04     B1                  12         51          12
1-C-05     A1                   6         73           6
2-A-02     A1                  24         51           6
2-A-02     A1                  24         62           8
2-A-02     A1                  24         73          10
2-D-23     B1                   1         51           1

11 rows selected.
```

Without further ado, here's the recursive method (I'll explain after :-) :

```with orderlinesbase as (
select o.ordno
, o.item
, o.qty
from orderline o
where ordno in (51, 62, 73)
), orderlines as (
select o.ordno
, i.item
, nvl(b.qty,0) qty
, dense_rank() over (
order by o.ordno
) rnum
from (
select distinct ordno
from orderlinesbase
) o
cross join (
select distinct item
from orderlinesbase
) i
left outer join orderlinesbase b
on b.ordno = o.ordno
and b.item = i.item
), fifo (item, loc, purch, remaining, pick_qty, ordno, rnum) as (
select i.item
, i.loc
, i.purch
, i.qty remaining
, 0 pick_qty
, 0 ordno
, 0 rnum
from inventory i
union all
select f.item
, f.loc
, f.purch
, f.remaining - greatest (
least(
f.remaining
, o.qty - nvl(sum(f.remaining) over (
partition by f.item
order by f.purch, f.loc
rows between unbounded preceding and 1 preceding
),0)
)
, 0
) remaining
, greatest(
least(
f.remaining
, o.qty - nvl(sum(f.remaining) over (
partition by f.item
order by f.purch, f.loc
rows between unbounded preceding and 1 preceding
),0)
)
, 0
) pick_qty
, o.ordno
, o.rnum
from fifo f
join orderlines o
on o.rnum = f.rnum + 1
and o.item = f.item
where f.remaining > 0
)
select f.loc
, f.item
, sum(f.pick_qty) over (
partition by f.loc, f.item
) pick_at_loc
, f.ordno
, f.pick_qty qty_for_ord
from fifo f
where f.rnum > 0
and f.pick_qty > 0
order by f.loc, f.ordno
/
```

Which yields this identical output:

```LOC        ITEM       PICK_AT_LOC      ORDNO QTY_FOR_ORD
---------- ---------- ----------- ---------- -----------
1-A-02     B1                   5         73           5
1-A-20     A1                  18         51          18
1-B-11     B1                   4         51           3
1-B-11     B1                   4         73           1
1-B-15     B1                   2         51           2
1-C-04     B1                  12         51          12
1-C-05     A1                   6         73           6
2-A-02     A1                  24         51           6
2-A-02     A1                  24         62           8
2-A-02     A1                  24         73          10
2-D-23     B1                   1         51           1

11 rows selected.
```

The recursive subquery is this one:

```), fifo (item, loc, purch, remaining, pick_qty, ordno, rnum) as (
```

It starts off with a primer to start pumping the recursion. Whatever is in the inventory is defined as the starting "remaining" qty for "recursion zero".

```   select i.item
, i.loc
, i.purch
, i.qty remaining
, 0 pick_qty
, 0 ordno
, 0 rnum
from inventory i
```

The second half of the UNION ALL handles the recursion. It selects itself and joins it to the orders. By "rnum + 1" the first recursion joins to the first order, the second recursion to the second order, and so on until there are no more orders.

```     from fifo f
join orderlines o
on o.rnum = f.rnum + 1
and o.item = f.item
where f.remaining > 0
```

In each recursion we calculate the PICK_QTY the same way as the our usual FIFO:

```        , greatest(
least(
f.remaining
, o.qty - nvl(sum(f.remaining) over (
partition by f.item
order by f.purch, f.loc
rows between unbounded preceding and 1 preceding
),0)
)
, 0
) pick_qty
```

And then we calculate a new REMAINING by subtracting the PICK_QTY - that way REMAINING is going down with each recursion and so for the second order REMAINING is the inventory that is not already allocated to the first order, and so on.

```        , f.remaining - greatest (
least(
f.remaining
, o.qty - nvl(sum(f.remaining) over (
partition by f.item
order by f.purch, f.loc
rows between unbounded preceding and 1 preceding
),0)
)
, 0
) remaining
```

It would have been nice if we could have done PICK_QTY in an inline view like we used to thus avoiding writing the same complex expression twice. But in a recursive subquery the subquery itself must be selected at "top-level" of the second half of the union all. It cannot be pushed into an inline view, so we must do it this way.

If we just joined fifo to the base orderline table, the second recursion (ordno 62) would remove all rows from fifo with item B1, because ordno 62 does not contain item B1. Therefore we need to create a fictive orderline row with qty 0 for item B1 ordno 62. This we do with this construct that makes sure all combinations of ordno and item is present in the orderlines subquery:

```     from (
select distinct ordno
from orderlinesbase
) o
cross join (
select distinct item
from orderlinesbase
) i
left outer join orderlinesbase b
on b.ordno = o.ordno
and b.item = i.item
```

And in order to join correctly in the recursion, we give the orders consecutive numbers 1, 2, 3 ...:

```        , dense_rank() over (
order by o.ordno
) rnum
```

Doing it this way with recursion we do not have the PICK_AT_LOC column readily available like when we did FIFO calculation on the total quantities. So we calculate PICK_AT_LOC with an analytic sum:

```     , sum(f.pick_qty) over (
partition by f.loc, f.item
) pick_at_loc
```

This recursive method was fun to do and has a certain elegance. But depending on your data (how big inventory and how many orders at a time) it might very well use more resources (CPU and particularly TEMP memory or even TEMP diskspace) doing the FIFO calculation over and over again for the separate orders.

The "pure" analytic version in part 1 has the advantage of doing a single (but bigger) FIFO calculation on the total quantities, but then it needs to perform more analytics to "distribute" the picked quantities on the orders. But then again - that is a single analytic calculation on the orders and then plain old joins :-)

Both methods might have their cases. I have a feeling though ;-) that the "pure" analytic wins most of the times.

Let me just quote Monty Latiolais again:
"Don’t you just love these kind of challenges? It’s why we do what we do!"

If you wish, you can download the script for this demo.

The third part of the series combines this with the route calculation.