簡體   English   中英

在 PostgreSQL 中高效計算滾動總和

[英]Computing rolling sums efficiently in PostgreSQL

假設我有一組帶有一組客戶日期的交易(購買),我想計算同一窗口中客戶的購買金額和購買數量的滾動x天總和。 我已經使用窗口函數讓它工作了,但我必須填寫客戶沒有進行任何購買的日期。 在這樣做時,我使用的是笛卡爾積。 是否有更有效的方法,以便隨着客戶數量和時間窗口的增加而更具可擴展性?

編輯:如評論中所述,我使用的是 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小提琴

你想寫RANGE '6 days' PRECEEDING而不是ROWS BETWEEN 6 PRECEDING AND CURRENT ROW嗎?

這一定是你要找的:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM