一、SQL 是什么?

SQL(Structured Query Language)结构化查询语言,用于与关系型数据库交互(如 MySQL、PostgreSQL、Oracle、SQL Server 等)。

主要功能包括:

  • 查询数据(SELECT)
  • 插入数据(INSERT)
  • 更新数据(UPDATE)
  • 删除数据(DELETE)
  • 建表、建库(CREATE)
  • 权限与索引管理

二、SQL 基础语法结构

1
2
3
4
5
6
7
SELECT [字段名或表达式]
FROM [表名]
WHERE [条件]
GROUP BY [分组字段]
HAVING [分组后的过滤条件]
ORDER BY [排序字段]
LIMIT [数量限制];

三、表连接(JOIN)

JOIN 是 SQL 的核心,用来从多个表中获取相关数据。

假设我们有两张表:

users orders
id name id user_id amount

表:users

id name
1 Alice
2 Bob
3 Charlie
4 Diana

表:orders

id user_id amount
1 1 100.00
2 1 200.00
3 2 300.00
4 3 50.00
5 3 70.00

1. 内连接(INNER JOIN)

内连接(INNER JOIN)—— 取匹配的记录,只有两张表 匹配的记录 才会显示。

graph LR
    subgraph users["users 表"]
    U1["Alice (id=1)"]
    U2["Bob (id=2)"]
    U3["Charlie (id=3)"]
    U4["Diana (id=4)"]
    end

    subgraph orders["orders 表"]
    O1["order#1 (user_id=1, 100)"]
    O2["order#2 (user_id=1, 200)"]
    O3["order#3 (user_id=2, 300)"]
    O4["order#4 (user_id=3, 50)"]
    O5["order#5 (user_id=3, 70)"]
    end

    %% 匹配关系
    U1 --- O1
    U1 --- O2
    U2 --- O3
    U3 --- O4
    U3 --- O5

对应 SQL

1
2
3
SELECT users.name, orders.amount
FROM users
INNER JOIN orders ON users.id = orders.user_id;

查询结果

name amount
Alice 100.00
Alice 200.00
Bob 300.00
Charlie 50.00
Charlie 70.00

2. 左连接(LEFT JOIN)

左连接(LEFT JOIN)—— 保留左表全部,左表(users)全部保留;右表(orders)如果没有匹配,则显示 NULL。

graph LR
    subgraph users["users 表(左表)"]
    U1["Alice (id=1)"]
    U2["Bob (id=2)"]
    U3["Charlie (id=3)"]
    U4["Diana (id=4)"]
    end

    subgraph orders["orders 表(右表)"]
    O1["order#1 (user_id=1, 100)"]
    O2["order#2 (user_id=1, 200)"]
    O3["order#3 (user_id=2, 300)"]
    O4["order#4 (user_id=3, 50)"]
    O5["order#5 (user_id=3, 70)"]
    end

    %% 匹配关系
    U1 --- O1
    U1 --- O2
    U2 --- O3
    U3 --- O4
    U3 --- O5
    U4 -.-> NULL["(无匹配,右表为 NULL)"]

对应 SQL

1
2
3
SELECT users.name, orders.amount
FROM users
LEFT JOIN orders ON users.id = orders.user_id;

查询结果

name amount
Alice 100.00
Alice 200.00
Bob 300.00
Charlie 50.00
Charlie 70.00
Diana NULL

3. 右连接(RIGHT JOIN)

右连接(RIGHT JOIN)—— 保留右表全部,与 LEFT JOIN 相反,保留右表全部记录。

graph LR
    subgraph users["users 表(左表)"]
    U1["Alice (id=1)"]
    U2["Bob (id=2)"]
    U3["Charlie (id=3)"]
    U4["Diana (id=4)"]
    end

    subgraph orders["orders 表(右表)"]
    O1["order#1 (user_id=1, 100)"]
    O2["order#2 (user_id=1, 200)"]
    O3["order#3 (user_id=2, 300)"]
    O4["order#4 (user_id=3, 50)"]
    O5["order#5 (user_id=3, 70)"]
    end

    %% 匹配关系
    U1 --- O1
    U1 --- O2
    U2 --- O3
    U3 --- O4
    U3 --- O5
    %% Diana 没订单,不参与连接

对应 SQL

1
2
3
SELECT users.name, orders.amount
FROM users
RIGHT JOIN orders ON users.id = orders.user_id;

查询结果

name amount
Alice 100.00
Alice 200.00
Bob 300.00
Charlie 50.00
Charlie 70.00

4. 全连接(FULL JOIN)

全连接(FULL JOIN)—— 两边都保留(MySQL 不支持,可用 UNION 模拟)

graph LR
    subgraph users["users 表(左表)"]
    U1["Alice (id=1)"]
    U2["Bob (id=2)"]
    U3["Charlie (id=3)"]
    U4["Diana (id=4)"]
    end

    subgraph orders["orders 表(右表)"]
    O1["order#1 (user_id=1, 100)"]
    O2["order#2 (user_id=1, 200)"]
    O3["order#3 (user_id=2, 300)"]
    O4["order#4 (user_id=3, 50)"]
    O5["order#5 (user_id=3, 70)"]
    end

    %% 匹配关系
    U1 --- O1
    U1 --- O2
    U2 --- O3
    U3 --- O4
    U3 --- O5
    U4 -.-> NULL右["(无匹配,右表为 NULL)"]

对应 SQL(MySQL 模拟实现)

1
2
3
4
5
6
7
SELECT users.name, orders.amount
FROM users
LEFT JOIN orders ON users.id = orders.user_id
UNION
SELECT users.name, orders.amount
FROM users
RIGHT JOIN orders ON users.id = orders.user_id;

查询结果

name amount
Alice 100.00
Alice 200.00
Bob 300.00
Charlie 50.00
Charlie 70.00
Diana NULL

5. 交叉连接(CROSS JOIN)

交叉连接(CROSS JOIN)—— 笛卡尔积(两表全组合),CROSS JOIN 不需要任何连接条件,它会把左表的每一行与右表的每一行 两两组合,一般不常用,会生成所有组合(性能差)。

graph LR
    subgraph users["users 表(左表)"]
    U1["Alice (id=1)"]
    U2["Bob (id=2)"]
    U3["Charlie (id=3)"]
    U4["Diana (id=4)"]
    end

    subgraph orders["orders 表(右表)"]
    O1["order#1 (100)"]
    O2["order#2 (200)"]
    O3["order#3 (300)"]
    O4["order#4 (50)"]
    O5["order#5 (70)"]
    end

    %% 每个用户都和所有订单组合(笛卡尔积)
    U1 --- O1
    U1 --- O2
    U1 --- O3
    U1 --- O4
    U1 --- O5

    U2 --- O1
    U2 --- O2
    U2 --- O3
    U2 --- O4
    U2 --- O5

    U3 --- O1
    U3 --- O2
    U3 --- O3
    U3 --- O4
    U3 --- O5

    U4 --- O1
    U4 --- O2
    U4 --- O3
    U4 --- O4
    U4 --- O5

对应 SQL

1
2
3
SELECT users.name, orders.amount
FROM users
CROSS JOIN orders;

查询结果(部分示例)

name amount
Alice 100.00
Alice 200.00
Alice 300.00
Alice 50.00
Alice 70.00
Bob 100.00
Bob 200.00
Bob 300.00
Bob 50.00
Bob 70.00
Charlie 100.00
Diana 70.00

四、分组与聚合

1. GROUP BY + 聚合函数

聚合函数用来对数据集计算汇总结果:

函数 含义
COUNT() 计数
SUM() 求和
AVG() 平均值
MAX() 最大值
MIN() 最小值

示例:每个用户的订单总额是多少?

1
2
3
4
SELECT users.name, SUM(orders.amount) AS total_amount
FROM users
JOIN orders ON users.id = orders.user_id
GROUP BY users.name;

执行逻辑图

graph TD
    A["users 表"] --> B["JOIN orders ON users.id = orders.user_id"]
    B --> C["分组 GROUP BY users.name"]
    C --> D["聚合 SUM(orders.amount)"]
    D --> E["输出 name, total_amount"]

思路拆解

  1. JOIN:先把 usersorders 关联;
  2. GROUP BY:按 users.name 分组;
  3. SUM:对每个分组的 amount 求和;
  4. SELECT:只输出每个用户的名字和总金额。

查询结果

name total_amount
Alice 300.00
Bob 300.00
Charlie 120.00

Diana 没有订单,因此没有出现在结果中(因为 INNER JOIN)。

2. 使用 HAVING 过滤聚合结果

示例:只显示订单总额大于 1000 的用户(这时必须用 HAVING,因为 WHERE 不能过滤聚合结果)。

1
2
3
4
5
SELECT users.name, SUM(orders.amount) AS total_amount
FROM users
JOIN orders ON users.id = orders.user_id
GROUP BY users.name
HAVING SUM(orders.amount) > 1000;

HAVING 执行逻辑图

graph TD
    A["JOIN users + orders"] --> B["GROUP BY users.name"]
    B --> C["计算 SUM(orders.amount)"]
    C --> D["HAVING SUM(orders.amount) > 1000"]
    D --> E["输出结果"]

查询结果

name total_amount
Alice 300.00
Bob 300.00

3. WHERE 与 HAVING 的区别

对比项 WHERE HAVING
作用阶段 分组前过滤行 分组后过滤分组
可用列 原始列 原始列 + 聚合函数
示例 WHERE amount > 100 HAVING SUM(amount) > 500

4. GROUP BY 与 HAVING 区别

GROUP BY HAVING
作用阶段 对数据进行分组 分组后的结果过滤
能否使用聚合函数 不可以(只能原始列) 可以(原始列+聚合函数)
执行顺序 在 SELECT 之前 在 SELECT 之后

五、常见子查询与嵌套查询

1. 子查询

子查询:指一个查询语句嵌套在另一个查询语句内部。 它通常出现在:

  • WHERE 子句中
  • FROM 子句中
  • SELECT 子句中

子查询返回的结果,可以是一条记录、一个值、一个表,或一个布尔判断。

示例:查询下过大额订单的用户

1
2
3
4
5
6
7
SELECT name
FROM users
WHERE id IN (
    SELECT user_id
    FROM orders
    WHERE amount > 500
);

2. 子查询的常见形式

子查询的常见形式(5种)

类型 写法位置 说明 示例
① 标量子查询(Scalar Subquery) SELECT 子句中 返回单个值 SELECT name, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id)
② 列子查询(Column Subquery) WHERE / HAVING 返回一列,用于 IN WHERE user_id IN (SELECT id FROM users)
③ 行子查询(Row Subquery) WHERE 返回多列 WHERE (id, name) IN (SELECT id, name FROM users)
④ 表子查询(Table Subquery) FROM 子句中 返回一个临时表 FROM (SELECT user_id, SUM(amount) total FROM orders GROUP BY user_id)
⑤ 相关子查询(Correlated Subquery) WHERE 子句中,引用外层表 外层行依赖子查询 WHERE amount > (SELECT AVG(amount) FROM orders WHERE orders.user_id = users.id)

3. 常见关键字与区别

INEXISTSANYALL

关键字 用法 返回结果 适合场景
IN 判断是否在一个结果集合中 值集合匹配 小结果集(效率高)
EXISTS 判断子查询是否返回至少一行 布尔(TRUE/FALSE) 大结果集(效率更优)
ANY 与比较符连用(> ANY / < ANY 与任意值比较 类似最小/最大值匹配
ALL 与比较符连用(> ALL / < ALL 必须满足所有值 比较严格条件

1. IN 示例

查询有下单记录的用户:

1
2
3
SELECT name
FROM users
WHERE id IN (SELECT user_id FROM orders);

查询结果:

name
Alice
Bob
Charlie

(因为 Diana 没订单)

2. EXISTS 示例

与上面功能相同,但用 EXISTS

1
2
3
4
5
6
7
SELECT name
FROM users u
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.user_id = u.id
);

结果相同,但执行逻辑不同:

这段 SQL 想干嘛?查询所有有下过订单的用户。

也就是说:

  • users 表取出每个用户;
  • 判断:这个用户的 id 是否出现在 orders.user_id 中;
  • 如果是 → 返回该用户;
  • 如果不是 → 不返回。

EXISTS 是一个逻辑判断(布尔测试), 它不看子查询返回的值,而只关心:

子查询是否返回至少一行数据

  • 如果子查询返回了 ≥1 行 → EXISTS 结果为 TRUE
  • 如果子查询返回 0 行 → EXISTS 结果为 FALSE

为什么是 SELECT 1

因为 EXISTS 只关心“有没有行”,根本不看列内容。 所以写成 SELECT 1SELECT *SELECT o.id 都可以,效果完全一样。

只不过:

  • SELECT 1 是最简洁、最推荐的写法(标准写法)
  • SELECT * 虽然也行,但数据库会多做点解析工作(不推荐)

逻辑执行过程

当前用户 内层查询 是否存在行? 是否返回
Alice (id=1) SELECT 1 FROM orders WHERE user_id=1 → 有2行 TRUE 返回
Bob (id=2) → 有1行 TRUE 返回
Charlie (id=3) → 有2行 TRUE 返回
Diana (id=4) → 没有行 FALSE 不返回

查询结果:

name
Alice
Bob
Charlie

IN 的对比

对比项 IN EXISTS
含义 值是否在集合中 是否存在满足条件的记录
执行方式 子查询先执行一次,生成结果集 外层每行执行一次子查询
性能建议 小结果集 → 用 IN 大结果集 → 用 EXISTS
是否依赖外层表 是(相关子查询)

可视化理解

graph TD
    U["users 表 u"] -->|逐行| Q["子查询 SELECT 1 FROM orders o WHERE o.user_id = u.id"]
    Q -->|存在匹配记录?| E{"EXISTS 结果?"}
    E -->|TRUE| R["返回该用户"]
    E -->|FALSE| X["跳过"]

3. > ANY

查询至少有一笔订单金额大于 Alice 的任意一笔订单金额(即大于最小订单)的用户:

1
2
3
4
5
6
7
SELECT name
FROM users
WHERE id IN (
    SELECT user_id
    FROM orders
    WHERE amount > ANY (SELECT amount FROM orders WHERE user_id = 1)
);

ANY 类似于 “至少比其中一个值大”,ANY (100, 200)。

4. > ALL

查询金额大于 所有 Alice 的订单金额 的用户:

1
2
3
4
5
6
7
SELECT name
FROM users
WHERE id IN (
    SELECT user_id
    FROM orders
    WHERE amount > ALL (SELECT amount FROM orders WHERE user_id = 1)
);

ALL 类似于 “比所有值都大”。

4. 相关子查询

1. 相关子查询

子查询依赖外层表的字段,每次外层表扫描一行,都会重新执行子查询。

例:查询每个用户大于自己平均订单金额的订单。

1
2
3
4
5
6
7
SELECT o.*
FROM orders o
WHERE o.amount > (
    SELECT AVG(o2.amount)
    FROM orders o2
    WHERE o2.user_id = o.user_id
);
  • 外层表:orders o
  • 内层子查询:SELECT AVG(o2.amount) FROM orders o2 WHERE o2.user_id = o.user_id

内层子查询依赖 外层表 o 的 user_id,这就是“相关子查询”的核心。

逐行执行逻辑

我们先看原始数据:

id user_id amount
1 1 100
2 1 200
3 2 300
4 3 50
5 3 70

外层循环每行

外层表 orders o 每一行都会检查 amount 是否大于该用户的平均订单金额

内层子查询计算每行的平均值

外层行 (o.id) user_id amount 内层子查询 AVG(o2.amount) 比较 o.amount > AVG
1 1 100 AVG(100,200) = 150 100 > 150 → 不满足
2 1 200 AVG(100,200) = 150 200 > 150 → 满足
3 2 300 AVG(300) = 300 300 > 300 → 不满足
4 3 50 AVG(50,70) = 60 50 > 60 → 不满足
5 3 70 AVG(50,70) = 60 70 > 60 → 满足

输出结果

id user_id amount
2 1 200
5 3 70

只有大于自己平均订单金额的订单会被选出来。

2. 普通子查询 vs 相关子查询的执行顺序

普通子查询(不依赖外层表)

  • 执行顺序

    1. 先执行子查询,计算 AVG(amount) → 得到单一值
    2. 外层查询使用这个值去筛选每行数据

    执行顺序:子查询 → 外层查询

相关子查询(依赖外层表字段)

  • 执行顺序

    1. 外层表逐行扫描
    2. 对当前外层行,执行子查询 → 计算当前用户的 AVG
    3. 用子查询结果判断当前行是否满足条件
    4. 扫描下一行,重复步骤 2-3

    执行顺序:外层查询行扫描 → 对每行执行子查询

类型 子查询是否依赖外层 执行顺序
普通子查询 子查询先 → 外层查询使用结果
相关子查询 外层查询逐行 → 对每行执行子查询

5. 总结对比表

类型 出现位置 返回结果 特点
标量子查询 SELECT 单个值 常用于列计算
列子查询 WHERE / HAVING 一列 搭配 IN
行子查询 WHERE 多列 搭配 (a,b) IN (...)
表子查询 FROM 多行多列 生成中间表
相关子查询 WHERE 动态多次执行 性能差但灵活
关键字 含义 匹配规则
IN 值是否在集合中 完全匹配
EXISTS 是否存在满足条件的行 布尔判断
ANY 比较符 + 任意一个成立 只要部分满足
ALL 比较符 + 全部成立 必须全部满足

六、SQL 执行顺序

这条执行路径是 SQL 的“灵魂线”,理解执行顺序可以让你写更复杂的查询:

1
FROM  JOIN  WHERE  GROUP BY  SUM/COUNT/AVG...  HAVING  SELECT  ORDER BY  LIMIT

七、总结

程序员职业的基础技能,必知必会。