云计算百科
云计算领域专业知识百科平台

2026年版-SQL学习文档

往期链接:

SQL学习文档-CSDN博客

关系数据库标准语言(SQL)- 软考备战(三十一)-CSDN博客

数据库系统 – 汇总篇-CSDN博客

数据库事务与并发控制 – 软考备战(三十二)-CSDN博客

分布式数据库 – 软考备战(三十六)-CSDN博客

数据库基础概念与体系结构 – 软考备战(二十九)-CSDN博客

数据库安全性与完整性 – 软考备战(三十三)-CSDN博客

数据库设计 – 软考备战(五十一)-CSDN博客

第 00 章 · 序言与知识地图

0.1 2026 年的 SQL 行业格局

SQL 核心语法几十年相对稳定,但「合格线」持续上移,行业格局出现明确分化:

窗口函数、CTE 已成为基础能力:数据分析、数仓开发、后端开发岗位均默认要求掌握,不再是「高级选修」。

引擎生态分化明确:

  • OLTP 领域:PostgreSQL 凭借 JSONB、pgvector 向量扩展、高度的标准兼容性,成为新项目的默认选择,2026 年开发者渗透率突破 55%,新增项目选型比例达 MySQL 的 3 倍;MySQL 仍在存量 Web 生态占据主流。
  • 本地分析领域:DuckDB 成为「嵌入式数仓」事实标准,可直接查询本地文件,开发效率与性能双优,是本地数据分析、单元测试、边缘计算的首选。
  • 云数仓:Snowflake、BigQuery、Redshift 持续主导云端大规模分析场景,扫描成本、分区裁剪是核心优化点。
  • HTAP 与分布式:TiDB、OceanBase、PolarDB 等国产分布式数据库快速崛起,兼顾事务与分析能力,信创场景应用广泛。
  • 实时分析:ClickHouse、StarRocks 主导 PB 级实时 OLAP 场景。

标准与落地:最新完整版标准仍是 SQL:2023,原生 JSON、属性图查询 SQL/PGQ、多维聚合等特性持续落地,但各引擎进度差异较大。

工作模式变革:AI 辅助写 SQL 全面普及,72% 的数据团队已将 AI 编码融入日常流程;验结果、控质量、懂逻辑 比「手写 SQL 速度」更重要。

工程化成熟:分析工程成为独立岗位方向,dbt 等工具将 SQL 从「查询语句」升级为「可测试、可复用、可追溯的数据管道」。

0.2 分岗位学习侧重点

岗位方向

核心掌握模块

深入方向

后端开发

基础语法、事务、索引、JOIN 优化

执行计划、锁机制、分库分表

数据分析

聚合、窗口函数、CTE、业务建模

漏斗/留存分析、多维汇总、性能优化

数仓开发

数仓建模、ETL 逻辑、增量计算

分层设计、数据质量、性能治理

分析工程师

全栈 SQL + 工程化工具

dbt 项目、数据治理、CI/CD

0.3 知识地图(完整版)

基础层:

关系思维 → 过滤排序 → 聚合分组 → 多表 JOIN → NULL 处理 → 集合运算     ↓ 中级层:

子查询 → CTE 分层 → 条件聚合 → 窗口函数 → 视图/事务/索引直觉     ↓ 高级层:

执行计划与优化 → 数据建模(OLTP/OLAP) → 半结构化/JSON → 方言适配 → 高级特性     ↓ 工程层:

数据质量测试 → 增量与幂等设计 → dbt 工具链 → 成本治理 → 安全与合规

第 01 章 · 心智模型

1.1 SQL 的声明式本质

SQL 是声明式语言:你描述「想要什么结果集合」,数据库优化器决定「怎么算、用什么路径算」。

  • 命令式思维(程序循环):先取用户列表 → 循环每个用户查订单 → 累加金额 → 过滤结果
  • SQL 思维:声明「按用户维度聚合订单金额,保留金额大于 1000 的用户」

新手常犯错误:用循环逻辑翻译 SQL,导致写出大量相关子查询、游标,性能极差且易出错。

1.2 结果集思维三问(写 SQL 前必过)

写任何查询前,先问自己三个问题,能避免 80% 的逻辑错误:

  • 一行代表什么?(粒度 Grain):是一个用户?一笔订单?一个用户的一天?粒度模糊必然导致聚合错误。
  • JOIN 后行数会变多还是变少?:一对多 JOIN 会膨胀行数,多对一 JOIN 行数不变,多对多 JOIN 会笛卡尔式膨胀。
  • NULL 和重复键会怎么破坏结论?:空值会被聚合函数忽略、会导致 NOT IN 结果为空、会影响排序与等值判断。
  • 1.3 逻辑执行顺序(必记)

    书写顺序

    SELECT

    → DISTINCT

    → FROM

    → JOIN

    → WHERE

    → GROUP BY

    → HAVING

    → ORDER BY

    → LIMIT/OFFSET

    引擎逻辑执行顺序(从先到后)

  • FROM / JOIN:确定数据源,生成笛卡尔积后按 ON 条件过滤,得到初始结果集
  • WHERE:对行级数据过滤,此时还没分组、没计算 SELECT 别名,因此 WHERE 中不能用 SELECT 别名
  • GROUP BY:按分组键塌缩行,每组变成一行
  • HAVING:对分组后的聚合结果过滤,可引用聚合函数
  • SELECT:计算输出列、表达式、窗口函数
  • DISTINCT:对 SELECT 结果去重
  • ORDER BY:排序,此时可使用 SELECT 别名
  • LIMIT / OFFSET:截取最终结果
  • 经典问题解答

    • 为什么 WHERE 不能用 SELECT 别名?因为 WHERE 执行在 SELECT 之前。
    • 为什么聚合过滤要用 HAVING?因为 WHERE 执行在 GROUP BY 之前,还没有聚合结果。
    • 为什么窗口函数不能写在 WHERE 里?因为窗口函数在 SELECT 阶段计算,WHERE 执行时还未计算。

    1.4 OLTP vs OLAP vs HTAP

    维度

    OLTP(业务库)

    OLAP(分析/数仓)

    HTAP(混合负载)

    典型操作

    点查、写入、事务

    全表扫描、聚合、宽表关联

    同时支持业务写入与实时分析

    模型倾向

    规范化(三范式),减少冗余

    星型/雪花模型、宽表、反范式

    兼顾规范化与分析性能

    性能关注点

    索引命中率、锁、隔离级别

    扫描数据量、分区、并行度

    读写隔离、资源隔离、列式存储

    数据规模

    行级,GB-TB 级

    全量历史,TB-PB 级

    可横向扩展,PB 级

    代表引擎

    PostgreSQL、MySQL

    DuckDB、Snowflake、ClickHouse

    TiDB、OceanBase、PolarDB

    第 02 章 · 关系模型与查询骨架(基础)

    2.1 关系模型核心概念

    • 表(关系):二维结构,由行和列组成
    • 行(元组):一条完整的实体记录
    • 列(属性):实体的一个特征,有严格的数据类型
    • 主键(PK):唯一标识一行,非空且唯一
    • 外键(FK):引用另一表的主键,表达实体间关联关系
    • 约束:NOT NULL、UNIQUE、CHECK、FOREIGN KEY、DEFAULT

    常见关系与实现:

    • 一对一:共享主键,或一方持有唯一外键(UNIQUE + FK)
    • 一对多:多方持有外键(最常见,如用户-订单)
    • 多对多:通过中间表拆解为两个一对多(如学生-课程-选课表)

    2.2 规范化与反范式

    三范式核心目标:减少数据冗余,避免更新异常、插入异常、删除异常。

    • 1NF:列不可再分
    • 2NF:消除部分依赖,非主键列完全依赖主键
    • 3NF:消除传递依赖,非主键列不依赖其他非主键列

    分析场景的反范式设计: 数仓与宽表场景常故意冗余维度信息(如把用户城市、等级冗余到订单表),用空间换查询效率,减少 JOIN 次数。

    2.3 查询基础骨架

    SELECT
      column1,
      column2 * 0.8 AS discount_price,  — 表达式与别名
      CONCAT(first_name, ' ', last_name) AS full_name
    FROM schema.table_name
    WHERE status = 'active'
      AND created_at >= '2026-01-01'
    ORDER BY created_at DESC
    LIMIT 100;

    要点补充:

    • 生产环境禁止 SELECT *:拉取无关列增加 IO,大表宽表会严重拖慢性能,且表结构变更时易出问题。
    • DISTINCT 成本很高:它需要排序或哈希去重,大表上慎用;先想清楚重复行的来源,优先通过过滤或 JOIN 优化消除重复。
    • 别名可读性优先:业务含义明确,避免无意义的 a、b、c 别名。

    2.4 过滤条件(WHERE)

    常用谓词

    • 比较:=<>/!=<><=>=
    • 逻辑:ANDORNOT(注意优先级:NOT > AND > OR,复杂条件务必加括号)
    • 集合:IN (…)BETWEEN … AND …(闭区间)
    • 模糊匹配:LIKE%匹配任意字符,_匹配单个字符);PostgreSQL 支持 ILIKE 忽略大小写
    • 空值判断:只能用 IS NULL / IS NOT NULL= NULL 永远不成立

    日期过滤黄金原则:左闭右开

    尽量使用 >= 起始< 结束+1 的写法,避免 DATE() 函数包裹索引列导致索引失效。

    — 推荐:左闭右开,可命中 created_at 索引  

    WHERE created_at >= '2026-01-01'
      AND created_at < '2026-02-01'

    — 不推荐:函数包裹列,索引失效

    WHERE DATE(created_at) BETWEEN '2026-01-01' AND '2026-01-31'

    2.5 NULL 与三值逻辑(基础第一大坑)

    SQL 条件结果有三种:TRUE / FALSE / UNKNOWN,WHERE 只保留 TRUE 的行。

    核心规则

  • 任何值与 NULL 比较都是 UNKNOWN:NULL = NULL → 未知,1 != NULL → 未知,NULL > 0 → 未知
  • 聚合函数默认忽略 NULL:SUM(col) 不计空值行;COUNT(*) 计所有行,COUNT(col) 不计该列为空的行
  • NOT IN 子查询含 NULL 时,结果全为空:经典坑,反连接优先用 NOT EXISTSLEFT JOIN … IS NULL
  • 排序时 NULL 位置:不同引擎默认不同,PostgreSQL 默认为 NULLS LAST,可显式指定 NULLS FIRST/LAST
  • 2.6 排序与截取

    ORDER BY revenue DESC NULLS LAST, user_id ASC
    LIMIT 10 OFFSET 20;  — 跳过前20条,取10条

    • 无稳定 ORDER BY 时,LIMIT 结果不确定,分页可能出现重复或漏数据。
    • 大偏移量 OFFSET 性能差:OFFSET 100000 需要先扫描 100000 行再丢弃,深分页推荐用「游标分页」(基于上一页最后一个 ID 过滤)。

    2.7 常用数据类型与转换

    • 字符串:拼接、大小写转换、截取、替换、正则匹配
    • 数值:四则运算、取整(ROUND/FLOOR/CEIL)、绝对值、百分比计算
    • 日期时间:加减间隔、截断(DATE_TRUNC)、时区转换(注意 TIMESTAMP WITH TIME ZONE)
    • 类型转换:标准写法 CAST(x AS type),PostgreSQL 支持简写 x::type

    隐式转换是性能与正确性的隐形杀手:字符串与数字比较、不同时间类型比较,都可能导致索引失效或结果错误。

    第 03 章 · 过滤聚合与 JOIN(基础)

    3.1 聚合函数

    工作中最常用的五大聚合:COUNT / SUM / AVG / MIN / MAX

    SELECT
      user_id,
      COUNT(*) AS order_cnt,
      SUM(amount) AS total_revenue,
      AVG(amount) AS avg_order_amount,
      MIN(amount) AS min_amount,
      MAX(amount) AS max_amount
    FROM orders
    WHERE status = 'paid'
    GROUP BY user_id;

    规则

    • SELECT 中的非聚合列,必须出现在 GROUP BY 中(严格 SQL 模式下)。
    • GROUP BY 的列决定了「输出一行代表什么」,即最终结果的粒度。
    • 可按多列分组:GROUP BY user_id, date 代表「每个用户每天一行」。

    3.2 HAVING:分组后过滤

    SELECT user_id, SUM(amount) AS revenue
    FROM orders
    GROUP BY user_id
    HAVING SUM(amount) > 1000;

    子句

    过滤时机

    可引用的内容

    WHERE

    分组前,行级

    原始列,不能用聚合函数

    HAVING

    分组后,组级

    聚合函数、GROUP BY 列

    优化建议:能用 WHERE 提前过滤的,不要放到 HAVING。先缩小数据范围再聚合,性能更好。

    3.3 JOIN:基础分水岭

    3.3.1 JOIN 类型与行数直觉

    类型

    含义

    行数变化直觉

    INNER JOIN

    只保留两边都匹配的行

    ≤ 两侧原始行数

    LEFT JOIN

    保留左表全部,右表不匹配则补 NULL

    ≥ 左表行数(一对多会膨胀)

    RIGHT JOIN

    保留右表全部,左表不匹配则补 NULL

    可用 LEFT JOIN 改写,少用

    FULL OUTER JOIN

    两侧未匹配的都保留,对方补 NULL

    ≥ 两侧中较大的行数

    CROSS JOIN

    笛卡尔积,左表每一行匹配右表每一行

    左表行数 × 右表行数,慎用

    3.3.2 ON vs WHERE(OUTER JOIN 核心坑)

    对 LEFT JOIN 而言:

    • 条件写在 ON 里:先按条件匹配右表,不匹配的左表行仍保留,右表列补 NULL。
    • 条件写在 WHERE 里:JOIN 完成后再过滤,会把右表为 NULL 的行过滤掉,效果接近 INNER JOIN。

    — 结果:所有用户都出现,2026年无订单的用户订单列为NULL  

    SELECT u.user_id, o.order_id
    FROM users u
    LEFT JOIN orders o
      ON u.user_id = o.user_id
      AND o.order_date >= '2026-01-01';

    — 结果:只有2026年有订单的用户才出现,等价于INNER JOIN

    SELECT u.user_id, o.order_id
    FROM users u
    LEFT JOIN orders o
      ON u.user_id = o.user_id
    WHERE o.order_date >= '2026-01-01';

    3.3.3 行数膨胀与粒度对齐

    一对多 JOIN 会复制左表行(如一个用户 3 个订单,用户字段会重复 3 次)。

    • 错误做法:JOIN 明细后直接 SUM 用户维度的金额,导致金额被放大 N 倍。
    • 正确做法:先聚合到相同粒度,再 JOIN;或先去重再关联。

    3.4 多表关联经典练习

    基于用户表 users、订单表 orders、订单明细表 order_items:

  • 某月成交额 Top 10 用户:INNER JOIN + 聚合 + 排序 + LIMIT
  • 从未下单的客户列表:LEFT JOIN + WHERE 右表主键 IS NULL
  • 当月活跃用户数:注意去重,用 COUNT(DISTINCT user_id),避免明细行重复计数
  • 每个用户的首单时间与首单金额:分组取最小值
  • 第 04 章 · 集合运算与 DDL/DML(基础)

    4.1 集合运算

    把两个查询结果集当作集合操作,要求两侧列数一致、类型兼容。

    — UNION:去重合并(性能差,需要排序去重)  

    SELECT user_id FROM orders_2025
    UNION
    SELECT user_id FROM orders_2026;

    — UNION ALL:不去重合并(性能好,优先使用)  

    SELECT user_id FROM orders_2025
    UNION ALL
    SELECT user_id FROM orders_2026;

    — INTERSECT:交集  

    SELECT user_id FROM paid_users
    INTERSECT
    SELECT user_id FROM active_users;

    — EXCEPT:差集(A 有 B 没有)

    SELECT user_id FROM all_users
    EXCEPT
    SELECT user_id FROM churned_users;

    要点:

    • 优先用 UNION ALL,确定需要去重时才用 UNION。
    • 列名跟随左侧查询,ORDER BY 只能写在最外层。
    • 分析场景常用 UNION ALL 拼接分区表、不同月份的分表。

    4.2 NULL 在集合中的坑

    • NOT IN (子查询) 若子查询结果包含 NULL,整个条件结果为 UNKNOWN,查询返回空集,这是经典易错点。
    • 反连接场景优先使用 NOT EXISTSLEFT JOIN … IS NULL,性能与正确性都更优。

    4.3 DDL:数据定义语言

    建表示例(PostgreSQL 风格)

    CREATE TABLE users (
      user_id     BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
      email       TEXT NOT NULL UNIQUE,
      nickname    TEXT,
      status      TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'blocked', 'deleted')),
      created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
      updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
    );

    常见操作

    — 加列  

    ALTER TABLE users ADD COLUMN last_login_at TIMESTAMPTZ;

    — 删表  

    DROP TABLE IF EXISTS tmp_import;

    — 建索引

    CREATE INDEX idx_users_status ON users(status);

    生产环境注意:

    • 大表 ALTER TABLE 可能锁表,导致业务不可用,使用在线 DDL 工具(如 PostgreSQL 的 pg_repack)。
    • 临时表命名规范,用完及时清理,避免空间占用。

    4.4 DML:数据操作语言

    — 插入  

    INSERT INTO users (email, nickname) VALUES ('a@example.com', 'Alice');

    — 批量插入  

    INSERT INTO users (email, nickname)
    SELECT email, name FROM temp_users;

    — 更新  

    UPDATE users
    SET status = 'blocked', updated_at = NOW()
    WHERE user_id = 1;

    — 删除

    DELETE FROM users WHERE status = 'deleted';

    进阶写法(方言差异大)

    • UPSERT:插入或更新(PostgreSQL: ON CONFLICT … DO UPDATE;MySQL: INSERT … ON DUPLICATE KEY UPDATE
    • 多表更新:UPDATE … FROM … 关联其他表更新
    • MERGE:SQL 标准语法,同时支持插入/更新/删除,数仓场景常用

    第 05 章 · 子查询与 CTE(中级)

    5.1 子查询类型

    标量子查询

    返回单个值,可出现在 SELECT、WHERE 中:

    SELECT *
    FROM products
    WHERE price > (SELECT AVG(price) FROM products);

    表子查询

    返回多行多列,作为临时表放在 FROM 后或 IN 中:

    SELECT u.*
    FROM users u
    JOIN (
      SELECT user_id, SUM(amount) AS total
      FROM orders
      GROUP BY user_id
    ) o ON u.user_id = o.user_id
    WHERE o.total > 1000;

    相关子查询

    内层引用外层的列,逐行执行,性能通常较差:

    — 每个用户的最新订单金额(相关子查询写法,不推荐)
    SELECT
      u.user_id,
      (SELECT amount FROM orders o WHERE o.user_id = u.user_id ORDER BY order_date DESC LIMIT 1)
    FROM users u;

    大多数相关子查询可改写为 JOIN 或窗口函数,可读性与性能更好。

    EXISTS / NOT EXISTS(半连接)

    判断子查询是否有匹配行,性能优异,是反连接的首选写法:

    SELECT * FROM users u
    WHERE EXISTS (
      SELECT 1 FROM orders o
      WHERE o.user_id = u.user_id
        AND o.amount > 1000
    );

    5.2 CTE:公共表表达式(2026 中级标配)

    用 WITH 给中间结果命名,把复杂查询拆成可读的步骤:

    WITH orders_2026 AS (
      SELECT * FROM orders
      WHERE order_date >= '2026-01-01'
    ),
    user_revenue AS (
      SELECT user_id, SUM(amount) AS revenue
      FROM orders_2026
      GROUP BY user_id
    )
    SELECT * FROM user_revenue
    WHERE revenue > 1000
    ORDER BY revenue DESC;

    CTE 的价值

  • 可读性:按业务逻辑分层,每一步只做一件事,便于理解与维护。
  • 可复用:同一中间结果可在后续多次引用。
  • 易调试:可单独执行每一个 CTE 验证数据正确性。
  • 优化器注意事项

    • PostgreSQL 12 及以上版本:普通 CTE 可被优化器内联,和子查询性能一致。
    • 旧版本 PostgreSQL:CTE 是「优化围墙」,优化器无法下推条件,大表场景可能性能下降。
    • DuckDB、Snowflake 等分析引擎:CTE 优化成熟,放心使用。

    5.3 递归 CTE

    用于处理树状结构、层级关系、路径展开(如组织架构、分类树、BOM):

    WITH RECURSIVE employee_tree AS (
      — 锚点:根节点
      SELECT employee_id, manager_id, name, 1 AS level
      FROM employees
      WHERE manager_id IS NULL

      UNION ALL

      — 递归部分:连接上一层结果
      SELECT e.employee_id, e.manager_id, e.name, t.level + 1
      FROM employees e
      JOIN employee_tree t ON e.manager_id = t.employee_id
    )
    SELECT * FROM employee_tree;

    • 必须包含锚点 + 递归部分,用 UNION ALL 连接。
    • 注意防止死循环:部分引擎支持 CYCLE 子句检测环。

    5.4 CASE 与条件聚合

    标准 CASE 写法(全方言通用)

    SELECT
      user_id,
      SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_amount,
      SUM(CASE WHEN status = 'refunded' THEN amount ELSE 0 END) AS refund_amount,
      COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_order_cnt
    FROM orders
    GROUP BY user_id;

    FILTER 子句(更简洁,PostgreSQL/DuckDB 支持)

    SELECT
      user_id,
      SUM(amount) FILTER (WHERE status = 'paid') AS paid_amount,
      COUNT(*) FILTER (WHERE status = 'refunded') AS refund_cnt
    FROM orders
    GROUP BY user_id;

    第 06 章 · 窗口函数(中级核心)

    窗口函数是数据分析岗位的核心分界线,它在不塌缩行数的前提下,实现分组内计算。

    6.1 基本语法框架

    函数名(参数) OVER (
      PARTITION BY 分组列      — 窗口按什么分组,类似 GROUP BY 但不合并行
      ORDER BY 排序列         — 窗口内的排序,排名、累计、环比依赖它
      ROWS/RANGE 帧范围       — 窗口覆盖哪些行,累计与滑动平均关键
    )

    6.2 排名类窗口函数

    函数

    特点

    并列名次处理

    ROW_NUMBER()

    强制连续序号,并列也按顺序排 1,2,3

    不重复

    RANK()

    并列同名次,下一名跳号:1,1,3

    跳号

    DENSE_RANK()

    并列同名次,下一名不跳号:1,1,2

    不跳号

    NTILE(n)

    把数据分成 n 个桶,返回桶编号

    均分分组

    经典场景:取每个用户最新的一笔订单

    WITH ranked_orders AS (
      SELECT
        user_id, order_id, amount, order_date,
        ROW_NUMBER() OVER (
          PARTITION BY user_id
          ORDER BY order_date DESC, order_id DESC
        ) AS rn
      FROM orders
    )
    SELECT * FROM ranked_orders WHERE rn = 1;

    6.3 取值类窗口函数

    函数

    作用

    LAG(col, n)

    取当前行往前第 n 行的值(环比常用)

    LEAD(col, n)

    取当前行往后第 n 行的值

    FIRST_VALUE(col)

    取窗口内第一行的值

    LAST_VALUE(col)

    取窗口内最后一行的值(注意帧范围)

    NTH_VALUE(col, n)

    取窗口内第 n 行的值

    经典场景:日销售额环比

    SELECT
      order_date,
      revenue,
      LAG(revenue) OVER (ORDER BY order_date) AS prev_day_revenue,
      ROUND(
        (revenue – LAG(revenue) OVER (ORDER BY order_date))
        / LAG(revenue) OVER (ORDER BY order_date) * 100,
        2
      ) AS wow_rate
    FROM daily_revenue;

    6.4 累计与滑动窗口

    帧子句三种类型

  • ROWS:按物理行计数,精确到行
  • RANGE:按排序键的值范围,相同值的行视为同一组
  • GROUPS:按排序键的分组计数(SQL:2011 标准,部分引擎支持)
  • 常用帧写法

    — 累计求和:从窗口开头到当前行(默认帧,ORDER BY 后默认就是这个)
    SUM(amount) OVER (
      PARTITION BY user_id
      ORDER BY order_date
      ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total

    — 近 7 天滑动平均(物理行)
    AVG(amount) OVER (
      ORDER BY order_date
      ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) AS avg_7d

    注意:只写 ORDER BY 不写帧子句时,默认帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,排序键有重复值时,ROWS RANGE 结果不同,需特别注意。

    6.5 聚合类窗口函数

    SUM / AVG / COUNT / MIN / MAX 都可以作为窗口函数使用,实现「明细行 + 分组聚合值」同屏展示

    SELECT
      order_id, user_id, amount,
      SUM(amount) OVER (PARTITION BY user_id) AS user_total_amount,
      amount / SUM(amount) OVER (PARTITION BY user_id) AS user_amount_ratio
    FROM orders;

    6.6 窗口函数 vs GROUP BY

    维度

    GROUP BY

    窗口函数

    行数变化

    塌缩,每组一行

    不变,保留原始明细行数

    输出内容

    只有分组键与聚合值

    明细 + 窗口计算值

    典型用途

    汇总报表、指标统计

    排名、环比、累计、打标过滤

    组合使用

    可先窗口打标,再聚合

    可嵌套在 CTE 中多层计算

    6.7 高频业务题型

  • 每用户最新/最早一笔订单
  • Top N per group(每类目销量前 3)
  • 用户留存分析(首次活跃后第 N 日回访)
  • 漏斗转化计算(步骤到达率)
  • 连续登录天数(岛屿问题)
  • 同比/环比/滚动平均
  • 去重保留最新版本数据
  • 第 07 章 · 视图、事务与索引直觉(中级)

    7.1 视图(VIEW)

    视图是保存起来的查询定义,使用起来像表一样:

    CREATE VIEW v_user_monthly_revenue AS
    SELECT
      user_id,
      DATE_TRUNC('month', order_date) AS order_month,
      SUM(amount) AS revenue
    FROM orders
    GROUP BY user_id, DATE_TRUNC('month', order_date);

    作用

    • 逻辑复用:复杂查询封装,上层调用简单
    • 权限隔离:只给用户开放部分列/部分行
    • 简化上层查询:隐藏复杂的 JOIN 和聚合逻辑

    注意事项

    • 视图本身不存储数据(除物化视图),查询时执行底层 SQL,复杂视图可能性能很差。
    • 不要嵌套过深视图,多层嵌套会导致优化器失效,性能指数级下降。

    物化视图(Materialized View)

    预计算结果并落盘,查询直接读结果,换查询速度。需要定时刷新,适用于变化不频繁的汇总指标。

    • PostgreSQL 原生支持,可手动刷新
    • 数仓引擎(Snowflake、ClickHouse)有更丰富的物化视图能力

    7.2 事务与 ACID

    事务是一组 SQL 操作的集合,要么全部成功,要么全部失败。

    ACID 四大特性

    • 原子性(Atomicity):事务是不可分割的最小单元,全成功或全回滚
    • 一致性(Consistency):事务前后,数据始终保持业务规则一致
    • 隔离性(Isolation):多个事务并发执行,互相不干扰
    • 持久性(Durability):事务提交后,数据永久生效,宕机不丢失

    事务语法

    BEGIN;    — 开启事务
    UPDATE accounts SET balance = balance – 100 WHERE id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
    COMMIT;   — 提交事务
    — ROLLBACK;  — 回滚事务

    隔离级别与并发问题

    从弱到强:读未提交 → 读已提交(RC,大多数数据库默认) → 可重复读(RR,MySQL InnoDB 默认) → 可串行化。

    对应并发问题:

    • 脏读:读到其他事务未提交的数据
    • 不可重复读:同一事务内,两次读同一行结果不同
    • 幻读:同一事务内,两次范围查询行数不同

    OLTP 业务必须理解事务边界与隔离级别;纯分析批处理更关注作业幂等与分区覆盖,而非行级锁细节。

    7.3 索引直觉

    索引是「用空间换时间」的数据结构,帮助数据库快速定位行。

    最常见类型与适用场景

    索引类型

    底层结构

    适用场景

    代表引擎

    B-Tree 索引

    B+树

    等值查询、范围查询、排序、JOIN 键,最通用

    所有关系型数据库

    哈希索引

    哈希表

    仅等值查询,速度极快,不支持范围与排序

    MySQL Memory、PostgreSQL

    GIN 索引

    倒排索引

    数组、JSONB、全文搜索,多值匹配

    PostgreSQL

    GiST 索引

    通用搜索树

    地理空间、全文搜索、模糊查询

    PostgreSQL

    BRIN 索引

    块范围索引

    时序数据、有序大表,体积极小

    PostgreSQL

    位图索引

    位图

    低基数列(性别、状态),数据仓库

    Oracle、Greenplum

    索引失效常见场景

  • 对索引列使用函数/运算:WHERE DATE(created_at) = '2026-01-01'
  • 隐式类型转换:字符串列用数字比较
  • 前导通配符模糊查询:LIKE '%keyword%'
  • 使用 NOT!=<> 反向查询(数据量大时失效)
  • OR 连接的条件中有非索引列
  • 选择性极差的列(如性别)建索引收益极低
  • 复合索引与最左前缀原则

    复合索引 (a, b, c)

    • 支持查询:aa+ba+b+c
    • 不支持:bcb+c
    • 等值查询列放前面,范围查询列放后面

    第 08 章 · 查询优化与数据建模(高级)

    8.1 先正确,再快

    优化的前提是逻辑正确:粒度准确、JOIN 不膨胀、NULL 处理符合业务、边界条件完整。 错误的快查询没有任何价值,甚至会带来业务损失。

    8.2 执行计划解读

    使用 EXPLAIN ANALYZE 查看实际执行路径与耗时(PostgreSQL 示例):

    EXPLAIN ANALYZE
    SELECT user_id, SUM(amount)
    FROM orders
    WHERE order_date >= '2026-01-01'
    GROUP BY user_id;

    关注核心点

  • 访问路径:Seq Scan(全表扫描)、Index Scan(索引扫描)、Index Only Scan(索引覆盖扫描,最优)
  • JOIN 算法:
    • Nested Loop:小表驱动大表,有索引时快
    • Hash Join:大表关联,无索引时常用,OLAP 主流
    • Merge Join:两个有序数据集关联,排序后合并
  • 估算行数 vs 实际行数:偏差大说明统计信息过期,需 ANALYZE
  • 昂贵节点:Sort、HashAggregate、重复物化、大表全表扫描
  • 8.3 常见病灶与优化手段

  • **SELECT ***:只查需要的列,减少 IO 与内存占用
  • 宽松 JOIN 后再过滤:先过滤再 JOIN,缩小参与关联的数据量
  • 隐式转换:统一类型,保证索引可用
  • 相关子查询:改写为 JOIN 或窗口函数
  • 不必要的 DISTINCT:找到重复根源,从源头消除
  • 大结果排序:排序字段加索引,或减少排序数据量
  • 统计信息过期:定期 ANALYZE 表,保证优化器判断准确
  • 云数仓额外关注:扫描字节数、分区裁剪是否生效、聚类键是否合理,这些直接等于计算成本。

    8.4 OLTP 建模原则

    • 遵循三范式,减少冗余,避免更新异常
    • 明确主键、外键与约束,保证数据一致性
    • 围绕事务与点查设计,避免大表宽表
    • 适当预留扩展字段,应对业务迭代

    8.5 OLAP 数仓建模

    星型模型

    • 事实表:度量业务事件(订单、点击、支付),包含外键与数值度量,先定粒度
    • 维度表:描述实体属性(用户、商品、时间、渠道),主键关联事实表

    缓慢变化维(SCD)

    维度属性发生变化时的处理方式:

    • SCD Type 1:覆盖更新,不保留历史
    • SCD Type 2:新增行,用生效时间/版本号区分历史(最常用)
    • SCD Type 3:新增列保存上一个值

    数仓分层思想

    ODS 原始层 → DWD 明细层(清洗、脱敏、标准化) → DWS 汇总层(轻度聚合) → ADS 应用层(报表/指标)

    每一层只做一类事,SQL 逻辑清晰,血缘明确,便于维护与排查问题。

    8.6 粒度(Grain)——高级逻辑杀手

    错误示例:订单事实表粒度是「一笔订单一行」,JOIN 「订单优惠明细表」(一笔订单多个优惠)后,直接 SUM(order_amount),订单金额被放大了优惠条目数倍。

    正确对齐步骤:

  • 写明每个表的粒度
  • 关联前先把两个表聚合到相同粒度
  • 再进行 JOIN 操作
  • 验证行数:JOIN 前后行数是否符合预期
  • 第 09 章 · 现代特性与方言版图(高级)

    9.1 高级 SQL 语言特性

    多维汇总 GROUPING SETS

    一次查询生成多个维度的聚合结果,替代多次 UNION ALL:

    SELECT region, category, SUM(amount) AS total
    FROM sales
    GROUP BY GROUPING SETS (
      (region, category),  — 区域+类目
      (region),            — 区域汇总
      ()                   — 全局总计
    );

    还有 ROLLUP(层级汇总)、CUBE(全维度组合)两种简化写法。

    行模式匹配 MATCH_RECOGNIZE

    SQL:2016 引入,在有序数据上识别行为模式(如连续登录、会话切割、用户路径),部分引擎支持。

    过程化能力

    存储过程、自定义函数(UDF)方言差异极大。原则:能用集合写法解决,就不要写逐行循环的存储过程,性能与可维护性都差。

    9.2 JSON 与半结构化数据

    SQL:2016 引入原生 JSON 支持,SQL:2023 进一步强化。现实中大量事件日志、接口数据都是 JSON 格式。

    常见操作

    • 存储:文本 / 原生 JSON / 二进制 JSON(PostgreSQL JSONB,推荐)
    • 提取:按路径取值,jsonb_extract_path_text->->> 操作符
    • 展开:数组转行,jsonb_array_elementsJSON_TABLE
    • 构造:从行数据构造 JSON 对象
    • 索引:GIN 索引加速 JSON 键值查询

    9.3 复杂类型:数组 / Map / Struct

    BigQuery、Spark SQL、DuckDB 等分析引擎广泛支持嵌套类型:

    • ARRAY 数组:多值属性,如用户标签列表
    • Map 键值对:动态属性
    • Struct 结构体:复合属性,如用户地址包含省、市、区

    分析场景常需要「爆炸展开」(UNNEST)后再关联聚合。

    9.4 属性图查询 SQL/PGQ

    SQL:2023 新增 Part 16,把表数据当作属性图,用 GRAPH_TABLE + MATCH 做路径模式查询,减少超深 JOIN 的表达负担。 2026 年现状:标准已定,主流引擎落地进度不一,属于前沿了解项,团队未采用时不必深挖。

    9.5 主流引擎方言版图

    场景

    代表引擎

    复习重点

    通用 OLTP

    PostgreSQL

    JSONB、窗口函数、索引类型、事务

    Web 生态 OLTP

    MySQL

    索引、InnoDB 特性、事务隔离

    本地分析

    DuckDB

    友好语法、列式执行、文件查询

    云数仓

    Snowflake / BigQuery

    分区、聚类、扫描成本、半结构化

    实时分析

    ClickHouse / StarRocks

    列式存储、物化视图、聚合优化

    湖仓一体

    Spark SQL / Databricks

    类型系统、ANSI 模式、UDF

    分布式 HTAP

    TiDB / OceanBase

    兼容 MySQL/Oracle、分布式事务

    DuckDB 友好语法示例(了解)

    • GROUP BY ALL / ORDER BY ALL:自动按 SELECT 中非聚合列分组/排序
    • SELECT * EXCEPT (col1, col2):排除指定列
    • FROM-first 写法:先写 FROM 再写 SELECT,编辑器补全更友好
    • 跨引擎迁移时收敛到标准 SQL,避免方言依赖

    9.6 跨方言迁移检查表

  • 日期函数与时区处理差异
  • 字符串拼接、正则、截取函数差异
  • 分页语法:LIMIT vs TOP vs FETCH FIRST
  • UPSERT/MERGE 语法差异
  • 近似去重、近似分位数等高级函数
  • 保留字与标识符引用:双引号 vs 反引号
  • 空值排序默认行为、聚合对空值的处理
  • 第 10 章 · 分析工程(高级延伸)

    分析工程把「写 SQL」升级为「工程化交付数据产品」,是数据团队规模化的必经之路。

    10.1 SQL 在数据管道中的位置

    典型数据链路:

    业务源系统 → 数据同步(CDC/ETL) → 数仓 ODS → DWD/DWS 转换(SQL/dbt) → ADS 指标层 → 报表/BI/应用

    你写的 CTE 分层逻辑,落到工程里就是模型分层与依赖图。

    10.2 数据质量:用 SQL 做断言

    高质量数据是分析可信的基础,至少覆盖四类测试:

  • 唯一性:主键不重复 COUNT(*) = COUNT(DISTINCT pk)
  • 非空:关键字段没有 NULL
  • 引用完整性:事实表外键都能在维度表中找到
  • 业务规则:金额 ≥ 0、结束时间 ≥ 开始时间、状态值在枚举内
  • 波动监控:行数、指标值相对昨日/上周异常波动
  • 在 dbt 中体现为:unique、not_null、relationships、自定义 SQL 测试。

    10.3 增量、幂等与迟到数据

    概念

    含义

    增量

    每次只处理新增/变更数据,而非全量重算,节省资源

    幂等

    同一批数据跑多次,结果完全一致,可重复执行

    迟到数据

    事件发生时间晚于数据到达时间,需要回补历史窗口

    常见实现策略:

    • 按日期分区覆盖:每次重跑当天/近 N 天分区
    • Merge 按业务键更新:主键存在则更新,不存在则插入
    • 水位线(Watermark):定义迟到阈值,超过阈值不再更新

    10.4 dbt 在 2026 年的发展

    dbt 是分析工程的事实标准工具,核心是「SQL 为中心 + 版本控制 + 测试 + 文档 + 血缘」。

    2026 年核心演进:

  • dbt Core v2:运行时重写,解析速度大幅提升,支持更大规模项目
  • dbt lint:内置 SQL 静态检查,兼容 SQLFluff 规则,提交前发现问题
  • 语义层(Semantic Layer):指标定义统一,BI 工具直接调用,避免口径不一致
  • AI 开发代理:内置 dbt Developer Agent,理解项目上下文,辅助写模型与测试
  • 状态感知运行:只运行变更的模型及其下游,提升开发效率
  • 10.5 可读性与协作规范

  • 命名即文档:表名、字段名反映业务含义与粒度,如 dws_user_order_di(用户订单日汇总)
  • 分层清晰:复杂逻辑用 CTE 分段,每段只做一件事
  • 硬编码注释:魔法数字、特殊业务规则必须加注释说明原因
  • 口径沉淀:转化率、活跃用户等核心指标,口径写在文档/注释里
  • Code Review 标准:先看逻辑正确性,再看风格与性能
  • 10.6 AI 时代的 SQL 能力

    AI 可以快速生成初稿,但审计 SQL、验证结果、把控口径才是人的核心竞争力。 AI 生成 SQL 必查清单:

  • 结果粒度是否正确,行数是否符合预期
  • JOIN 是否导致行数膨胀,聚合是否被放大
  • NULL 值处理是否符合业务定义
  • 日期边界是否正确(左闭右开、时区)
  • 重复业务键是否去重
  • 分区裁剪是否生效,会不会扫全表
  • 10.7 安全与成本意识

    • 最小权限原则:账号只给必要的查询权限,禁止生产库高权限
    • 防 SQL 注入:业务代码中禁止字符串拼接 SQL,使用参数化查询
    • 数据脱敏:敏感字段(手机号、身份证、金额)分级脱敏
    • 行级安全(RLS):不同用户只能看到自己权限范围内的数据
    • 成本治理:云上数仓按扫描量计费,避免无意义的全表扫描,合理设置分区与聚类

    附录 A:SQL 避坑速查表

  • 永远不要写 = NULL,用 IS NULL
  • NOT IN 子查询注意排除 NULL,优先用 NOT EXISTS
  • LEFT JOIN 过滤条件写 ON 和 WHERE 结果不一样
  • COUNT(col) 不计 NULL,COUNT(*) 计所有行
  • 窗口函数的 ROWS 和 RANGE 在排序键重复时结果不同
  • 函数包裹索引列会导致索引失效
  • 深分页 OFFSET 性能差,用游标分页替代
  • UNION 会去重排序,不需要去重一定用 UNION ALL
  • 一对多 JOIN 后聚合要警惕金额被放大
  • 日期过滤用左闭右开,避免 DATE() 函数包裹列
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » 2026年版-SQL学习文档
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!