I have the following data:
sale_id | sale_date | installments | total_value |
---|---|---|---|
1 | 2023/01/01 | 2 | 100.0 |
1 | 2023/02/01 | 2 | 0.0 |
1 | 2023/03/01 | 2 | 0.0 |
1 | 2023/04/01 | 3 | 90.0 |
1 | 2023/05/01 | 3 | 0.0 |
1 | 2023/06/01 | 3 | 0.0 |
2 | 2023/01/01 | 1 | 100.0 |
I need to divide the total_value of a sale_id by the next X installments. Example: In this scenario, the first row should be 50.00 and the second row also 50.00, as following:
sale_id | sale_date | installments | total_value |
---|---|---|---|
1 | 2023/01/01 | 2 | 50.0 |
1 | 2023/02/01 | 2 | 50.0 |
1 | 2023/03/01 | 2 | 0.0 |
1 | 2023/04/01 | 3 | 30.0 |
1 | 2023/05/01 | 3 | 30.0 |
1 | 2023/06/01 | 3 | 30.0 |
2 | 2023/01/01 | 1 | 100.0 |
My first idea was to get everything that total_vale > 0 and for each row get the next X rows based on the installments column, but I can’t find a way to get it with single SQL query.
Maybe I’m missing something simple, but someone could help me with ideas? I’ve tried also to do that via Function in PLpgsql, but I didn’t have a good performance with this approach.
I can’t just update everything once, since the customer needs to keep this original layout of data.
4
Answers
As Atmo suggested.
installments-row_number()
with themax(total_value)
per sale_id andinstallments
should divide it correctly.Fiddle
There exists a different approach from the other answers (and my original idea in comments) that needs no window function. It uses
generate_series
.As a bonus, it supports installment periods overlapping (but it works just fine if there aren’t any) and will generate rows if the existing ones are not enough to store all installments.
On that note, considering the records with
total_value = 0
are basically unnecessary with this method, you could consider removing them, making the table lighter and faster (in case of aFULL SCAN
) to access. I guess that depends on the constraints you talk about in your question.Use the below
SELECT
query:I was forced to do something strange with the
installments
column and maybe having the additional records is not what you want.To "restore" the behavior of the original table on this points, do a
JOIN
(INNER JOIN
restoresinstallments
and removes the additional records,LEFT OUTER JOIN
only restoresinstallments
):You can use either of them to create a view.
However, if your target is to update the table, you do not have to care about the
installments
column (it will not be updated) nor the additional records (anUPDATE
does not create records), so theUPDATE
query is:Just be careful with it (i.e. start a transaction that you can rollback) as this query cannot be repeated if anything goes wrong.
Create new group whenever new non zero installment appears, then use this groups for further analysis:
dbfiddle demo
Data will be divided using the
cte
into groups based on the sale_id and the number ofinstallments
, and each group will be given a different number using therow_number
function. Additionally, we will get the maximumtotal_value
for each group.Then the total_value per group will be
max_total_value/installments
where therow_number
<=installments
Demo here
After reading the comments and getting some remarks from @Atmo I conclude that there its could be the taken :
First
CTE
will create new group whenever new non zero installment appears.Second
CTE
will get max value and row number by the 0 based group.Demo here