在SQL的实际使用场景中,累计去重计数是一类很常见的需求,比如统计截至每天新增的不重复用户数、累计不重复的商品访问量等。但标准的SQL语法中,count(distinct 字段) over(partition by ... order by ...)是不被支持的,直接这么写会返回语法错误,因此需要用其他方式实现相同的效果。

通用实现方案:子查询+条件判断
最通用的思路是先给每一行的目标字段标记是否为首次出现,再对标记做累计求和,这样就能得到累计去重计数。核心逻辑是先通过子查询找到每个值第一次出现的行,再按排序规则累计统计这些首次出现的行数。
示例表结构如下,存储了用户每天的访问记录:
| id | visit_date | user_id |
|---|---|---|
| 1 | 2024-01-01 | 1001 |
| 2 | 2024-01-01 | 1002 |
| 3 | 2024-01-02 | 1001 |
| 4 | 2024-01-02 | 1003 |
| 5 | 2024-01-03 | 1002 |
实现累计去重用户数的SQL代码如下:
-- 统计截至每天的累计去重用户数
SELECT
visit_date,
user_id,
-- 对首次出现的用户标记求和,得到累计去重计数
SUM(is_first_visit) OVER(ORDER BY visit_date) AS cumulative_distinct_user_count
FROM (
SELECT
visit_date,
user_id,
-- 判断当前用户是否是第一次出现,是则标记为1,否则为0
CASE
WHEN ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY visit_date) = 1 THEN 1
ELSE 0
END AS is_first_visit
FROM visit_log
) t
ORDER BY visit_date;
这段代码的执行逻辑是,内层查询先按用户分组按日期排序,给每个用户第一次出现的访问记录打上1的标记,其余记录打0,外层查询再按日期排序对这些标记做累计求和,最终得到的就是截至每一天的累计去重用户数。
数据库特定函数实现
部分数据库提供了原生的支持累计去重计数的函数,使用起来更简洁。
PostgreSQL / Snowflake
这两个数据库支持使用count(distinct 字段) over(... rows between unbounded preceding and current row)的写法,不过需要注意窗口范围要指定为从开头到当前行,示例代码如下:
SELECT visit_date, user_id, COUNT(DISTINCT user_id) OVER(ORDER BY visit_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_distinct_user_count FROM visit_log ORDER BY visit_date;
BigQuery
BigQuery可以使用APPROX_COUNT_DISTINCT配合窗口函数实现近似的累计去重计数,适合数据量极大的场景,示例代码如下:
SELECT visit_date, user_id, APPROX_COUNT_DISTINCT(user_id) OVER(ORDER BY visit_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_distinct_user_count FROM visit_log ORDER BY visit_date;
不同方案的选择建议
如果是跨数据库的场景,优先选择子查询+条件判断的通用方案,兼容性最好;如果是特定数据库的环境,可以使用对应的原生函数,代码更简洁。如果数据量极大且可以接受近似结果,BigQuery的近似计数方案效率更高。需要注意的是,通用方案在数据量极大时可能会有性能问题,因为需要对目标字段做分组排序,实际使用时可以根据数据量加合适的索引优化。
SQL累计去重计数distinct_count_over_window窗口函数数据分析修改时间:2026-06-08 22:12:22