【问题标题】:Postgresql select until certain total amount is reached and lockPostgresql 选择直到达到一定的总量并锁定
【发布时间】:2020-09-23 19:44:05
【问题描述】:

我有一个用户批次表。我只想选择直到我的总金额达到一定金额。

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

考虑以下查询:

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

查询结果为:

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

我想在表上做一个选择,直到余额总数为 6。那么应该只返回 ids 1、2:

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

另一个余额总计为 1 的示例。那么应该只返回 ids 1:

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

余额总数为 11 的示例。应仅返回 id 1、3、4:

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

所以,在那之后我需要用 FOR UPDATE ex 锁定这些行:

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

我尝试使用窗口功能,但它不允许锁定(用于更新)。感谢您的帮助。

【问题讨论】:

    标签: sql postgresql sum window-functions gaps-and-islands


    【解决方案1】:

    我可以select. . . for update 使用窗口函数:

    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

    【讨论】:

    • 是的,确实如此。我想这意味着当 id 1 和 id 4 的行已经加起来 6 时,不包括 id 4 的行。但在这种情况下,也许 OP 确实也想要“下”一行。
    • @stickybit 你是对的。那应该是直的&lt;。谢谢你接听。
    • 工作。拥有数百万条记录的表会出现性能问题吗?
    • @KelvinSantiago 您需要运行它才能找到答案。假设 user_id 被索引,这应该表现得很好。如果没有,那么您可以将 rtotal CTE 更改为子查询。
    【解决方案2】:

    你在找这个吗?

    with w0 as (
      select id, user_id, balance, batch_id,
         coalesce(lag(running_balance) over (partition by user_id order by batch_id asc), 0) running_balance 
      from (
          SELECT t.* ,
            sum(balance) over (partition by user_id order by batch_id asc) running_balance
          FROM tb_batch_user t 
          --where t.user_id = 1
      ) x 
    )
    select * from w0
    where running_balance < 6
    

    PS:您可以将 user_id 添加为 where 子句。看评论

    用于锁定,

    select * from tb_batch_user tb
    where tb.id in (select w0.id from w0 where running_balance < 6)
    for update 
    

    【讨论】:

    • 嗯,当添加FOR UPDATE 时,这似乎没有锁定任何东西。但它不会引发错误。
    • 更新和查询工作正常。我将使用 explain 评估此查询的性能。
    • @Derviş Kayımbaşıoğlu 有一些方法可以不遍历整个表,即停止直到找到值。在查询 SELECT t.* 中, sum(balance) over (partition by user_id order by batch_id asc) running_balance FROM tb_batch_user t
    【解决方案3】:

    这是一种使用窗口函数的方法:

    select id, balance, user_id, batch_id
    from (
        select t.*, 
            sum(balance) over(partition by user_id order by id) sum_balance
        from mytable t
        where user_id = 1
    ) t
    where sum_balance - balance < 6
    

    您需要累积余额,直到第一个等于或超过阈值。为此,您可以使用窗口sum()

    您可以将不等式条件更改为您喜欢的阈值。您还可以在子查询中更改(或删除)user_id 上的过滤。

    我们可以很容易地用一个支持for update的子查询来实现同样的逻辑:

    select *
    from mytable t
    where user_id = 1 and (
        select coalesce(sum(balance), 0)
        from mytable t1
        where t1.user_id = t.user_id and t1.id < t.id
    ) < 6
    for update
    

    Demo on DB Fiddle

    编号 |余额|用户身份 -: | ------: | ------: 1 | 2 | 1 3 | 8 | 1

    【讨论】:

    • 添加FOR UPDATE 会引发错误“窗口函数不允许FOR UPDATE”。
    • @stickybit:很好,谢谢。我用与子查询相同的解决方案更新了我的答案。
    • 我可以确认您的编辑似乎正确锁定。
    • 嗯,但另一件事:嗯,if the balance of the row with id 3 was 4 it includes the row with the id of 4 and 6... id 4 的行可能是 OP 在这种情况下想要的,但我无法想象他们也想要与id 6 排在一起。
    • @GMB 不工作,尝试使用数量 11,只应返回 1、3、4 的 ID
    【解决方案4】:

    假设(user_id, batch_id) 是一个键,您可以使用相关子查询来避免窗口函数。外部子查询获取最小的batch_id,其中balance 的总和达到或超过给定用户ID 的6。那个总和是在内部的。

    SELECT *
           FROM tb_batch_user bu1
                WHERE bu1.user_id = 1
                      AND bu1.batch_id <= (SELECT min(bu2.batch_id) batch_id
                                                  FROM tb_batch_user bu2
                                                  WHERE bu2.user_id = bu1.user_id
                                                        AND (SELECT sum(bu3.balance)
                                                                    FROM tb_batch_user bu3
                                                                    WHERE bu3.user_id = bu2.user_id
                                                                          AND bu3.batch_id <= bu2.batch_id) >= 6)
           FOR UPDATE;
    

    安装pgrowlocks extension 后,我们可以检查正确的行是否已锁定。

    SELECT *
           FROM pgrowlocks('tb_batch_user');
    

    返回:

     locked_row | locker   | multi | xids       | modes          | pids
    ------------+----------+-------+------------+----------------+---------
     (0,1)      | 10847645 | f     | {10847645} | {"For Update"} | {11996}
     (0,3)      | 10847645 | f     | {10847645} | {"For Update"} | {11996}
    

    【讨论】:

      猜你喜欢
      • 2012-05-19
      • 1970-01-01
      • 2013-01-08
      • 2019-03-16
      • 1970-01-01
      • 2012-10-21
      • 2020-05-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多