You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改SQL语句实现每日所有取消类型的计数(含0值)

问题:如何统计每日所有取消类型的订阅取消次数(无数据时显示0)

问题描述

我有一张订阅取消表,表结构包含USER_ID、CANCEL_TYPE、OCCURRED_AT三个字段。其中CANCEL_TYPE的可选值固定为:“Cancelled Free Trial”、“Cancelled Paying”、“Set Cancellation”。

需求是按OCCURRED_AT的日期和CANCEL_TYPE分组,统计每日每种取消类型的用户取消次数。当前使用的SQL语句如下:

select
    extract(date from occurred_at) as date, cancel_type, COUNT(customer_id) as total
from TABLE
group by date, cancel_type
order by date, cancel_type

遇到的问题:部分日期没有任何取消记录,或者某日期下部分取消类型没有数据,导致这些组合不会出现在结果中。需要修改语句,让所有日期的所有取消类型都返回,无数据时显示0。


解决方案

核心思路是先构建完整的日期范围 + 所有取消类型的组合维度,再左连接原表进行统计,确保所有可能的分组都被覆盖。

1. 定义所有取消类型

因为取消类型是固定的3种,用UNION ALL生成临时数据集:

SELECT 'Cancelled Free Trial' AS cancel_type UNION ALL
SELECT 'Cancelled Paying' AS cancel_type UNION ALL
SELECT 'Set Cancellation' AS cancel_type

2. 生成目标日期范围

不同数据库生成连续日期的语法不同,以下是主流数据库的实现方式:

PostgreSQL

使用generate_series快速生成连续日期:

SELECT generate_series(
    (SELECT MIN(extract(date from occurred_at)) FROM 你的表名),
    (SELECT MAX(extract(date from occurred_at)) FROM 你的表名),
    INTERVAL '1 day'
)::date AS date

MySQL 8.0+

用递归CTE生成日期序列:

WITH RECURSIVE date_range AS (
    SELECT MIN(DATE(occurred_at)) AS date FROM 你的表名
    UNION ALL
    SELECT date + INTERVAL 1 DAY FROM date_range
    WHERE date + INTERVAL 1 DAY <= (SELECT MAX(DATE(occurred_at)) FROM 你的表名)
)
SELECT date FROM date_range

SQL Server

用递归CTE生成日期,需开启最大递归次数:

WITH date_range AS (
    SELECT CAST(MIN(occurred_at) AS DATE) AS date FROM 你的表名
    UNION ALL
    SELECT DATEADD(DAY, 1, date) FROM date_range
    WHERE DATEADD(DAY, 1, date) <= (SELECT CAST(MAX(occurred_at) AS DATE) FROM 你的表名)
)
SELECT date FROM date_range OPTION (MAXRECURSION 0)

3. 完整SQL示例(以PostgreSQL为例)

将日期范围和取消类型做笛卡尔积,左连接原表统计,用COALESCE把空值转为0:

WITH all_cancel_types AS (
    SELECT 'Cancelled Free Trial' AS cancel_type UNION ALL
    SELECT 'Cancelled Paying' AS cancel_type UNION ALL
    SELECT 'Set Cancellation' AS cancel_type
),
date_range AS (
    SELECT generate_series(
        (SELECT MIN(extract(date from occurred_at)) FROM 你的表名),
        (SELECT MAX(extract(date from occurred_at)) FROM 你的表名),
        INTERVAL '1 day'
    )::date AS date
)
SELECT
    dr.date,
    ct.cancel_type,
    COALESCE(COUNT(t.user_id), 0) AS total
FROM date_range dr
CROSS JOIN all_cancel_types ct
LEFT JOIN 你的表名 t
    ON dr.date = extract(date from t.occurred_at)
    AND ct.cancel_type = t.cancel_type
GROUP BY dr.date, ct.cancel_type
ORDER BY dr.date, ct.cancel_type;

关键提示

  • CROSS JOIN生成日期和取消类型的所有组合,确保每个日期都有3种类型的记录
  • LEFT JOIN保留所有组合,原表无对应数据时COUNT(t.user_id)返回NULL,COALESCE将其转为0
  • 如果需要固定日期范围(比如近30天),可把date_range里的MIN/MAX换成具体日期,例如'2024-05-01'和'2024-05-30'

内容的提问来源于stack exchange,提问作者Asher Canessa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 08:35:50