【问题标题】:SQL cumulative sum for each day refresh with new balance for each userSQL累计总和每天刷新每个用户的新余额
【发布时间】:2019-08-21 01:34:57
【问题描述】:

我有一张桌子,桌子上有多个用户,我想知道每天的总余额。

每个用户可以在一天内进行交易,我可以看到他们的总余额,但我想查看他们每天结束时的总余额。

所以如果我今天没有加入,但昨天我想添加我明天的余额,如果我昨天加入并进行交易,我想为用户更新余额。

SUM(total) OVER (PARTITION BY user ORDER BY DATE(time) ROWS UNBOUNDED PRECEDING) gain,

我可以做分区,但我想知道我可以在逻辑上用 SQL 做这个吗?

| Date | User | Balance
+------+------+--------
| Day1 |  A   | 100  
| Day1 |  B   |  50  
| Day1 |  C   | 100
| Day2 |  A   | 150  
| Day2 |  B   |  20 
| Day3 |  E   | 100 
| Day4 |  F   | 200 
| Day5 |  A   |  50 
| Day5 |  F   |  50 

所以

  • 第 1 天 -> A、B、C 250
  • 第 2 天 -> A 新 150 ,B 20 我知道 C(100) 昨天最后余额,所以 270
  • 第 3 天 -> E + 最后一个余额 (270) -> 370
  • 第 4 天 -> F + 其他最后余额 -> 570
  • 第 5 天 -> A 和 F 更改新余额 320

等等..

【问题讨论】:

    标签: sql postgresql amazon-redshift


    【解决方案1】:

    非常有趣的问题! :)

    step-by-step demo:db<>fiddle

    SELECT
        the_date,
        SUM(balance)
    FROM (
        SELECT DISTINCT ON (the_date, elems -> 'the_user')
            the_date,
            elems ->> 'the_user' AS the_user,
            (elems ->> 'balance')::int AS balance
        FROM (
            SELECT
                the_date::date AS the_date,
                jsonb_agg(
                    row_to_json(mytable)::jsonb
                ) OVER (ORDER BY the_date) as agg
            FROM
               mytable
        ) s,
        jsonb_array_elements(agg) as elems
        ORDER BY the_date, elems -> 'the_user', elems -> 'the_date' DESC
    ) s
    GROUP BY the_date
    

    构思草图:

    1 累计汇总所有记录。 (为了以后能够访问每一列,这些记录在查询中存储为 JSON 对象)。

    这会产生

    date   data       cum_data
    Day1   (A:100)    [(A:100)]
    Day1   (B:50)     [(A:100),(B:50)],
    Day1   (C:100)    [(A:100),(B:50),(C:100)],
    Day2   (A:150)    [(A:100),(B:50),(C:100),(A:150)],
    Day2   (B:20)     [(A:100),(B:50),(C:100),(A:150),(B:20)]
    

    您可以看到,每天的最后一条记录包含所有相关数据。每个用户的相关数据是它在数组中的最后一个元素。

    2 因此,在此之后,您对 (1.) 每天的最后一条记录以及在此 (2.) 每个用户的最后一条记录感兴趣。所以,你必须先扩展记录:

    date   cum_data                                  expansion
    Day1   [(A:100)]                                 (A:100)
    Day1   [(A:100),(B:50)],                         (A:100)
                                                     (B:50)
    Day1   [(A:100),(B:50),(C:100)],                 (A:100)     <- last A day1
                                                     (B:50)      <- last B day1
                                                     (C:100)     <- last C day1 
    Day2   [(A:100),(B:50),(C:100),(A:150)],         (A:100)
                                                     (B:50)
                                                     (C:100)
                                                     (A:150)
    Day2   [(A:100),(B:50),(C:100),(A:150),(B:20)]   (A:100)
                                                     (B:50)
                                                     (C:100)     <- last C day2 (unchanged)
                                                     (A:150)     <- last A day2 (changed)
                                                     (B:20)      <- last B day2 (changed)
    

    3 因此,下一步是获取每个用户每天的最后一次出现次数。这可以通过DISTINCT ON 完成,它获取有序组的第一条记录。在您的情况下,该组是(date, user),并且顺序是按用户的日期DESC。当然,用户的日期存储在 json 中。所以(A:100) 实际上是(A:100, day1)(A:150)(A:150, day2)。顺序是由第二个元素决定的。当然,要先获取最新的,顺序需要降序。

    这会产生

    date   cum_data                                  expansion
    Day1   [(A:100),(B:50),(C:100)],                 (A:100)     <- last A day1
                                                     (B:50)      <- last B day1
                                                     (C:100)     <- last C day1 
    
    Day2   [(A:100),(B:50),(C:100),(A:150),(B:20)]   (C:100)     <- last C day2 (unchanged)
                                                     (A:150)     <- last A day2 (changed)
                                                     (B:20)      <- last B day2 (changed)
    

    4这最后可以简单的归为date列:

    date  sum
    Day1  (A:100) + (B:50) + (C:100) = 250
    Day2  (C:100) + (A:150) + (B:20) = 270
    

    当然,对于大型数据集,累积的性能会非常低。在这种情况下,我建议编写一个遍历所有记录的简单函数;像这样:

    date list := empty list of (date, balance)
    user list := empty list of (user, balance) 
    
    for all records:
        get current date
        if current date <> previous date
           add element (previous date, sum(all balances in user list)) to date list
    
        get current user
        if current user already exists in user list then
            replace its balance
        else
            add current user to user list
    
    return date list
    

    编辑:这是一种可能的功能(比查询快得多)。它完全遵循给定的伪代码。这只是第一次抛出,我相信你可以优化代码,所以也请看这个草图:

    demo:db<>fiddle

    CREATE OR REPLACE FUNCTION  foobar() RETURNS SETOF record 
    AS $$
    DECLARE
        _record record;
        _date_rec record;
        _prev_date date;
        _user_balance int;
        _date_balance int;
    BEGIN
        CREATE TEMP TABLE user_recs (the_user text, balance int);
    
        FOR _record IN 
            SELECT * FROM mytable ORDER BY the_date
        LOOP
            IF (_prev_date IS NOT NULL AND (_record.the_date::date > _prev_date )) THEN
               SELECT 
                   SUM(ur.balance)
               FROM
                   user_recs ur
               INTO _date_balance;
    
               _date_rec = (_prev_date , _date_balance);
               RETURN NEXT _date_rec;
            END IF;
    
            SELECT balance FROM user_recs ur WHERE ur.the_user = _record.the_user
            INTO _user_balance;
    
            IF _user_balance IS NULL THEN
                INSERT INTO user_recs VALUES (_record.the_user, _record.balance);
            ELSE
                UPDATE user_recs ur SET balance = _record.balance WHERE ur.the_user = _record.the_user;
            END IF;
    
            _prev_date = _record.the_date;
        END LOOP;
    
        RETURN QUERY
             SELECT 
                 _prev_date,
                 SUM(ur.balance)::int
             FROM
                 user_recs ur;
    END;
    $$ LANGUAGE 'plpgsql'
    

    【讨论】:

    • @Axis 添加说明
    • 这看起来不错,但我犯了一个错误,我的数据库是 redshift 并且我无法运行。所以我会想办法在 Redshift 中使用。另外我还有一个问题,使用记录超过 100k 的表的效率如何?
    • 如前所述,我相信这在庞大的数据集上可能会非常慢。这就是为什么我会尝试函数方法。稍后我可以创建一个小提琴来演示。这个函数应该也适用于 Redshift
    • 你的答案绝对值得。如果可能的话,我很想在 redshift 中运行一个函数,谢谢大家
    • 新增功能。
    【解决方案2】:

    您是否在寻找每天的累计总和,例如这样?

    select user, date_trunc('day', datetimecol),
           sum(total) as daily_total,
           sum(sum(total)) over (partition by user order by date_trunc('day', datetimecol)) as running_total
    from t
    group by user, date_trunc('day', datetimecol);
    

    【讨论】:

    • 编辑问题。感谢您的帮助
    • 我做了这个,但如果有人在一天内没有进行任何交易,我不想错过它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-16
    • 1970-01-01
    相关资源
    最近更新 更多