Search code examples
sqlpostgresqlsumwindow-functionsgaps-and-islands

Postgresql select until certain total amount is reached and lock


I have a table of user batches. I only want to select until my amount total reaches a certain amount.

id  | user_id | balance | batch_id 
----|---------|-------- |--------
 1  | 1       |   2     | 1
 2  | 2       |   15    | 2
 3  | 1       |   8     | 3
 4  | 1       |   5     | 4 
 5  | 2       |   7     | 5
 6  | 1       |   1     | 6
 7  | 2       |   5     | 7

Consider the following query:

SELECT * FROM tb_batch_user WHERE user_id = 1 ORDER BY batch_id asc

The result of query is:

    id  | user_id | balance | batch_id 
    ----|---------|-------- |--------
     1  | 1       |   2     | 1
     3  | 1       |   8     | 3
     4  | 1       |   5     | 4 
     6  | 1       |   1     | 6

I want to do a select on the table until the balance total is 6. Then only ids 1, 2 should be returned:

    id  | user_id | balance | batch_id 
    ----|---------|-------- |--------
     1  | 1       |   2     | 1
     3  | 1       |   8     | 3

Another example with balance total 1. Then only ids 1 should be returned:

    id  | user_id | balance | batch_id 
    ----|---------|-------- |--------
     1  | 1       |   2     | 1

Example with balance total 11. Only ids 1,3,4 should be returned:

    id  | user_id | balance | batch_id 
    ----|---------|-------- |--------
     1  | 1       |   2     | 1
     3  | 1       |   8     | 3
     4  | 1       |   5     | 4

So, after that I need to lock those lines with FOR UPDATE ex:

     SELECT * FROM tb_batch_user WHERE user_id = 1 ORDER BY batch_id asc FOR UPDATE

I tried with window function, but it doesn't allow lock (FOR UPDATE). Thanks for any help.


Solution

  • I am able to select. . . for update using window functions:

    with inparms as (
      select 1 as user_id, 6 as target
    ), rtotal as (
      select t.id, i.target,
             sum(t.balance) over (partition by t.user_id
                                      order by t.id
                                  rows between unbounded preceding
                                           and 1 preceding) as runbalance
        from tb_batch_user t
             join inparms i 
               on i.user_id = t.user_id
    )
    select t.*
      from rtotal r
           join tb_batch_user t
             on t.id = r.id
     where coalesce(r.runbalance, 0) < r.target
    for update of t;
    
    

    Fiddle here