【问题标题】:PostgreSQL: How to select last balance for each account on each day in a given date range? [duplicate]PostgreSQL:如何在给定日期范围内的每一天为每个帐户选择最后余额? [复制]
【发布时间】:2016-05-14 10:17:42
【问题描述】:

我正在运行 PostgreSQL 9.3,并且有一个如下所示的表:

     entry_date      | account_id | balance
---------------------+------------+---------
 2016-02-01 00:00:00 |        123 |     100
 2016-02-01 06:00:00 |        123 |     200
 2016-02-01 12:00:00 |        123 |     300
 2016-02-01 18:00:00 |        123 |     250
 2016-02-01 00:00:00 |        456 |     400
 2016-02-01 06:00:00 |        456 |     300
 2016-02-01 12:00:00 |        456 |     200
 2016-02-01 18:00:00 |        456 |     299
 2016-02-02 00:00:00 |        123 |     250
 2016-02-02 06:00:00 |        123 |     300
 2016-02-02 12:00:00 |        123 |     400
 2016-02-02 18:00:00 |        123 |     450
 2016-02-02 00:00:00 |        456 |     299
 2016-02-02 06:00:00 |        456 |     200
 2016-02-02 12:00:00 |        456 |     100
 2016-02-02 18:00:00 |        456 |       0
(16 rows)

我的目标是在给定日期范围内的每一天检索每个帐户的最终余额。所以我想要的结果是:

     entry_date      | account_id | balance
---------------------+------------+---------
 2016-02-01 18:00:00 |        123 |     250
 2016-02-01 18:00:00 |        456 |     299
 2016-02-02 18:00:00 |        123 |     450
 2016-02-02 18:00:00 |        456 |       0
(4 rows)

请注意,我的示例中的时间戳比实际情况要简洁得多...我不能总是将 18:00 作为每天的最后一个时间。

我将如何编写这个 SQL 查询?

我尝试了这个的变体:

SELECT max(entry_date), account_id, max(balance)
FROM ledger
WHERE entry_date BETWEEN '2016-02-01'::timestamp AND '2016-02-02'::timestamp
GROUP BY account_id, entry_date;

这是架构:

CREATE TABLE ledger (
  entry_date    timestamp(3),
  account_id    int,
  balance       int
);

INSERT INTO ledger VALUES ('2016-02-01T00:00:00.000Z', 123, 100);
INSERT INTO ledger VALUES ('2016-02-01T06:00:00.000Z', 123, 200);
INSERT INTO ledger VALUES ('2016-02-01T12:00:00.000Z', 123, 300);
INSERT INTO ledger VALUES ('2016-02-01T18:00:00.000Z', 123, 250);

INSERT INTO ledger VALUES ('2016-02-01T00:00:00.000Z', 456, 400);
INSERT INTO ledger VALUES ('2016-02-01T06:00:00.000Z', 456, 300);
INSERT INTO ledger VALUES ('2016-02-01T12:00:00.000Z', 456, 200);
INSERT INTO ledger VALUES ('2016-02-01T18:00:00.000Z', 456, 299);

INSERT INTO ledger VALUES ('2016-02-02T00:00:00.000Z', 123, 250);
INSERT INTO ledger VALUES ('2016-02-02T06:00:00.000Z', 123, 300);
INSERT INTO ledger VALUES ('2016-02-02T12:00:00.000Z', 123, 400);
INSERT INTO ledger VALUES ('2016-02-02T18:00:00.000Z', 123, 450);

INSERT INTO ledger VALUES ('2016-02-02T00:00:00.000Z', 456, 299);
INSERT INTO ledger VALUES ('2016-02-02T06:00:00.000Z', 456, 200);
INSERT INTO ledger VALUES ('2016-02-02T12:00:00.000Z', 456, 100);
INSERT INTO ledger VALUES ('2016-02-02T18:00:00.000Z', 456, 0);

这是一个 SQL Fiddle:http://sqlfiddle.com/#!15/56886

提前致谢!

【问题讨论】:

    标签: sql postgresql aggregate-functions greatest-n-per-group


    【解决方案1】:

    您可以将ROW_NUMBERPARTITION BY 一起使用:

    SELECT entry_date, account_id, balance
    FROM (
      SELECT entry_date, account_id, balance, 
             ROW_NUMBER() OVER (PARTITION BY account_id, entry_date::date 
                                ORDER BY entry_date DESC) AS rn
      FROM ledger
      WHERE entry_date BETWEEN '2016-02-01'::timestamp AND '2016-02-02'::timestamp) AS t
    WHERE t.rn = 1
    

    PARTITION BY 每天创建account_id 值的切片,因为entry_date 在被转换为日期值后也用于同一子句。每个切片按entry_date 降序排列,因此ROW_NUMBER = 1 对应于当天的最后一条记录。

    Demo here

    【讨论】:

      【解决方案2】:

      在Postgres中,我认为最简单的方法是distinct on

      SELECT DISTINCT ON (account_id) l.*
      FROM ledger l
      WHERE entry_date BETWEEN '2016-02-01'::timestamp AND '2016-02-02'::timestamp
      ORDER BY account_id, entry_date DESC;
      

      DISTINCT ON 根据ORDER BY 中的键对数据进行排序。然后它选择 ON 列表中键的唯一值,选择遇到的第一个值。

      编辑:

      完全同样的想法适用于一天的记录——我只是误读了最初的要求:

      SELECT DISTINCT ON (account_id, date_trunc('day', entry_date)) l.*
      FROM ledger l
      WHERE entry_date BETWEEN '2016-02-01'::timestamp AND '2016-02-02'::timestamp
      ORDER BY account_id, date_trunc('day', entry_date), entry_date DESC;
      

      【讨论】:

      • 我知道这是多么简洁,但不幸的是它只返回日期范围内最后一天的行。我需要在日期范围内的每一天为每个帐户设置一行(预计四行,得到两行)。
      猜你喜欢
      • 2010-12-08
      • 1970-01-01
      • 1970-01-01
      • 2020-03-29
      • 1970-01-01
      • 1970-01-01
      • 2021-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多