SQL:如果先前存在计数 > 1,则在给定月份中计数 1

标签 sql postgresql

我在名为 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/

相关文章:

java - SQLSyntaxErrorException : ORA-00979: not a GROUP BY expression

SQL - 在两个连续记录中减去日期,然后求平均值

php - INSERT ..从多个表中选择多行

python - Django 在单个查询集中组合外键

git - 提交到 git 时 stash spring-boot 中 application.properties 中的信息

postgresql - 升级 PostgreSQL 数据库。函数会发生什么?

mysql - 查询一次选择并检查 1 行并在找到正确的值时停止(MYSQL)?

php - 如何在 SQL、Codeigniter 中的一个查询中设置不同的限制不同列?

postgresql - Postgresql 中的全文搜索

postgresql - 使用表关联时保存多对多关系的策略