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

如何使用窗口函数计算分城市的30日活跃用户基数?

各城市每日30日活跃用户基数统计方案

针对你遇到的窗口函数不支持COUNT(DISTINCT)、CTE求和无法跨月统计的问题,这里提供两种通用且可行的解决方案:

方案一:非等值关联(通用SQL,适配多数数据库)

核心思路是先构建城市+日期的完整维度表,再通过非等值关联匹配每个统计日期过去30天内的所有客户,最后按维度分组统计唯一客户数。

代码示例(PostgreSQL版本)

WITH date_range AS (
    -- 生成2014-01-01至2014-06-30的所有日期
    SELECT generate_series('2014-01-01'::DATE, '2014-06-30'::DATE, '1 day'::INTERVAL)::DATE AS dt
),
city_dates AS (
    -- 生成每个城市与所有统计日期的组合(确保无日期遗漏)
    SELECT DISTINCT c.cityname, dr.dt AS stat_date
    FROM clients c
    CROSS JOIN date_range dr
)
SELECT 
    cd.cityname,
    cd.stat_date,
    COUNT(DISTINCT cl.clientid) AS rolling_30d_active_users
FROM city_dates cd
LEFT JOIN clients cl 
    ON cd.cityname = cl.cityname
    AND cl.date::DATE BETWEEN cd.stat_date - INTERVAL '29 days' AND cd.stat_date
-- 限定原表数据范围,避免关联无关数据
WHERE cl.date BETWEEN '2014-01-01' AND '2014-06-30'
GROUP BY cd.cityname, cd.stat_date
ORDER BY cd.cityname, cd.stat_date;

适配MySQL的日期生成语法

如果使用MySQL,替换date_range部分为递归CTE:

WITH RECURSIVE date_range AS (
    SELECT '2014-01-01' AS dt
    UNION ALL
    SELECT DATE_ADD(dt, INTERVAL 1 DAY)
    FROM date_range
    WHERE dt < '2014-06-30'
)

方案二:窗口函数+条件聚合(适配支持窗口函数的数据库)

通过先去重每日客户活跃记录,再用窗口函数标记客户是否在统计窗口内,最后通过条件聚合实现“去重计数”,规避窗口函数不支持COUNT(DISTINCT)的限制。

代码示例

WITH client_daily AS (
    -- 去重:每个客户每日在同一城市仅保留一条记录
    SELECT DISTINCT 
        cityname, 
        date::DATE AS active_date, 
        clientid
    FROM clients
    WHERE date BETWEEN '2014-01-01' AND '2014-06-30'
),
date_range AS (
    SELECT generate_series('2014-01-01'::DATE, '2014-06-30'::DATE, '1 day'::INTERVAL)::DATE AS stat_date
),
city_dates AS (
    SELECT DISTINCT c.cityname, dr.stat_date
    FROM client_daily c
    CROSS JOIN date_range dr
),
client_window AS (
    SELECT 
        cd.cityname,
        cd.stat_date,
        cl.clientid,
        -- 判断客户活跃日期是否在当前统计日期的30天窗口内
        CASE WHEN cl.active_date BETWEEN cd.stat_date - INTERVAL '29 days' AND cd.stat_date THEN 1 ELSE 0 END AS is_in_window,
        -- 标记该客户在当前统计窗口内的首次活跃记录(用于去重)
        ROW_NUMBER() OVER (PARTITION BY cd.cityname, cd.stat_date, cl.clientid ORDER BY cl.active_date) AS rn
    FROM city_dates cd
    LEFT JOIN client_daily cl ON cd.cityname = cl.cityname
)
SELECT 
    cityname,
    stat_date,
    SUM(CASE WHEN is_in_window = 1 AND rn = 1 THEN 1 ELSE 0 END) AS rolling_30d_active_users
FROM client_window
GROUP BY cityname, stat_date
ORDER BY cityname, stat_date;

优化建议

  • 若数据量较大,建议给clients表建立(cityname, date, clientid)联合索引,大幅提升非等值关联的查询性能。
  • 部分数据库(如BigQuery、Snowflake)支持更简洁的滑动窗口语法,可直接使用COUNT(DISTINCT clientid) OVER (PARTITION BY cityname ORDER BY date RANGE BETWEEN INTERVAL 29 DAY PRECEDING AND CURRENT ROW),但需确认数据库兼容性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 06:45:34