postgresql:合并行保留一些信息,没有循环

时间:2015-08-05 12:48:00

标签: postgresql amazon-redshift

我有一个每个用户的呼叫列表,有时用分钟分隔。用户可以在这些电话中购买或不购买。 当用户在最后一次通话后45分钟内拨打电话时,我需要考虑它与第一次通话时的通话时间相同。

我需要获得最终的呼叫数量(将呼叫分开的时间少于45分钟) 以及每位用户购买东西的电话数量。

例如,我有一个这样的列表:

buyer       timestamp              bougth_flag        
tom         20150201 9:15                 1
anna        20150201 9:25                 0
tom         20150201 10:15                0
tom         20150201 10:45                1
tom         20150201 10:48                1
anna        20150201 11:50                0
tom         20150201 11:52                0
anna        20150201 11:54                0

决赛桌将是:

buyer       time_started        calls              articles_bought        
tom         20150201 9:15          1                        1
anna        20150201 9:25          1                        0
tom         20150201 10:15         3                        2
anna        20150201 10:50         2                        0
tom         20150201 11:52         1                        0

因此,我需要合并分隔少于45分钟的行,并按用户分开。 这很容易用循环,但我没有在我使用的postgresql中的循环或函数/过程。 关于如何做的任何想法?

谢谢

3 个答案:

答案 0 :(得分:1)

最大的问题是你需要每45分钟对结果进行分组,这使得它变得棘手。这个查询是一个很好的起点,但它并不完全正确。它可以帮助你开始:

SELECT a.buyer,
       MIN(a.timestamp),
       COUNT(a),
       COUNT(b),
       SUM(a.bougth_flag),
       SUM(b.bougth_flag)
FROM calls a
LEFT JOIN calls b ON (a.buyer = b.buyer
                      AND a.timestamp != b.timestamp
                      AND a.timestamp < b.timestamp
                      AND a.timestamp + '45 minutes'::INTERVAL > b.timestamp)
GROUP BY a.buyer,
         DATE_TRUNC('hour', a.timestamp) ;

结果:

┌───────┬─────────────────────┬───────┬───────┬─────┬─────┐
│ buyer │         min         │ count │ count │ sum │ sum │
├───────┼─────────────────────┼───────┼───────┼─────┼─────┤
│ tom   │ 2015-02-01 11:52:00 │     1 │     0 │   0 │   Ø │
│ anna  │ 2015-02-01 11:50:00 │     2 │     1 │   0 │   0 │
│ anna  │ 2015-02-01 09:25:00 │     1 │     0 │   0 │   Ø │
│ tom   │ 2015-02-01 09:15:00 │     1 │     0 │   1 │   Ø │
│ tom   │ 2015-02-01 10:15:00 │     4 │     3 │   2 │   3 │
└───────┴─────────────────────┴───────┴───────┴─────┴─────┘

答案 1 :(得分:1)

由于您事先并不知道“通话”将持续多长时间(您可以在一整天内每隔30分钟接听一位买家的电话 - 请参阅提问评论),您只能通过递归解决此问题CTE。 (注意我将您的列'时间戳'更改为'ts'。从不使用关键字作为表名或列名。)

WITH conversations AS (
  WITH RECURSIVE calls AS (
    SELECT buyer, ts, bought_flag, row_number() OVER (ORDER BY ts) AS conversation, 1::int AS calls
    FROM (
      SELECT buyer, ts, lag(ts) OVER (PARTITION BY buyer ORDER BY ts) AS lag, bought_flag
      FROM list) sub
    WHERE lag IS NULL OR ts - lag > interval '45 minutes'
    UNION ALL
    SELECT l.buyer, l.ts, l.bought_flag, c.conversation, c.calls + 1
    FROM list l
    JOIN calls c ON c.buyer = l.buyer AND l.ts > c.ts
    WHERE l.ts - c.ts < interval '45 minutes'
  )
  SELECT buyer, ts, bought_flag, conversation, max(calls) AS calls
  FROM calls
  GROUP BY buyer, ts, bought_flag, conversation
  order by conversation, ts
)
SELECT buyer, min(ts) AS time_started, max(calls) AS calls, sum(bought_flag) AS articles_bought
FROM conversations
GROUP BY buyer, conversation
ORDER BY time_started

几句解释:

  • 内部递归CTE的起始项有一个子查询,它从每个调用的表中获取基本数据,以及前一次调用的时间。内部CTE的起始术语中的主要查询仅保留那些没有先前呼叫(lag IS NULL)或前一次呼叫超过45分钟的行。因此,这些是我称之为“对话”的最初调用。对话获取一个列和一个id,它只是查询中的行号,另一列用于跟踪对话“呼叫”中的呼叫数。
  • 在递归术语中,添加了同一个会话中的连续调用,并且“调用”计数器递增。
  • 当呼叫非常接近时(例如10:15之后的10:45和10:48),后来的呼叫可能被多次包括,那些重复的(10:48)被放入外部CTE中,通过选择每个对话的序列中最早的呼叫。
  • 在主要查询中,最后,每个买家的每次会话都会对“buy_flag”列进行求和。

答案 2 :(得分:1)

感谢Patrick关于原始版本的通知。 你在这里肯定需要WINDOW函数,但CTE在这里是可选的。

STICKY SERVICE