Griffin Chow / SQL刷题模板

Created Sun, 01 Jun 2025 00:00:00 +0000 Modified Thu, 06 Nov 2025 04:24:59 +0000

类型一:时间间隔类问题

通用思路

  1. 处理日期格式后进行排序

    ROW_NUMBER() OVER (
        PARTITION BY 用户编号
        ORDER BY 时间
    ) AS 日期排序
    
  2. 求出相邻时间的差值:

    a. 序号错位相减:

    SELECT
        b.日期 - a.日期 AS 时间差
    FROM a
    LEFT JOIN b ON (
        a.日期排序 = b.日期排序 - 1
    )
    

    b. 窗口函数 LAG()

    DATEDIFF(
        日期,
        LAG(日期,1) OVER (
            PARTITION BY 用户
            ORDER BY 日期
        )
    )
    
  3. 根据题目要求,求出相应数据指标

模板题:浏览时间之差

求每个用户相邻两次浏览时间间隔小于 1500 分钟的次数

数据原表

user_idsign_datecontinues_time
10012023-08-0109:00:00
10022023-08-0110:30:00
10032023-08-0111:45:00
10042023-08-0114:20:00
10012023-08-0208:39:00
10022023-08-0211:15:00
10032023-08-0213:49:00
10042023-08-0215:50:00
10012023-08-0310:00:00
10022023-08-0312:30:00
10032023-08-0314:10:00
10042023-08-0316:45:00
10012023-08-0409:45:00
10022023-08-0412:15:00
10032023-08-0414:30:00
10042023-08-0416:20:00
10012023-08-0518:00:00
10022023-08-0512:40:00

建表语句

CREATE TABLE continues_time (
    sign_date DATE,
    user_id INT,
    continues_time TIME
);
INSERT INTO continues_time (sign_date, user_id, continues_time)
VALUES
('2023-08-01', 1001, '09:00:00.0'),
('2023-08-01', 1002, '10:30:00.0'),
('2023-08-01', 1003, '11:45:00.0'),
('2023-08-01', 1004, '14:20:00.0'),
('2023-08-02', 1001, '08:39:00.0'),
('2023-08-02', 1002, '11:15:00.0'),
('2023-08-02', 1003, '13:49:00.0'),
('2023-08-02', 1004, '15:50:00.0'),
('2023-08-03', 1001, '10:00:00.0'),
('2023-08-03', 1002, '12:30:00.0'),
('2023-08-03', 1003, '14:10:00.0'),
('2023-08-03', 1004, '16:45:00.0'),
('2023-08-04', 1001, '09:45:00.0'),
('2023-08-04', 1002, '12:15:00.0'),
('2023-08-04', 1003, '14:30:00.0'),
('2023-08-04', 1004, '16:20:00.0'),
('2023-08-05', 1001, '18:00:00.0'),
('2023-08-05', 1002, '12:40:00.0');

参考结果

user_idcnt
10012
10023
10032
10042

解题思路

  1. 按照用户分组,对浏览时间进行排序,再用 left join 连接两表,利用排序差值,形成错位相减形式
user_idDATETIMEnumDATETIME(1)num(1)
10012023-08-01 09:00:0012023-08-02 08:39:002
10012023-08-02 08:39:0022023-08-03 10:00:003
10012023-08-03 10:00:0032023-08-04 09:45:004
10012023-08-04 09:45:0042023-08-05 18:00:005
10012023-08-05 18:00:005(Null)(Null)
10022023-08-01 10:30:0012023-08-02 11:15:002
10022023-08-02 11:15:0022023-08-03 12:30:003
  1. 利用错位相减计算出时间间隔分钟数,并筛选出小于 1500 min 的数据
  2. 最后用 COUNT() 函数计数输出结果

参考代码

a. 序号错位相减:

-- 目标:统计在同一用户内,相邻两次打点(日期+时间)差值 < 1500 分钟的记录数量(步聚 3 所需指标)
-- 步骤 1:日期排序 —— 为每个用户的时间记录按时间顺序分配行号 ROW_NUMBER()
WITH a AS (
    SELECT
        user_id,
        UNIX_TIMESTAMP(CONCAT(sign_date, ' ', continues_time)) AS unix_timestamp,  -- 将日期与时间合并为时间戳,便于差值计算
        ROW_NUMBER() OVER (
            PARTITION BY user_id
            ORDER BY CONCAT(sign_date, ' ', continues_time)
        ) AS num  -- 步骤 1:日期排序生成的序号
    FROM continues_time
),
b AS (
    -- 与 CTE a 相同:用于实现 “序号错位” 自连接(步骤 2a)
    SELECT
        user_id,
        UNIX_TIMESTAMP(CONCAT(sign_date, ' ', continues_time)) AS unix_timestamp,
        ROW_NUMBER() OVER (
            PARTITION BY user_id
            ORDER BY CONCAT(sign_date, ' ', continues_time)
        ) AS num
    FROM continues_time
)
SELECT
    a.user_id,
    COUNT(a.user_id) AS cnt  -- 步骤 3:根据题目要求统计满足条件的次数
FROM a
LEFT JOIN b ON a.user_id = b.user_id
    AND a.num = b.num - 1      -- 步骤 2a:序号错位相减:将当前行与“下一行”配对
WHERE
    (b.unix_timestamp - a.unix_timestamp)/60 < 1500  -- 步骤 2:计算相邻时间差(单位:分钟)并筛选
GROUP BY a.user_id;

b. 窗口函数 LAG()

-- 目标相同:统计同一用户内,相邻两次时间差 < 1500 分钟的记录数量
WITH base AS (
    SELECT
        user_id,
        UNIX_TIMESTAMP(CONCAT(sign_date, ' ', continues_time)) AS unix_timestamp,
        LAG(UNIX_TIMESTAMP(CONCAT(sign_date, ' ', continues_time)), 1) OVER (
            PARTITION BY user_id
            ORDER BY CONCAT(sign_date, ' ', continues_time)
        ) AS prev_unix_timestamp  -- 步骤 2b:窗口函数 LAG() 获取“上一条”时间戳
    FROM continues_time
)
SELECT
    user_id,
    COUNT(*) AS cnt  -- 步骤 3:统计满足相邻差值条件的次数
FROM base
WHERE
    prev_datetime IS NOT NULL
    AND (unix_timestamp - prev_unix_timestamp)/60 < 1500  -- 步骤 2b:计算与上一条的时间差(分钟)
GROUP BY user_id;

难题:最大间隔连续登录

计算每个用户最大的连续登录天数,(间隔一天也算连续)

解释:如果一个用户在 1,3,5,6 登录游戏,则视为连续 6 天登录

数据原表

user_idlogin_datetime
1002021-12-01 19:00:00
1002021-12-01 19:30:00
1002021-12-02 21:01:00
1002021-12-03 11:01:00
1012021-12-01 19:05:00
1012021-12-01 21:05:00

参考结果

user_idstart_login_dateday_count
1002021-12-017
1002021-12-102
1012021-12-016
1022021-12-013
1042021-12-023
1052021-12-011
1052021-12-094

解题思路

  1. 对数据原表进行数据清洗,去重和日期格式转换
  2. 按照用户 id 维度,对日期进行分组排序,并用 lag() 函数求出时间间隔
  3. 判断时间间隔是否 >2 天,用 sum() 函数累计求和,为间隔天数分组
  4. 最后根据用户 id + part_id,统计每部分连续登录日期的天数。因为是含有间隔, 故使用 max(time) - min(time) + 1 来计算分组内的连续登录天数

参考代码

-- 关键步骤:
--   步骤 1:提取“去重后的登录日期”(按天);
--   步骤 2:计算与上一登录日期的差值 diffdate(单位:天);
--   步骤 3:根据 diffdate > 2 标记新的分段 part_id(累积求和实现分段编号);
--   步骤 4:按用户与分段聚合得到开始日期、结束日期并计算连续跨度 day_count(含缺失天的跨距 = MAX - MIN + 1)。

WITH distinct_login_dates AS (  -- 步骤 1:规范化为日期粒度并去重
    SELECT
        user_id,
        DATE_FORMAT(login_datetime, '%Y-%m-%d') AS login_date
    FROM login_events
    GROUP BY
        user_id,
        DATE_FORMAT(login_datetime, '%Y-%m-%d')
),
diff_calc AS (  -- 步骤 2:计算与上一条日期的间隔(天)
    SELECT
        user_id,
        login_date,
        DATEDIFF(
            login_date,
            LAG(login_date, 1) OVER (
                PARTITION BY user_id
                ORDER BY login_date
            )
        ) AS diffdate
    FROM distinct_login_dates
),
partitions AS (  -- 步骤 3:累积分段编号(间隔 > 2 天则开启新分段)
    SELECT
        user_id,
        login_date AS login_datetime,  -- 保留原字段命名风格(原代码后续称为 login_datetime)
        SUM(
            IF(diffdate > 2, 1, 0)
        ) OVER (
            PARTITION BY user_id
            ORDER BY login_date
        ) AS part_id,
        diffdate
    FROM diff_calc
),
aggregated AS (  -- 步骤 4:对每个分段做聚合
    SELECT
        user_id,
        part_id,
        MIN(login_datetime) AS start_login_date,
        MAX(login_datetime) AS end_login_date,
        DATEDIFF(MAX(login_datetime), MIN(login_datetime)) + 1 AS day_count
    FROM partitions
    GROUP BY
        user_id,
        part_id
)
SELECT
    user_id,
    start_login_date,
    day_count
FROM aggregated;

类型二:连续类问题

解题思路

  1. 处理区间(比如时间去重、格式化等操作)并进行区间范围选定/条件:方便转化成连续登录问题的操作
  2. 计算区间连续锚点(anchor/dense):在严格连续的整数序列中,值与其行号的差是常量;用该常量(anchor/dense)作为分段键,再聚合得到每段的起止与长度
  3. 分组统计:分组统计区间数,筛选出长度最大(或指定长度)的片段
  4. 注意每道题的区间条件特殊处理

模板题:连续登录问题

求每个用户近一周内最大连续活跃天数

数据原表

continues_time.event_datecontinues_time.user_idcontinues_time.time
2023-08-0110012023-08-01 09:00:00.0
2023-08-0110022023-08-01 10:30:00.0
2023-08-0110032023-08-01 11:45:00.0
2023-08-0110042023-08-01 14:20:00.0
2023-08-0210012023-08-02 08:30:00.0
2023-08-0210022023-08-02 11:15:00.0
2023-08-0210032023-08-02 13:40:00.0
2023-08-0210042023-08-02 15:50:00.0
2023-08-0310012023-08-03 10:00:00.0
2023-08-0310022023-08-03 12:30:00.0

参考结果

解题思路

  1. 对当日重复登录的用户去重,并通过 DATEDIFF(CURRENT_DATE(), event_date) <= 7 计算 “近一周” 这个范围
  2. 计算dense_dateDATE_SUB(event_date, rn),若不是相同常量则出现分段
  3. 分组统计连续登录天数,筛选出最大登录天数

参考代码

WITH recent AS (  -- 去重 + 近 7 天
    SELECT DISTINCT
        user_id,
        event_date
    FROM continues_time
    WHERE DATEDIFF(CURRENT_DATE(), event_date) <= 7
),
seq AS (  -- 计算锚点 + 段内长度
    SELECT
        user_id,
        ROW_NUMBER() OVER (
            PARTITION BY user_id
            ORDER BY event_date
        ) AS rn,
        DATE_SUB(event_date, rn) AS dense_date
    FROM recent
),
seg AS (
    SELECT
        user_id,
        dense_date,
        COUNT(*) AS seg_len
    FROM seq
    GROUP BY
        user_id,
        dense_date
)
SELECT
    user_id,
    MAX(seg_len) AS result
FROM seg
GROUP BY user_id;

注意问题

  • MySQL 需要 DATE_SUB(date, INTERVAL 'n DAY') :比如,DATE_SUB(CURRENT_DATE(), INTERVAL ‘7 DAY’)

类型三:top N 问题

解题思路

  1. 对题目条件进行分组聚合排序:聚合排序因题目条件不同而不同
    • 只要唯一第 N 行(编号唯一,且可能丢弃与第 N 名并列的行):ROW_NUMBER
    • 保留并列(编号不唯一,跳号无所谓):RANK
    • 保留并列且不跳号(编号不唯一,不丢弃与第 N 名并列的行):DENSE_RANK
    • 需要百分比/比例:PERCENT_RANK(当前行数-1 / 窗口分区总行数-1) 或 CUME_DIST(当前行数 / 窗口分区总行数)
  2. 选出符合条件的前 N 项记录
WITH ranked AS (
  SELECT
    user_id,
    score,
    ROW_NUMBER() OVER (
        PARTITION BY user_id 
        ORDER BY score 
        DESC
    ) AS rn
  FROM score_table
)
SELECT *
FROM ranked
WHERE rn <= N;

类型四:累计类问题

解题思路

全部转换成窗口问题 + 设定窗口范围(注意窗口帧的应用)

  1. 窗口帧定义两种形式(SQL Standard):
    • 简写(只有起点):ROWS 起点
    • BETWEEN AND 形式(给出起点与终点):ROWS BETWEEN 起点 AND 终点
  2. 起点/终点 可取值(起点不能在终点后面):
    • UNBOUNDED PRECEDING(从分区第一行)(默认)
    • n PRECEDING(往前 n 行)
    • CURRENT ROW(当前行)
    • n FOLLOWING(往后 n 行)
    • UNBOUNDED FOLLOWING(到分区最后一行)

模板题:统计截止到每个月份的营业总额

数据原表

count_money.timecount_money.money
2022-01-011000
2022-02-011500
2022-03-012000
2022-04-011200
2022-05-011800
2022-06-012200
2022-07-011700
2022-08-011900
2022-09-012300
2022-10-012500

建表语句

-- 建表
CREATE TABLE event_log (
    event        BIGINT,
    event_type   VARCHAR(50),
    event_time   TIMESTAMP
);

-- 插入示例数据
INSERT INTO event_log (event, event_type, event_time) VALUES
(1037176,'首次','2024-04-27 22:10:00'),
(1037176,'催催','2024-04-28 10:20:00'),
(1037176,'砸开','2024-04-28 18:42:00'),
(1037176,'催催','2024-04-28 18:49:00'),
(1037176,'首次','2024-04-29 10:20:00'),
(1037177,'首次','2024-04-29 10:20:00'),
(1037176,'催催','2024-05-01 10:20:00'),
(1037177,'砸开','2024-04-30 10:20:00'),
(1037176,'催催','2024-05-01 17:20:00'),
(1037176,'首次','2024-05-02 10:20:00'),
(1037177,'催催','2024-05-01 10:20:00');

参考结果

timesum_window
2022-01-011000
2022-02-012500
2022-03-014500
2022-04-015700
2022-05-017500
2022-06-019700
2022-07-0111400
2022-08-0113300
2022-09-0115600
2022-10-0118100

解题思路: 统计从 2022.01 月份到 2023.08 月份,截止到窗口当前行月份的营业总额

参考代码

SELECT
    count_money.time,
    SUM(count_money.money) OVER (
        ORDER BY count_money.time
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS cum_money
FROM count_money
ORDER BY time;

类型五:排序类问题

解题思路

TODO

模板题:当出现首次则重新从 1 开始排序

数据原表

eventevent_typeevent_time
1037176首次2024-04-27 22:10:00
1037176催催2024-04-28 10:20:00
1037176砸开2024-04-28 18:42:00
1037176催催2024-04-28 18:49:00
1037176首次2024-04-29 10:20:00
1037177首次2024-04-29 10:20:00
1037176催催2024-05-01 10:20:00
1037177砸开2024-04-30 10:20:00
1037176催催2024-05-01 17:20:00
1037176首次2024-05-02 10:20:00
1037177催催2024-05-01 10:20:00

参考结果

eventevent_timeevent_typern
10371762024-04-27 22:10:00首次1
10371762024-04-28 10:20:00催催2
10371762024-04-28 18:42:00砸开3
10371762024-04-28 18:49:00催催4
10371762024-04-29 10:20:00首次1
10371762024-05-01 10:20:00催催2
10371762024-05-01 17:20:00催催3
10371762024-05-02 10:20:00首次1
10371772024-04-29 10:20:00首次1
10371772024-04-30 10:20:00砸开2
10371772024-05-01 10:20:00催催3

解题思路

  1. 按照 event 分组,按时间排序,类型是遇到首次就重新从 1 开始排 序,需要对首次进行数字分组,利用 SUM() 函数,遇到首次就会+1;
  2. 再用 ROW_NUMBER() 函数,按照 event, part_id 分组,event_time 排序;

参考代码

WITH part AS (
    SELECT
        event,
        event_type,
        event_time,
        -- 分段编号:每遇到 '首次' 累加
        SUM(CASE WHEN event_type = '首次' THEN 1 ELSE 0 END)
            OVER (PARTITION BY event ORDER BY event_time) AS part_id
    FROM event_log
)
SELECT
    event,
    event_time,
    event_type,
    ROW_NUMBER() OVER (PARTITION BY event, part_id ORDER BY event_time) AS rn
FROM part
ORDER BY event, event_time;

类型六:同时类问题

解题思路

将每个用户的登录时间退出时间变成两个不同的时间点,按照时间从小到大的顺序有序排列,登录的时候就给在线人数+1退出的时候在线人数-1,每个时间点都有一个在线人数,如图所示:

时间登录用户同时在线人数计算过程(登录+1,退出-1)
19:00:00AE2A、E登录,累计值为2
19:06:00ABCE4B、C登录,且A、E未退出,累计值为4
19:12:00ABC3E退出,A、B、C未退出,累计值为3
19:18:00BD2A、C退出,原有累计值减2,D登录+1
19:24:00D1B退出,原有累计值减1

这样题目就变成一个由用户的登录和退出时间组成的有序的时间序列,求对应的在线人数的累加问题,用窗口函数 SUM() 解决

模板题:直播各科目同时在线人数

某APP推出大型在线直播课,用户可以选择报名任意一场或多场直播课。请统计每个科目最大同时在线人数(按 course_id 排序)

数据源表

课程表 course_tb:包含课程编号 course_id、课程名称 course_name、上课时间(直播开始结束时间段)course_datetime

course_tb(课程信息)
course_idcourse_namecourse_datetime
1Python2021-12-1 19:00-21:00
2SQL2021-12-2 19:00-21:00
3R2021-12-3 19:00-21:00

上课情况表 attend_tb:记录用户进入与离开某课程直播间的时间;包含用户编号 user_id、课程编号 course_id、进入时间 in_datetime、离开时间 out_datetime

attend_tb(用户进入/离开直播间记录)
user_idcourse_idin_datetimeout_datetime
10012021-12-01 19:00:222021-12-01 19:28:00
10112021-12-01 19:30:212021-12-01 19:53:00
10212021-12-01 19:50:222021-12-01 20:55:00
10312021-12-01 19:10:212021-12-01 19:50:00
10422021-12-02 19:08:552021-12-02 20:25:00
10522021-12-02 19:12:322021-12-02 20:58:00
10622021-12-02 19:05:182021-12-02 20:43:00
10732021-12-03 19:02:132021-12-03 21:08:00
10832021-12-03 19:12:032021-12-03 19:52:00

参考结果

course_idcourse_namemax_num
1Python4
2SQL4
3R3

解题思路

  1. 取用户进入直播间,并赋值 uv 为 1;取用户离开直播间,并赋值 uv 为-1;此处用 UNION ALL 连接(不去重)
  2. 使用窗口函数 sum() 计算直播间的同时在线用户数
  3. 计算各个科目直播间的同时在线最大值并按照 course_id 排序

参考代码

WITH events AS (           -- Step 1: 将区间拆成事件流
    SELECT course_id, in_datetime  AS event_time,  1  AS delta
    FROM attend_tb
    UNION ALL
    SELECT course_id, out_datetime AS event_time, -1 AS delta
    FROM attend_tb
),
ordered AS (               -- Step 2: 按课程与时间排序(控制同一时间点的处理顺序)
    SELECT
        course_id,
        event_time,
        delta
    FROM events
    -- 如果同一 event_time 既有进入(+1)又有离开(-1),
    -- 选择 delta 排序方向决定并发瞬时的处理语义:
    ORDER BY event_time, delta DESC  -- 先加后减(并发尽量不降)
--  ORDER BY event_time, delta ASC   -- 先减后加(并发先下降再上升)
),
running AS (               -- Step 3: 前缀和 = 当前时刻并发在线人数
    SELECT
        course_id,
        event_time,
        SUM(delta) OVER (
            PARTITION BY course_id
            ORDER BY event_time, delta ASC   -- 这里采用“先处理离开(-1)”语义,可按需要改成 DESC
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS concurrent_cnt
    FROM ordered
),
max_concurrent AS (        -- Step 4: 每门课程的最大并发
    SELECT
        course_id,
        MAX(concurrent_cnt) AS max_num
    FROM running
    GROUP BY course_id
)
SELECT                      -- Step 5: 关联课程名称输出结果
    c.course_id,
    c.course_name,
    m.max_num
FROM max_concurrent m
JOIN course_tb c USING (course_id)
ORDER BY c.course_id;

类型七:用户留存问题

解题思路

模板题:计算用户的次日留存率

  • 第一天登录,第二天也登陆的为次日留存,以此类推
  • 如果 in_time-进入时间 和 out_time-离开时间 跨天了,在两天里都记为该用户活跃过,结果按日期升序

数据源表

tb_user_log(用户行为日志)
iduidartical_idin_timeout_timesign_in
110190012021-11-01 10:00:002021-11-01 10:00:421
210290012021-11-01 10:00:002021-11-01 10:00:090
310390012021-11-01 10:00:002021-11-01 10:01:500
410190022021-11-02 10:00:002021-11-02 10:02:400
510290022021-11-02 10:00:002021-11-02 10:00:590
610490012021-11-02 11:00:282021-11-02 11:01:240
710190032021-11-03 11:00:552021-11-03 11:01:240
810390032021-11-03 11:00:552021-11-03 11:00:550
910590032021-11-03 11:00:532021-11-03 11:00:590
1010290022021-11-04 11:00:552021-11-04 11:00:590

解题思路 一

  1. 首先使用窗口函数,按照每个用户作为窗口,计算出每个用户如果是次日留存所对应的登陆时间,并对结果进行去重(每个用户每天最多只有一条记录)
  2. 接着按照用户进行分组,统计出每个用户是否是次日留存用户(出现了和通过窗口函数计算出的留存时间相同的时间记录),是为1,否为0
  3. 计算次日留存率

参考代码 一

-- 下面代码默认 time 是日期-时间的格式,如果仅仅是日期格式,可以不用 date(time)
WITH a AS (
    SELECT DISTINCT
        user_id,
        DATE(time) AS time,
        MIN(DATE_ADD(DATE(time), 1)) OVER (PARTITION BY user_id) AS next_day_time
    FROM user_login
),
b AS (
    SELECT
        user_id,
        -- 用户不是次日留存,指标就是 0;是次日留存,指标就是 1(因为已经去重了)
        SUM(IF(time = next_day_time, 1, 0)) AS if_next_keep
    FROM a
    GROUP BY user_id
)
-- 计算次日留存率
SELECT
    ROUND(SUM(if_next_keep) / COUNT(1), 2) AS next_keep_rate
FROM b;

解题思路 二

  1. 首先计算出每个用户的首次登陆时间:first_login_time
  2. 然后将源表user_log和首次登陆时间Join,按照用户进行分组,计算每个用户是否在第次日有留存记录,有的话就取1,否则就取0
  3. 计算次日留存率

参考代码 二

-- 计算次日留存率:用户首登次日是否再次登录
WITH a AS ( 
    SELECT
        user_id,
        MIN(time) AS first_log_time
    FROM user_login
    GROUP BY user_id
), 
b AS ( 
    SELECT
        t1.user_id,
        -- 如果用户次日登录了,那就是1,否则就是0
        -- CASE WHEN语句如果没有指定ELSE,则默认值为NULL
        -- COUNT(不会统计NULL)
        COUNT(DISTINCT CASE WHEN DATEDIFF(time, first_log_time) = 1 THEN time END) AS day1_retention
    FROM user_login AS t1
    JOIN a AS t2 ON t1.user_id = t2.user_id  
    GROUP BY t1.user_id
) 
SELECT
    ROUND(SUM(day1_retention) / COUNT(1), 2) AS next_keep_rate
FROM b;

类型八:行列互转问题(PIVOT/数据透视问题)

【行转列】直接拆开

TODO

【行转列】有基准id

解题思路

group by+sum/max/min(case when)(聚合函数 + case when的题目)

模板题:原表格有基准id(product_id)进行行转列

数据源表

Products
product_idstoreprice
0store195
0store3105
0store2100
1store170
1store380

参考结果

Result
product_idstore1store2store3
095100105
170null80

参考代码

SELECT
    product_id,
    MAX(CASE WHEN store = 'store1' THEN price END) AS store1,
    MAX(CASE WHEN store = 'store2' THEN price END) AS store2,
    MAX(CASE WHEN store = 'store3' THEN price END) AS store3
FROM Products
GROUP BY product_id
ORDER BY product_id;

【行转列】无基准id

解题思路

row_number() + group by + sum/max/min(case when) (聚合函数 + case when 的题目),原表格没有基准id需要自己构造(使用窗口函数,自己构造row_number)

模板题:原表格没有基准id进行行转列

数据源表

People
namecontinent
JackAmerica
PascalEurope
XiAsia
JaneAmerica

参考结果

Result(按洲展开列)
AmericaAsiaEurope
JackXiPascal
Jane

参考代码

WITH a AS (
    SELECT
        ROW_NUMBER() OVER (PARTITION BY continent ORDER BY name) AS id,
        name,
        continent
    FROM student
)
SELECT
    MAX(CASE continent WHEN 'America' THEN name ELSE NULL END) AS America,
    MAX(CASE continent WHEN 'Europe'  THEN name ELSE NULL END) AS Europe,
    MAX(CASE continent WHEN 'Asia'    THEN name ELSE NULL END) AS Asia
FROM a
GROUP BY id;

【列转行】按新字段规则进行查询(无中生有)

解题思路

分别查询每一行的内容,无中生有出来bin的内容,然后使用UNION ALL进行最后的拼接

模板题:原表格没有基准id进行行转列

数据源表

Sessions(session 时长,单位:秒)
session_idduration
130
2199
3299
4580
51000

参考结果

Result(会话时长分箱统计按分钟区间)
bintotal
[0-5>3
[5-10>1
[10-15>0
15 or more1

参考代码

SELECT '[0-5>' AS bin, COUNT(*) AS total 
FROM Sessions 
WHERE duration BETWEEN 0 AND 5*60 
UNION ALL 
SELECT '[5-10>' AS bin, COUNT(*) AS total 
FROM Sessions 
WHERE duration BETWEEN 5*60 AND 10*60 
UNION ALL 
SELECT '[10-15>' AS bin, COUNT(*) AS total 
FROM Sessions 
WHERE duration BETWEEN 10*60 AND 15*60 
UNION ALL 
SELECT '15 or more' AS bin, COUNT(*) AS total 
FROM Sessions 
WHERE duration >= 15*60