在PostgreSQL中有效地计算滚动总和

时间:2019-01-11 15:17:02

标签: postgresql postgresql-9.3

假设我有一组交易(购买),其中包含一组客户的日期,我想计算购买金额和客户购买数量的滚动 x 天总和< / strong>。我已经使用窗口功能使它正常工作,但是我必须填写客户未进行任何购买的日期。这样,我正在使用笛卡尔积。是否有一种更有效的方法,以便随着客户数量和时间窗口的增加而具有更大的可扩展性?

编辑:如评论中所述,我使用的是PostgreSQL v9.3。

以下示例数据(请注意,某些客户在给定日期可能有0、1或多次购买):

| id | cust_id |   txn_date | amount |
|----|---------|------------|--------|
|  1 |     123 | 2017-08-17 |     10 |
|  2 |     123 | 2017-08-17 |      5 |
|  3 |     123 | 2017-08-18 |      5 |
|  4 |     123 | 2017-08-20 |     50 |
|  5 |     123 | 2017-08-21 |    100 |
|  6 |     456 | 2017-08-01 |      5 |
|  7 |     456 | 2017-08-01 |      5 |
|  8 |     456 | 2017-08-01 |      5 |
|  9 |     456 | 2017-08-30 |      5 |
| 10 |     456 | 2017-08-01 |   1000 |
| 11 |     789 | 2017-08-15 |   1000 |
| 12 |     789 | 2017-08-30 |   1000 |

这是所需的输出:

| cust_id |   txn_date | sum_dly_txns | tot_txns_7d | cnt_txns_7d |
|---------|------------|--------------|-------------|-------------|
|     123 | 2017-08-17 |           15 |          15 |           2 |
|     123 | 2017-08-18 |            5 |          20 |           3 |
|     123 | 2017-08-20 |           50 |          70 |           4 |
|     123 | 2017-08-21 |          100 |         170 |           5 |
|     456 | 2017-08-01 |         1015 |        1015 |           4 |
|     456 | 2017-08-30 |            5 |           5 |           1 |
|     789 | 2017-08-15 |         1000 |        1000 |           1 |
|     789 | 2017-08-30 |         1000 |        1000 |           1 |

以下是根据需要生成总数的SQL:

SELECT *
FROM (
    -- One row per day per user
    WITH daily_txns AS (
        SELECT
             t.cust_id
            ,t.txn_date AS txn_date
            ,SUM(t.amount) AS sum_dly_txns
            ,COUNT(t.id) AS cnt_dly_txns
        FROM transactions t
        GROUP BY t.cust_id, txn_date
    ),
    -- Every possible transaction date for every user
    dummydates AS (
        SELECT txn_date, uids.cust_id
        FROM (
            SELECT generate_series(
                 timestamp '2017-08-01'
                ,timestamp '2017-08-30'
                ,interval '1 day')::date
            ) d(txn_date)
        CROSS JOIN (SELECT DISTINCT cust_id FROM daily_txns) uids
    ),
    txns_dummied AS (
        SELECT 
             d.cust_id
            ,d.txn_date
            ,COALESCE(sum_dly_txns,0) AS sum_dly_txns
            ,COALESCE(cnt_dly_txns,0) AS cnt_dly_txns
        FROM dummydates d
        LEFT JOIN daily_txns dx
          ON d.txn_date = dx.txn_date
          AND d.cust_id = dx.cust_id
        ORDER BY d.txn_date, d.cust_id
    )

    SELECT
         cust_id
        ,txn_date
        ,sum_dly_txns
        ,SUM(COALESCE(sum_dly_txns,0)) OVER w AS tot_txns_7d
        ,SUM(cnt_dly_txns) OVER w AS cnt_txns_7d
    FROM txns_dummied
    WINDOW w AS (
        PARTITION BY cust_id
        ORDER BY txn_date
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW -- 7d moving window
        )
    ORDER BY cust_id, txn_date
    ) xfers
WHERE sum_dly_txns > 0 -- Omit dates with no transactions
;

SQL Fiddle

1 个答案:

答案 0 :(得分:1)

您要写ROWS BETWEEN 6 PRECEDING AND CURRENT ROW而不是RANGE '6 days' PRECEEDING吗?

这必须是您要寻找的:

SELECT DISTINCT
       cust_id
      ,txn_date
      ,SUM(amount) OVER (PARTITION BY cust_id, txn_date) sum_dly_txns
      ,SUM(amount) OVER (PARTITION BY cust_id ORDER BY txn_date RANGE '6 days' PRECEDING)
      ,COUNT(*) OVER (PARTITION BY cust_id ORDER BY txn_date RANGE '6 days' PRECEDING)
from transactions
ORDER BY cust_id, txn_date

编辑:由于您使用的是旧版本(我在我的postgresql 11上测试了上面的版本),因此上述要点没有多大意义,因此您需要使用老式的SQL(是,没有窗口功能)。
它的效率稍低,但是做的还不错。

WITH daily_txns AS (
        SELECT
        t.cust_id
        ,t.txn_date AS txn_date
        ,SUM(t.amount) AS sum_dly_txns
        ,COUNT(t.id) AS cnt_dly_txns
        FROM transactions t
        GROUP BY t.cust_id, txn_date
)
SELECT t1.cust_id, t1.txn_date, t1.sum_dly_txns, SUM(t2.sum_dly_txns), SUM(t2.cnt_dly_txns)
from daily_txns t1
join daily_txns t2 ON t1.cust_id = t2.cust_id and t2.txn_date BETWEEN t1.txn_date - 7 and t1.txn_date
group by t1.cust_id, t1.txn_date, t1.sum_dly_txns
order by t1.cust_id, t1.txn_date