填充缺失天数的余额

问题描述 投票:0回答:1

我有一个表保存每个帐户的每日余额,但仅限于帐户发生交易的日期。以以下帐户为例:

 account_id |          updated_at           | balance 
------------+-------------------------------+---------
 7          | 2024-03-14 23:51:12.430866+00 |     400
 7          | 2024-03-15 23:44:34.791627+00 |     400
 7          | 2024-03-16 23:38:02.437022+00 |     400
 7          | 2024-03-17 23:56:52.592801+00 |     400
 7          | 2024-03-17 23:56:52.592801+00 |     400
 7          | 2024-03-17 23:56:52.592801+00 |     400
 7          | 2024-03-17 23:56:52.592801+00 |     400
 7          | 2024-03-17 23:56:52.592801+00 |     400
 7          | 2024-03-20 17:47:04.989205+00 |     400
 7          | 2024-03-20 17:47:04.989205+00 |     400
 7          | 2024-03-21 19:27:48.242602+00 |  357.27
 7          | 2024-03-22 16:19:17.737126+00 |  325.83
 7          | 2024-03-23 18:09:37.164425+00 |  325.83
 7          | 2024-03-24 22:45:58.444256+00 |  325.83
 7          | 2024-03-24 22:45:58.444256+00 |  325.83
 7          | 2024-03-25 16:46:49.831235+00 |  221.22
 7          | 2024-03-26 15:32:09.949961+00 |  150.39
 7          | 2024-03-27 21:58:14.160818+00 |  150.39
 7          | 2024-03-28 19:23:19.219855+00 |  127.08
 7          | 2024-03-29 17:33:07.109838+00 |  160.47
 7          | 2024-03-30 17:49:41.642925+00 |  210.47
 7          | 2024-04-01 16:53:34.900447+00 |  300.47

我在 2024 年 3 月 18 日和 2024 年 3 月 19 日没有任何条目。

我正在尝试构建一个表,其中包含截至昨天的所有天数的余额,包括上一个表中缺少的日期。预期结果是:

 account_id | updated_at | balance 
------------+------------+---------
 7          | 2024-03-14 |     400
 7          | 2024-03-15 |     400
 7          | 2024-03-16 |     400
 7          | 2024-03-17 |     400
 7          | 2024-03-18 |     400
 7          | 2024-03-19 |     400
 7          | 2024-03-20 |     400
 7          | 2024-03-21 |  357.27
 7          | 2024-03-22 |  325.83
 7          | 2024-03-23 |  325.83
 7          | 2024-03-24 |  325.83
 7          | 2024-03-25 |  221.22
 7          | 2024-03-26 |  150.39
 7          | 2024-03-27 |  150.39
 7          | 2024-03-28 |  127.08
 7          | 2024-03-29 |  160.47
 7          | 2024-03-30 |  210.47
 7          | 2024-04-01 |  300.47
 ....

这个表格会一直持续到昨天。

基于这个线程,我编写了以下代码片段:

WITH days AS (
        SELECT generate_series('2024-03-01'::DATE, (CURRENT_DATE - INTERVAL '1 DAY')::DATE, '1 day'::INTERVAL)::DATE AS date_
    )

    , discontinued_days AS (
        SELECT DISTINCT
            account_id
            , balance
            , days.date_
            , updated_at
        FROM days LEFT JOIN my_balance_table
        ON days.date_ >= my_balance_table.updated_at::DATE
    )                                                          
    
    , grouped AS (                                             
        SELECT
            account_id
            , balance
            , date_
            , updated_at
            , COUNT(account_id) OVER (PARTITION BY account_id ORDER BY date_) AS grp
        FROM discontinued_days                                
    )                                                         
                                                              
    SELECT DISTINCT                                           
        account_id                                                
        , COALESCE(MIN(balance) OVER (PARTITION BY account_id, grp ORDER BY date_), 0) AS bba_balance
        , date_                                                                                                       
    FROM grouped    

它在 3 月 28 日之前的日子里运行良好,但是在 3 月 29 日,它返回余额

127.08
,而预计会看到
160.47
,因为这是我们在输入表
my_balance_table
中看到的余额。 3 月 29 日之后的日子也是如此:

 account_id | balance |   date_    
------------+---------+------------
 7          |     400 | 2024-03-14
 7          |     400 | 2024-03-15
 7          |     400 | 2024-03-16
 7          |     400 | 2024-03-17
 7          |     400 | 2024-03-18
 7          |     400 | 2024-03-19
 7          |     400 | 2024-03-20
 7          |  357.27 | 2024-03-21
 7          |  325.83 | 2024-03-22
 7          |  325.83 | 2024-03-23
 7          |  325.83 | 2024-03-24
 7          |  221.22 | 2024-03-25
 7          |  150.39 | 2024-03-26
 7          |  150.39 | 2024-03-27
 7          |  127.08 | 2024-03-28
 7          |  127.08 | 2024-03-29
 7          |  127.08 | 2024-03-30
 7          |  127.08 | 2024-03-31
 7          |  127.08 | 2024-04-01

如何解决这个问题?

postgresql date series
1个回答
0
投票

“日”

我正在尝试构建一个包含所有天数余额的表

让我们首先解决一个潜在的问题。您有一个

timestamptz
列,其中没有“天”的概念。您必须设置定义您的日子的时区。否则,查询取决于当前会话的
timezone
设置
——它“有效”,直到它不起作用。

有多种方法可以做到这一点。在下面的查询中,我假设时区为“UTC”并相应地生成日期边界。

索引

您评论:

...即使设置索引后性能仍然很差[...] 在 my_balance_table 上创建索引 my_balance_table_multi_idx(account_id、updated_at DESC、余额);

索引无法使用。您的查询会筛选应用于表列的表达式

updated_at::DATE
。无论如何,这排除了上述索引。此外,它排除了 any 索引,因为该表达式 取决于当前会话
timezone
设置。所以它不是不可变的,也不能成为索引。 不要以这种方式表述您的查询。使用 sargable 过滤表达式。然后索引就会产生奇迹。甚至稍微好一点:

CREATE INDEX my_balance_table_multi_idx ON my_balance_table (account_id, updated_at DESC) INCLUDE (balance);

参见:

查询

为了优化性能,基数很重要。总共有多少行,有多少个不同的

account_id
,有多少重复项,缺失了多少天?每天可以有多个不同行吗?表是否已分区?索引、资源等

假设许多不同的

account_id
,很少有重复(只有像您的示例中那样的完整重复),很少有缺失的一天,并且我们查询单个给定的
account_id

SELECT 7 AS account_id  -- same as filter !!!
     , (d.the_day AT TIME ZONE 'utc')::date  -- don't depend on timezone setting !!!
     , b.updated_at
     , b.balance
FROM   generate_series(timestamptz '2024-03-01 0:0+0'   -- start-of-day for UTC !!!
                     , date_trunc('day', now(), 'UTC')  -- possibly off-by-1 !!!
                     , interval     '1 day') AS d(the_day)
LEFT   JOIN LATERAL (
   SELECT updated_at, balance 
   FROM   my_balance_table
   WHERE  account_id = 7  -- filter early !!!
   AND    updated_at < d.the_day + interval '1 day'  -- sargable !!!
   ORDER  BY updated_at DESC
   LIMIT  1
   ) b ON true
ORDER  BY d.the_day;

小提琴

如果这没有在 ms(而不是秒或分钟)内执行,则说明有问题。考虑聘请一名顾问来调查这一问题。 :)

date_trunc()
将时区作为第三个参数需要 Postgres 12+。

有很多细则可以确保一切正确。

相关:

要立即处理整个表,而不是单个帐户,我的相关答案中第二个查询的变体将表现更好,并且上述索引并不重要。

© www.soinside.com 2019 - 2024. All rights reserved.