我在名为 orders
的 Postgres 表中有以下数据:
month_year order_id customer_id
2016-04 0001 24662
2016-05 0002 24662
2016-05 0002 24662
2016-07 0003 24662
2016-07 0003 24662
2016-07 0004 24662
2016-07 0004 24662
2016-08 0005 24662
2016-08 0006 24662
2016-08 0007 24662
2016-08 0008 24662
2016-08 0009 24662
2016-08 0010 11372
2016-08 0011 11372
2016-09 0012 24662
2016-10 0013 24662
2016-10 0014 11372
2016-11 0015 24662
2016-11 0016 11372
2016-12 0017 11372
2017-01 0018 11372
2017-01 0019 11372
SQL fiddle http://sqlfiddle.com/#!17/4efe6/1 .
我希望能够统计每月“回头客”的数量。我们可以将“重复”定义为客户曾经下过订单(无论是上个月还是几年前)。
例如,客户 24662
在 2016 年 4 月下了第一个订单。因此,在随后的任何一个月中,如果客户 24662
再次下了订单,那么他将获得 1计算当月。
我尝试了以下方法:
SELECT
month_year,
COUNT(DISTINCT(customer_id))
FROM
orders
GROUP BY
month_year
HAVING
COUNT(order_id) > 1
这给出:
month_year repeat_orders
2016-05 1
2016-07 1
2016-08 2
2016-10 2
2016-11 2
2017-01 1
但是,由于 GROUP BY
,这仅根据客户在给定月份内是否有超过 1 个订单来应用 1 计数 ,而不是他/她之前是否有过订单。
我正在寻找后者,并希望看到以下内容:
month_year repeat_orders
2016-05 1
2016-07 1
2016-08 1
2016-09 1
2016-10 2
2016-11 2
2016-12 1
2017-01 1
任何帮助将不胜感激。谢谢!
最佳答案
自连接通常表现不佳。
您可以使用窗口函数FIRST_VALUE
来获取客户出现在数据中的第一个日期,并仅扫描一次表。之后,只需将当前日期与第一个日期进行比较,看看这是否是回头客。
初步查询:
WITH
CTE
AS
(
SELECT
*
,FIRST_VALUE(month_year) OVER (PARTITION BY customer_id ORDER BY month_year) AS first_date
FROM
orders
)
SELECT
*
FROM
CTE
ORDER BY
customer_id
,month_year
,order_id
;
这会产生以下结果:
| month_year | order_id | customer_id | first_date |
|------------|----------|-------------|------------|
| 2016-08 | 0010 | 11372 | 2016-08 |
| 2016-08 | 0011 | 11372 | 2016-08 |
| 2016-10 | 0014 | 11372 | 2016-08 |
| 2016-11 | 0016 | 11372 | 2016-08 |
| 2016-12 | 0017 | 11372 | 2016-08 |
| 2017-01 | 0018 | 11372 | 2016-08 |
| 2017-01 | 0019 | 11372 | 2016-08 |
| 2016-04 | 0001 | 24662 | 2016-04 |
| 2016-05 | 0002 | 24662 | 2016-04 |
| 2016-05 | 0002 | 24662 | 2016-04 |
| 2016-07 | 0003 | 24662 | 2016-04 |
| 2016-07 | 0003 | 24662 | 2016-04 |
| 2016-07 | 0004 | 24662 | 2016-04 |
| 2016-07 | 0004 | 24662 | 2016-04 |
| 2016-08 | 0005 | 24662 | 2016-04 |
| 2016-08 | 0006 | 24662 | 2016-04 |
| 2016-08 | 0007 | 24662 | 2016-04 |
| 2016-08 | 0008 | 24662 | 2016-04 |
| 2016-08 | 0009 | 24662 | 2016-04 |
| 2016-09 | 0012 | 24662 | 2016-04 |
| 2016-10 | 0013 | 24662 | 2016-04 |
| 2016-11 | 0015 | 24662 | 2016-04 |
最终查询
WITH
CTE
AS
(
SELECT
*
,FIRST_VALUE(month_year) OVER (PARTITION BY customer_id ORDER BY month_year) AS first_date
FROM
orders
)
SELECT
month_year
,COUNT(DISTINCT CASE WHEN month_year > first_date THEN customer_id END) AS repeat_orders
,COUNT(DISTINCT CASE WHEN month_year = first_date THEN customer_id END) AS first_orders
FROM
CTE
GROUP BY
month_year
ORDER BY
month_year
;
此查询产生以下结果:
| month_year | repeat_orders | first_orders |
|------------|---------------|--------------|
| 2016-04 | 0 | 1 |
| 2016-05 | 1 | 0 |
| 2016-07 | 1 | 0 |
| 2016-08 | 1 | 1 |
| 2016-09 | 1 | 0 |
| 2016-10 | 2 | 0 |
| 2016-11 | 2 | 0 |
| 2016-12 | 1 | 0 |
| 2017-01 | 1 | 0 |
性能
如果您在(customer_id,month_year)
上创建索引,您应该消除查询计划中的排序(即它会运行得更快)。
关于SQL:如果先前存在计数 > 1,则在给定月份中计数 1,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73127680/