I’m working on a BI request and I’m stuck and need your help 🙂
Here’s the context :
My company is receiving products and selling them. Until here, nothing complex 🙂
The selling price is varying depending on my available stock and future incoming products. The operation to fix the selling price is manual. We register the historic of the price in a table.
I would like to know for each income, the total selling price. Some incomes can overlaps each other.
The stock rule is FIFO. So we first complete the first income with the firsts sold quantities the the oder one and so on. At the time of the request, it’s possible that we still have stock in our warehouse.
Here’s an example :
. Date | Stock move quantity | Sell price |
---|---|---|
01/01/24 | +120 | |
01/02/24 | -20 | 100 |
01/03/24 | -40 | 80 |
01/04/24 | +100 | |
01/05/24 | -50 | 100 |
01/06/24 | -40 | 90 |
01/07/24 | +60 | |
01/08/24 | -20 | 100 |
01/09/24 | -40 | 90 |
01/10/24 | -40 | 80 |
So the result should be :
Income 1 : 10300 -> 20 *100 + 40 *80 + 50 *100 + 10 (to complete 120 qty) *90 = 10300
Income 2 : 9100 -> 30 (the rest of sale on 01/06/24 : 40-10) *90 + 20 *100 + 40 *90 + 10 (to complete 100 qty) *80 = 9100
Income 3 : 2400 -> 30 (the rest of sale on 01/10/24 : 40-10) *80 = 2400.
There is still 30 in stock but don’t need to appear in the result.
I’m working in POSTGRE SQL
2
Answers
First CTE calculate rolling balance for every income or outcome stock move.
Accordingly
bal_in
is incoming (balance before operation) and andbal_out
is outcoming. We calculate rollup sum separately for incomes and outcomes.Our task is to JOIN the movements – in and out – with intersection of (bal_in,bal_out) for incoming and outcoming’s.
For example:
for incoming 100 bal_in=0, bal_out=120, inc103 bal_in=120 bal_out=220.
for outcoming 105 bal_in=110, bal_out=150
Intersection (JOIN) is from 110 to 120 with inc100
and 120-150 with inc103
See example.
I added 1 row of data to your example, and also introduced additional columns(identifiers) for the product and its movement. It’s clearer and clearer this way.
fiddle
An interesting problem! I will use the word "sale" for negative amounts, and for "income" I use "supply" (positive amounts).
One possible query plan is to number each stock item on the order it is supplied, and then draw items for sale in the order of stock number. That is, the first supply here generates items 1-120, the second supply generates items 121-220. On the sales side, the first sale will consume items 1-20, and second items 21-60 and so on.
We can then use these stock numbers like a time line to relate sales with supplies, that is combine sales with supplies by overlaping stock numbers.
Here is a db<>fiddle. I will explain the steps below.
The first step provides the input data which you provided with the question:
Next, we sum the amounts, ordered by timestamps, to provide the end-points of stock item number intervals. We do this separately for sales and supplies, and we sum the absolute value for sales, of course:
Next, we build the intervals from the interval endpoints, by the calculating the start-points. We no longer need the amount, but we need to know if it was a sale or supply:
We want to join the supplies with sales on overlapping stock item number intervals. For this we need to split the transactions into supplies and sales:
The next step is to join them. We use the range overlap (&&) and intersection (*) operators:
The result at this stage is
| supply | sale | amount | price |
|:——-|:—–|——-:|——:|
| 01/01/24 | 01/02/24 | 20 | 100 |
| 01/01/24 | 01/03/24 | 40 | 80 |
| 01/01/24 | 01/05/24 | 50 | 100 |
| 01/01/24 | 01/06/24 | 10 | 90 |
| 01/04/24 | 01/06/24 | 30 | 90 |
| 01/04/24 | 01/08/24 | 20 | 100 |
| 01/04/24 | 01/09/24 | 40 | 90 |
| 01/04/24 | 01/10/24 | 10 | 80 |
| 01/07/24 | 01/10/24 | 30 | 80 |
It is easy to see that the supply from 01/01/24 combines with four sales, and the sales at 01/06/24 matches two supplies, each contributing some to the sale.
All that remains here is to group by the supply date, and calculate the average profit per supply:
The result is:
| supply | avg sales price |
|:——-|—————-:|
| 01/01/24 | 92.5000000000000000 |
| 01/04/24 | 91.0000000000000000 |
| 01/07/24 | 80.0000000000000000 |
There are other approaches to this, like continuing accumulation, e.g. ValNiks solution above. It is convenient to use ranges, but it also works without. The best approach depends on circumstances and performance or other non-functional requirements.