Loading......

文章背景图

SQL 面试题练习 1️⃣

2026-01-07
12
-
- 分钟

场景 1:连续登录至少两天的用户

业务需求

找出按自然天连续登录至少两天的用户(只要某一天有登录行为即算当天登录)。

表结构

user_login (user_id, login_date)

核心思路

  1. 对用户登录日期去重,避免同一天多次登录干扰;

  2. 用「登录日期 - 排序序号」生成连续登录分组标识(相同标识为连续天);

  3. 按用户 + 分组标识统计天数,筛选≥2 天的分组,最终去重得到用户 ID。

完整 SQL

sql

WITH t1 AS (
    SELECT 
        user_id,
        login_date,
        DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS group_rk 
    FROM user_login
    GROUP BY user_id, login_date
),
t2 AS (
    SELECT
        user_id,
        group_rk,
        COUNT(*) 
    FROM t1
    GROUP BY user_id, group_rk
    HAVING COUNT(*) >= 2
)
SELECT DISTINCT user_id FROM t2;

关键要点

  • DATE_SUB(login_date, INTERVAL 排序序号 DAY) 是生成连续分组的核心;

  • 先按user_id+login_date去重,避免同一天多次登录误判为连续。


场景 2:用户最大连续登录天数(含起止时间)

业务需求

统计用户最大连续登录天数,并输出对应连续时间段的最早登录时间、最晚登出时间。

表结构

user_login (user_id, login_time, logout_time)

核心思路

  1. 按用户 + 登录日期去重,保留每天最早登录、最晚登出时间;

  2. 生成连续登录分组标识;

  3. 统计每个分组的连续天数,找到用户最大天数;

  4. 关联回分组信息,输出最大天数及对应起止时间。

完整 SQL

sql

WITH daily_login AS (
    SELECT 
        user_id,
        DATE(login_time) AS login_date,
        MIN(login_time) AS daily_first_login,
        MAX(logout_time) AS daily_last_logout
    FROM user_login
    WHERE logout_time >= login_time
    GROUP BY user_id, login_date
),
continuous_group AS (
    SELECT
        user_id,
        login_date,
        daily_first_login,
        daily_last_logout,
        DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS group_id
    FROM daily_login
),
group_stats AS (
    SELECT
        user_id,
        group_id,
        COUNT(*) AS continuous_days,
        MIN(daily_first_login) AS start_login,
        MAX(daily_last_logout) AS end_logout
    FROM continuous_group
    GROUP BY user_id, group_id
),
user_max_con AS (
    SELECT
        user_id,
        MAX(continuous_days) AS max_days
    FROM group_stats
    GROUP BY user_id
)
SELECT 
    a.user_id,
    a.max_days,
    b.start_login,
    b.end_logout
FROM user_max_con a 
INNER JOIN group_stats b 
    ON a.user_id = b.user_id 
    AND a.max_days = b.continuous_days;

关键要点

  • 过滤logout_time < login_time的无效数据;

  • 连续段起止时间取分组内最早登录、最晚登出时间。


场景 3:共同好友≥3 的用户对

业务需求

找出共同好友数量≥3 的用户对(避免 1-2 和 2-1 重复的用户对)。

表结构

friend_relation (user_id, friend_id)

核心思路

  1. 匹配两个用户的共同好友,生成user1 < user2的有序用户对;

  2. 统计每个用户对的共同好友数量;

  3. 筛选数量≥3 的用户对。

完整 SQL

sql

WITH t1 AS (
    SELECT
        a.user_id AS user1,
        b.user_id AS user2,
        a.friend_id AS friend
    FROM friend_relation a 
    JOIN friend_relation b ON a.friend_id = b.friend_id
    WHERE a.user_id < b.user_id
),
t2 AS (
    SELECT
        user1,
        user2,
        COUNT(*) AS cnt
    FROM t1 
    GROUP BY user1, user2
    HAVING cnt >= 3
)
SELECT user1, user2 FROM t2;

关键要点

  • a.user_id < b.user_id 确保用户对唯一,避免重复;

  • 可先对friend_relation去重,避免重复记录导致统计错误。


场景 4:刷题用户次日留存率

业务需求

计算用户刷题的次日留存率(当天刷题的用户中,次日仍刷题的比例)。

表结构

question_practice_detail (id, device_id, question_id, result, date)

核心思路

  1. 按用户 + 刷题日期去重,避免同一天多次刷题重复统计;

  2. 左连接匹配用户次日刷题记录,标记是否留存;

  3. 按日期统计总用户数、留存用户数,计算留存率(保留 2 位小数)。

完整 SQL

sql

WITH t1 AS (
    SELECT DISTINCT
        device_id,
        date AS practice_date
    FROM question_practice_detail
),
t2 AS (
    SELECT
        a.practice_date,
        a.device_id,
        CASE WHEN b.device_id IS NOT NULL THEN 1 ELSE 0 END AS is_retention
    FROM t1 a 
    LEFT JOIN t1 b 
        ON a.device_id = b.device_id
        AND b.practice_date = DATE_ADD(a.practice_date, INTERVAL 1 DAY)
)
SELECT 
    practice_date,
    COUNT(DISTINCT device_id) AS total_users,
    SUM(is_retention) AS retention_users,
    ROUND(
        IF(COUNT(DISTINCT device_id)=0,0,SUM(is_retention)/COUNT(DISTINCT device_id)),
        2
    ) AS retention_rate
FROM t2
GROUP BY practice_date
ORDER BY practice_date;

关键要点

  • DATE_ADD(practice_date, INTERVAL 1 DAY) 匹配次日日期;

  • IF(总用户数=0,0,留存数/总用户数) 避免除以 0 错误;

  • 留存率用ROUND(...,2)格式化,便于运营查看。


通用核心技巧总结

  1. 连续日期类问题:核心是「日期 - 排序序号」生成连续分组标识;

  2. 用户对类问题:用user1 < user2确保用户对唯一,避免重复;

  3. 留存率类问题:先按用户 + 日期去重,再左连接匹配次日记录;

  4. 数据鲁棒性:过滤无效数据(如登出时间 < 登录时间)、避免除以 0 错误。

评论交流