(课堂笔记)SQL 高级查询技巧:行列转换、重复数据、递归查询、连续登录

📝SQL高级查询技巧


本笔记总结了SQL数据处理中的5个核心技巧:

1)行列转换:使用CASE WHEN/PIVOT实现行转列,UNION ALL/UNPIVOT实现列转行;

2)伪列应用:ROWNUM分页查询,ROWID精确去重;

3)重复数据处理:通过ROW_NUMBER()时效性去重,利用ROWID删除重复记录;

4)递归查询:使用START WITH/CONNECT BY处理层级关系,支持上下级双向查询;

5)连续登录分析:采用"日期-序号"差值法识别连续日期,通过三步计算连续天数。


每种方法均提供标准语法模板和典型应用场景说明。


📚 课堂笔记:SQL 高级查询技巧

本指南涵盖了数据处理中常用的几个核心技巧,包括数据重塑、伪列应用、重复值处理、递归查询和连续问题分析。


一、 行列转换

核心思想:同一份数据,根据分析或展示的需要,可以在行式存储和列式存储之间转换。

1. 行转列 (多行 → 多列)

场景:将某个字段的不同值作为新的列头,多行数据聚合到一行展示。

三种方法对比

方法 语法/关键词 优点 缺点
Plan A CASE WHEN 标准SQL,所有数据库通用 语句较长,列多时书写繁琐
Plan B DECODE() Oracle 特有,语法简洁 数据库兼容性差
Plan C PIVOT 语法清晰,专为此场景设计 Oracle 11g+ 支持,其他数据库语法不同

示例:将每个学生的多门成绩转成一行。

sql

复制代码
-- 【核心示例】成绩表行转列
-- 原始数据:张三  语文 98;张三  数学 76...
-- 目标结果:张三  98  76  90

-- Plan A: CASE WHEN (推荐,通用性最强)
SELECT 
    SNAME,
    SUM(CASE WHEN CLASS = '语文' THEN SCORE END) AS 语文,
    SUM(CASE WHEN CLASS = '数学' THEN SCORE END) AS 数学,
    SUM(CASE WHEN CLASS = '英语' THEN SCORE END) AS 英语
FROM T_SCORE
GROUP BY SNAME;

-- Plan C: PIVOT (语法最清晰)
SELECT * FROM T_SCORE
PIVOT (SUM(SCORE) FOR CLASS IN ('语文' AS 语文, '数学' AS 数学, '英语' AS 英语));

-- PIVOT 语法模板:
-- PIVOT ( 聚合函数(指标列) FOR 待转换的列名 IN (列值1 AS 新列名1, 列值2 AS 新列名2...) )

2. 列转行 (多列 → 多行)

场景:将一行中多个列的数据,合并转换为一列中的多行数据。

两种方法对比

方法 语法/关键词 优点
Plan A UNION ALL 通用性强,所有数据库都支持
Plan B UNPIVOT 语法简洁,性能通常更好

示例:将每个人的成绩从一行转回多行。

sql

复制代码
-- 【核心示例】成绩表列转行
-- 原始数据:张三  98  76  90
-- 目标结果:张三  语文  98; 张三  数学  76...

-- Plan A: UNION ALL
SELECT SNAME, '语文' AS 科目, 语文 AS 成绩 FROM TT_SCORE
UNION ALL
SELECT SNAME, '数学' AS 科目, 数学 AS 成绩 FROM TT_SCORE
UNION ALL
SELECT SNAME, '英语' AS 科目, 英语 AS 成绩 FROM TT_SCORE;

-- Plan B: UNPIVOT
SELECT SNAME, 科目, 成绩 FROM TT_SCORE
UNPIVOT (成绩 FOR 科目 IN (语文, 数学, 英语));

-- UNPIVOT 语法模板:
-- UNPIVOT ( 数值列的列名 FOR 合并后列名 IN (原列头1, 原列头2, 原列头3...) )

二、 伪列

核心概念:像表里的列,但实际不存在,是数据库自动生成的。

  1. ROWNUM

    • 作用 :为查询结果集动态分配行号(从1开始)。

    • 注意ROWNUMWHERE 判断时生成,不能直接用于 >>=

    • 主要用途 :实现分页查询

    sql

    复制代码
    -- 【分页模板】获取第6到第10条数据
    SELECT * FROM (
        SELECT ROWNUM AS RN, E.* FROM EMP E
    )
    WHERE RN BETWEEN 6 AND 10;
    
    -- 正确用法:取前N行
    SELECT * FROM EMP WHERE ROWNUM <= 4;
    -- 错误用法:WHERE ROWNUM > 2 (无结果)
  2. ROWID

    • 作用 :表示数据行在数据库中物理存储的地址,是唯一的18位字符串。

    • 主要用途:删除重复记录时精确定位。


三、 重复数据处理

  1. 查询重复 :使用 GROUP BY + HAVING COUNT(1) > 1

  2. 去重显示

    • DISTINCTSELECT DISTINCT DEPTNO FROM EMP;

    • GROUP BYSELECT DEPTNO FROM EMP GROUP BY DEPTNO;

    • ROW_NUMBER():保留每组内最新的一条记录(时效性去重)。

    sql

    复制代码
    -- 【时效性去重】每个部门只保留入职最晚的那位员工信息
    SELECT * FROM (
        SELECT
            ROW_NUMBER() OVER (PARTITION BY DEPTNO ORDER BY HIREDATE DESC) AS RN,
            E.*
        FROM EMP E
    )
    WHERE RN = 1; -- 分组内按时间排序后,序号为1的即是最新的一条
  3. 删除重复 :利用 ROWID 的唯一性,保留一条,删除其余。

    sql

    复制代码
    -- 【删除重复模板】保留每个EMPNO下ROWID最大的那一行
    DELETE FROM EMP
    WHERE ROWID NOT IN (
        SELECT MAX(ROWID) FROM EMP
        GROUP BY EMPNO  -- 替换为标识重复的业务字段
    );
    -- COMMIT; -- 数据无价,操作前务必备份!

四、 递归查询 (层次查询)

场景:处理表中自带的上下级关系(如组织架构、物料清单)。

  • 关键语法

    • START WITH:定义递归的根节点(起点)。

    • CONNECT BY PRIOR:定义父子关系的连接条件

    • LEVEL伪列,表示当前数据所在的层级。

  • 规律

    • 向下查(找下属):CONNECT BY PRIOR 父 = 子

    • 向上查(找领导):CONNECT BY PRIOR 子 = 父

sql

复制代码
-- 【核心示例1】从'KING'开始,向下找所有下属
SELECT LEVEL, E.* FROM EMP E
START WITH ENAME = 'KING'
CONNECT BY PRIOR EMPNO = MGR; -- PRIOR在父键旁,表示向下遍历

-- 【核心示例2】从'SMITH'开始,向上找所有领导
SELECT LEVEL, E.* FROM EMP E
START WITH ENAME = 'SMITH'
CONNECT BY PRIOR MGR = EMPNO; -- PRIOR在子键旁,表示向上遍历

-- 【高级用法】生成连续数据 (1到100的偶数)
SELECT LEVEL AS ID FROM DUAL
WHERE MOD(LEVEL, 2) = 0
CONNECT BY LEVEL <= 100;

-- 【高级用法】获取血缘关系链
SELECT 
    LEVEL,
    SYS_CONNECT_BY_PATH(ENAME, '->') AS "领导链条" -- 将路径用'->'拼接
FROM EMP
START WITH ENAME = 'KING'
CONNECT BY PRIOR EMPNO = MGR;

五、 连续登录问题

场景:计算用户连续登录的天数,找出长期活跃用户。

核心算法日期减去排序序号 。如果日期连续,登录日期 - 排序序号 的差值相同。

sql

复制代码
-- 【核心算法】计算每个用户的连续登录情况
WITH LOGIN_PREP AS (
    SELECT
        USER_ID,
        LOGIN_DATE,
        -- 1. 按用户对登录日期排序,生成序号 RN (1,2,3...)
        ROW_NUMBER() OVER (PARTITION BY USER_ID ORDER BY LOGIN_DATE) AS RN,
        -- 2. 核心:日期减去序号,差值相同的即为连续日期
        LOGIN_DATE - ROW_NUMBER() OVER (PARTITION BY USER_ID ORDER BY LOGIN_DATE) AS DATE_DIFF
    FROM T_LOGIN
),
CONTINUOUS_DAYS AS (
    SELECT
        USER_ID,
        DATE_DIFF,
        COUNT(1) AS CONTINUOUS_CNT, -- 本次连续的天数
        MIN(LOGIN_DATE) AS START_DATE,
        MAX(LOGIN_DATE) AS END_DATE
    FROM LOGIN_PREP
    GROUP BY USER_ID, DATE_DIFF
)
-- 最终查询:找出每个用户的最大连续天数和最近连续天数
SELECT
    USER_ID,
    MAX(CONTINUOUS_CNT) AS "最大连续天数",
    LAST_VALUE(CONTINUOUS_CNT) OVER (PARTITION BY USER_ID ORDER BY END_DATE ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS "最近连续天数"
FROM CONTINUOUS_DAYS
GROUP BY USER_ID;

解题三步走

  1. 排序 :用 ROW_NUMBER() 为每次登录生成序号。

  2. 做差 :计算 登录日期 - 序号,得到分组标识 DATE_DIFF

  3. 聚合 :按 用户DATE_DIFF 分组,统计数量 COUNT(1) 即为连续天数。

相关推荐
旖-旎5 天前
深搜(二叉树的所有路径)(6)
c++·算法·leetcode·深度优先·递归
khalil10206 天前
代码随想录算法训练营Day-31贪心算法 | 56. 合并区间、738. 单调递增的数字、968. 监控二叉树
数据结构·c++·算法·leetcode·贪心算法·二叉树·递归
旖-旎7 天前
深搜(二叉树剪枝)(3)
数据结构·c++·算法·力扣·剪枝·递归
旖-旎9 天前
递归(快速幂)(5)
c++·算法·力扣·递归
旖-旎11 天前
递归(汉诺塔问题)(1)
c++·学习·算法·leetcode·深度优先·递归
Kinghiee15 天前
从零打造生产级前端错误监控 SDK:架构设计与 Vue3 实践
前端·javascript·vue.js·去重·错误捕获·上报·离线持久化
♛识尔如昼♛16 天前
C 基础(8) - 函数
c语言·指针·递归·函数
进击的荆棘18 天前
递归、搜索与回溯——递归
算法·leetcode·递归
王老师青少年编程22 天前
csp信奥赛c++中的递归和递推研究
c++·算法·递归·递推·csp·信奥赛