导读:本期聚焦于小伙伴创作的《SQL中如何统计每个月新增的用户数量?通过MIN函数获取首次登录日期并分组》,敬请观看详情。想弄清楚产品每月拉新效果,核心是要定位每个用户的首次活跃时间。不少团队直接按注册表统计,却忽略了未注册但已登录的游客群体。用MIN函数对用户登录记录做聚合,能精准取出每人最早登录日期,再依此按月份分组计数,便可得到真实新增规模。该方法兼容MySQL、PostgreSQL等主流库,无需维护额外状态字段。相比依赖注册时间,它能覆盖所有产生行为的账号,也避免脏数据导致的漏算。下文将演示建表、取首登及按月汇总的完整写法,并比较窗口函数替代思路的适用边界。

在用户行为分析里,每月新增用户数是一个基础却容易算错的指標。很多系统并没有严格的注册环节,或者注册表和实际登录表不一致,这时如果直接对注册时间做GROUP BY,就会漏掉大量先试用后补信息的用户。更稳妥的做法是:从登录流水表中,利用MIN函数求出每个用户的最早登录日期,将其视为首次活跃时间,再按年月分组统计人数。

SQL中如何统计每个月新增的用户数量?通过MIN函数获取首次登录日期并分组

一、为什么用MIN函数而不是注册时间

大多数业务都有游客体验路径,用户可能在未填手机号前就点了微信授权并生成了本地账号,登录日志已经写入,但注册表还是空的。如果报表只查注册表,这部分人不会被计入新增,导致运营误判渠道质量。MIN函数作用在登录时间字段上,无论用户后来是否补全资料,都能抓到他第一次留下痕迹的时刻。

另一个常见问题是注册时间被后台脚本回写或批量导入覆盖,造成时间失真。登录流水通常 append-only,篡改成本高,用MIN(login_time) GROUP BY user_id 得到的首次登录日期更贴近真实拉新。下面先用一张简单的登录表来演示。

-- 登录流水表
CREATE TABLE user_login (
    user_id   BIGINT,
    login_time DATETIME,
    channel   VARCHAR(20)
);

INSERT INTO user_login VALUES
(1001, '2023-01-05 10:00:00', 'app'),
(1001, '2023-01-20 12:00:00', 'app'),
(1002, '2023-01-15 09:30:00', 'web'),
(1003, '2023-02-03 18:00:00', 'app'),
(1002, '2023-02-10 08:00:00', 'web');

二、用MIN函数获取每个用户的首次登录日期

核心语句是对 user_id 分组,取 login_time 的最小值。这样每个用户只返回一行,即他的首登时间。我们可以把这一步写成子查询或CTE,方便后续按月汇总。

注意,如果 login_time 包含时分秒,直接按月份分组前需要用DATE_FORMAT或EXTRACT把年月提取出来,否则同一天的不同时刻不会被归到同一个月。以下查询在 MySQL 语法下求出首登日期:

-- 每个用户的首登日期
SELECT
    user_id,
    MIN(login_time) AS first_login
FROM user_login
GROUP BY user_id;

运行后,user_id为1001的 first_login 是2023-01-05,1002是2023-01-15,1003是2023-02-03。这个结果就是后续统计的基准。若数据量很大,建议在 user_id 和 login_time 上建联合索引,使分组扫描走索引有序性,减少临时表。

三、按月份分组统计新增用户数

把上面的首登结果作为派生表,再对外层按年月分组计数,就得到了每月新增。这里用DATE_FORMAT把首登转成年月字符串,COUNT(user_id)即为该月首次出现的人数。

该写法的优点是逻辑直观,任何支持标准SQL的数据库都能跑。缺点是派生表会先物化全部用户的首登,再分组;在亿级用户时,可改为直接嵌套聚合,或借助窗口函数优化,但MIN+GROUP BY仍是最易维护的版本。

-- 每月新增用户统计
SELECT
    DATE_FORMAT(first_login, '%Y-%m') AS month,
    COUNT(user_id) AS new_users
FROM (
    SELECT
        user_id,
        MIN(login_time) AS first_login
    FROM user_login
    GROUP BY user_id
) AS t
GROUP BY DATE_FORMAT(first_login, '%Y-%m')
ORDER BY month;

以上语句输出两行:2023-01对应2人(1001和1002),2023-02对应1人(1003)。这就清晰回答了“每个月新增多少”的问题。如果还想拆到渠道维度,只需在内外层都加上 channel 分组,并注意子查询里 channel 要跟 user_id 一起GROUP BY或使用窗口函数取首登渠道。

四、与窗口函数写法的对比

有些开发者喜欢用 ROW_NUMBER() 打序号,取 rn=1 作为首登,再分组。这种方式在只需要首登本身时没问题,但MIN函数直接聚合,不需要排序开窗,在仅求最值场景通常更省资源。

下面的示例用窗口函数达到同样目的,逻辑稍重,但在同一查询里还要顺带取首登渠道、首登IP时更灵活。实际选型应看执行计划和表规模,不要盲目追求语法新颖。

-- 窗口函数替代方案
SELECT month, COUNT(*) AS new_users
FROM (
    SELECT
        user_id,
        DATE_FORMAT(login_time, '%Y-%m') AS month,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_time) AS rn
    FROM user_login
) AS t
WHERE rn = 1
GROUP BY month
ORDER BY month;

两种写法结果一致。MIN函数方案更贴合本题“通过MIN获取首次登录并分组”的语义,也更容易给后续接手的人讲清楚。生产环境中,把首登计算结果沉淀到用户宽表,每天增量更新,能进一步降低即席查询开销。

SQLMIN函数用户新增统计修改时间:2026-08-09 11:24:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。