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

基于日期字段关联条件的月度用户数统计SQL查询问题

嘿,这个需求我之前做订阅系统的时候刚好碰到过!你猜的没错,必须要有一个日期月份序列表才能生成连续的月份统计结果——不然直接按用户的订阅/退订日期分组,会漏掉那些没有新增或退订操作但仍有活跃用户的月份(比如你示例里的2017-02-01)。

下面我给你拆解具体的实现步骤,分数据库类型给你写示例:

第一步:生成连续的月份序列

我们需要先得到所有要统计的月份(从最早的订阅日期到最晚的退订日期,没有退订的就到当前月份)。

如果你用PostgreSQL:

用generate_series可以快速生成序列:

WITH month_series AS (
    SELECT 
        generate_series(
            -- 从最早的订阅月份开始
            (SELECT date_trunc('month', MIN(subscribed)) FROM your_table),
            -- 到最晚的退订月份,没有退订就用当前月份
            (SELECT COALESCE(date_trunc('month', MAX(unsubscribed)), CURRENT_DATE) FROM your_table),
            INTERVAL '1 month'
        ) AS stats_month
)

如果你用MySQL:

MySQL没有generate_series,用递归CTE来生成:

WITH RECURSIVE month_series AS (
    -- 起始月份:最早的订阅月份第一天
    SELECT (SELECT DATE_FORMAT(MIN(subscribed), '%Y-%m-01') FROM your_table) AS stats_month
    UNION ALL
    -- 递归生成下一个月份
    SELECT DATE_ADD(stats_month, INTERVAL 1 MONTH)
    FROM month_series
    -- 终止条件:到最晚的退订月份或当前月份
    WHERE stats_month < (SELECT COALESCE(DATE_FORMAT(MAX(unsubscribed), '%Y-%m-01'), CURDATE()) FROM your_table)
)

第二步:关联用户表统计活跃用户数

核心是判断用户在统计月份内是否处于订阅状态:

  • 订阅日期不晚于统计月份的最后一天(用户在该月份或之前订阅)
  • 退订日期不早于统计月份的第一天(用户在该月份内还没退订),或者从未退订(unsubscribed为null)

把这个逻辑写成JOIN条件,再统计去重后的用户数:

PostgreSQL完整SQL:

WITH month_series AS (
    SELECT 
        generate_series(
            (SELECT date_trunc('month', MIN(subscribed)) FROM your_table),
            (SELECT COALESCE(date_trunc('month', MAX(unsubscribed)), CURRENT_DATE) FROM your_table),
            INTERVAL '1 month'
        ) AS stats_month
)
SELECT
    TO_CHAR(ms.stats_month, 'YYYY-MM-DD') AS date,
    COUNT(DISTINCT t.user) AS user_count
FROM month_series ms
LEFT JOIN your_table t
    ON t.subscribed <= ms.stats_month + INTERVAL '1 month - 1 day'
    AND (t.unsubscribed IS NULL OR t.unsubscribed >= ms.stats_month)
GROUP BY ms.stats_month
ORDER BY ms.stats_month;

MySQL完整SQL:

WITH RECURSIVE month_series AS (
    SELECT (SELECT DATE_FORMAT(MIN(subscribed), '%Y-%m-01') FROM your_table) AS stats_month
    UNION ALL
    SELECT DATE_ADD(stats_month, INTERVAL 1 MONTH)
    FROM month_series
    WHERE stats_month < (SELECT COALESCE(DATE_FORMAT(MAX(unsubscribed), '%Y-%m-01'), CURDATE()) FROM your_table)
)
SELECT
    DATE_FORMAT(ms.stats_month, '%Y-%m-%d') AS date,
    COUNT(DISTINCT t.user) AS user_count
FROM month_series ms
LEFT JOIN your_table t
    ON t.subscribed <= LAST_DAY(ms.stats_month)
    AND (t.unsubscribed IS NULL OR t.unsubscribed >= ms.stats_month)
GROUP BY ms.stats_month
ORDER BY ms.stats_month;

为什么需要月份序列表?

举个例子:你示例里的2017-02-01,没有用户在这个月订阅或退订,但有2个用户处于活跃状态。如果没有序列表,直接GROUP BY订阅/退订日期的话,根本不会出现这个月份的记录,也就满足不了你的需求。

如果你的统计范围是固定的(比如只统计2017年),也可以手动写死月份列表,不用动态生成,但动态生成更灵活。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:17:58