子查询 #

一、子查询概述 #

1.1 子查询类型 #

text
子查询类型
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   标量子查询 (Scalar Subquery)                              │
│   └── 返回单个值                                           │
│                                                             │
│   列子查询 (Column Subquery)                                │
│   └── 返回单列多行                                         │
│                                                             │
│   行子查询 (Row Subquery)                                   │
│   └── 返回单行多列                                         │
│                                                             │
│   表子查询 (Table Subquery)                                 │
│   └── 返回多行多列                                         │
│                                                             │
│   相关子查询 (Correlated Subquery)                          │
│   └── 引用外部查询                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 子查询位置 #

sql
-- SELECT 子句
SELECT name, (SELECT COUNT(*) FROM orders WHERE user_id = users.id)
FROM users;

-- WHERE 子句
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders);

-- FROM 子句
SELECT * FROM (SELECT * FROM users WHERE age > 25) AS u;

-- HAVING 子句
SELECT status, COUNT(*)
FROM users
GROUP BY status
HAVING COUNT(*) > (SELECT AVG(cnt) FROM (
    SELECT COUNT(*) AS cnt FROM users GROUP BY status
) t);

二、标量子查询 #

2.1 基本用法 #

sql
-- 返回单个值的子查询
SELECT 
    name,
    (SELECT COUNT(*) FROM orders WHERE user_id = users.id) AS order_count
FROM users;

-- 在 WHERE 中使用
SELECT * FROM users
WHERE age > (SELECT AVG(age) FROM users);

-- 在表达式中使用
SELECT 
    name,
    age,
    age - (SELECT AVG(age) FROM users) AS age_diff
FROM users;

2.2 注意事项 #

sql
-- 标量子查询必须只返回一个值
-- 错误示例:
SELECT * FROM users
WHERE id = (SELECT user_id FROM orders);  -- 如果返回多行会报错

-- 正确做法:
SELECT * FROM users
WHERE id = (SELECT user_id FROM orders LIMIT 1);

-- 或使用聚合函数确保返回单个值
SELECT * FROM users
WHERE id = (SELECT MAX(user_id) FROM orders);

三、列子查询 #

3.1 IN子查询 #

sql
-- IN 子查询
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders);

-- NOT IN 子查询
SELECT * FROM users
WHERE id NOT IN (SELECT user_id FROM orders);

-- 注意 NULL 值
SELECT * FROM users
WHERE id NOT IN (
    SELECT user_id FROM orders WHERE user_id IS NOT NULL
);

3.2 ANY和ALL #

sql
-- ANY: 任一满足条件
SELECT * FROM users
WHERE age > ANY (SELECT age FROM users WHERE status = 'active');
-- 等价于 age > MIN(age of active users)

-- ALL: 全部满足条件
SELECT * FROM users
WHERE age > ALL (SELECT age FROM users WHERE status = 'inactive');
-- 等价于 age > MAX(age of inactive users)

-- SOME 等同于 ANY
SELECT * FROM users
WHERE age > SOME (SELECT age FROM users WHERE status = 'active');

3.3 比较运算符 #

sql
-- = ANY 等价于 IN
SELECT * FROM users
WHERE id = ANY (SELECT user_id FROM orders);

-- != ALL 等价于 NOT IN
SELECT * FROM users
WHERE id != ALL (SELECT user_id FROM orders);

-- > ANY
SELECT * FROM products
WHERE price > ANY (SELECT price FROM products WHERE category = 'premium');

四、EXISTS子查询 #

4.1 EXISTS基本用法 #

sql
-- EXISTS: 存在满足条件的行
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o
    WHERE o.user_id = u.id
);

-- NOT EXISTS: 不存在满足条件的行
SELECT * FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o
    WHERE o.user_id = u.id
);

4.2 EXISTS vs IN #

sql
-- EXISTS 通常性能更好
-- EXISTS: 找到第一个匹配就停止
-- IN: 可能需要计算所有值

-- 使用 EXISTS
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o
    WHERE o.user_id = u.id AND o.amount > 100
);

-- 使用 IN
SELECT * FROM users
WHERE id IN (
    SELECT user_id FROM orders WHERE amount > 100
);

-- 建议:
-- - 子查询结果集大: 使用 EXISTS
-- - 主查询结果集大: 使用 IN

4.3 复杂EXISTS #

sql
-- 检查用户是否有多个订单
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o1
    WHERE o1.user_id = u.id
    AND EXISTS (
        SELECT 1 FROM orders o2
        WHERE o2.user_id = u.id
        AND o2.id != o1.id
    )
);

-- 检查用户是否有特定产品的订单
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o
    JOIN order_items oi ON o.id = oi.order_id
    WHERE o.user_id = u.id
    AND oi.product_name = 'Product A'
);

五、相关子查询 #

5.1 基本概念 #

text
相关子查询
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   特点:                                                     │
│   ├── 子查询引用外部查询的列                               │
│   ├── 对外部每一行执行一次子查询                           │
│   └── 性能开销较大                                         │
│                                                             │
│   执行过程:                                                 │
│   1. 外部查询获取一行                                      │
│   2. 将值传递给子查询                                      │
│   3. 执行子查询                                            │
│   4. 返回结果给外部查询                                    │
│   5. 重复以上步骤                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.2 相关子查询示例 #

sql
-- 查找每个用户最新的订单
SELECT u.name, o.order_no, o.created_at
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.created_at = (
    SELECT MAX(created_at)
    FROM orders
    WHERE user_id = u.id
);

-- 查找高于平均年龄的用户
SELECT * FROM users u1
WHERE age > (
    SELECT AVG(age)
    FROM users u2
    WHERE u2.status = u1.status
);

5.3 优化相关子查询 #

sql
-- 相关子查询可能很慢
-- 优化方法: 使用 JOIN 或窗口函数

-- 原始相关子查询
SELECT u.name, o.order_no, o.created_at
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.created_at = (
    SELECT MAX(created_at)
    FROM orders
    WHERE user_id = u.id
);

-- 优化: 使用窗口函数
WITH ranked_orders AS (
    SELECT 
        o.*,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
    FROM orders o
)
SELECT u.name, ro.order_no, ro.created_at
FROM users u
JOIN ranked_orders ro ON u.id = ro.user_id
WHERE ro.rn = 1;

六、派生表 #

6.1 FROM子句子查询 #

sql
-- 派生表 (FROM中的子查询)
SELECT u.name, o.order_count
FROM users u
JOIN (
    SELECT user_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY user_id
) o ON u.id = o.user_id;

-- 必须使用别名
SELECT * FROM (
    SELECT name, age FROM users
) AS u;  -- 必须有别名 AS u

6.2 复杂派生表 #

sql
-- 多层派生表
SELECT 
    summary.name,
    summary.total_orders,
    summary.total_amount
FROM (
    SELECT 
        u.name,
        COUNT(o.id) AS total_orders,
        COALESCE(SUM(o.amount), 0) AS total_amount
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    GROUP BY u.id, u.name
) summary
WHERE summary.total_orders > 0
ORDER BY summary.total_amount DESC;

七、CTE (WITH子句) #

7.1 基本CTE #

sql
-- CTE (Common Table Expression)
WITH active_users AS (
    SELECT * FROM users WHERE status = 'active'
)
SELECT * FROM active_users;

-- 多个CTE
WITH 
active_users AS (
    SELECT * FROM users WHERE status = 'active'
),
user_orders AS (
    SELECT user_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY user_id
)
SELECT 
    au.name,
    COALESCE(uo.order_count, 0) AS orders
FROM active_users au
LEFT JOIN user_orders uo ON au.id = uo.user_id;

7.2 递归CTE #

sql
-- 递归CTE: 组织结构
WITH RECURSIVE org_hierarchy AS (
    -- 基础查询: 顶级员工
    SELECT id, name, manager_id, 1 AS level
    FROM employees
    WHERE manager_id IS NULL
    
    UNION ALL
    
    -- 递归查询: 下级员工
    SELECT e.id, e.name, e.manager_id, h.level + 1
    FROM employees e
    JOIN org_hierarchy h ON e.manager_id = h.id
)
SELECT * FROM org_hierarchy ORDER BY level;

-- 递归CTE: 路径查找
WITH RECURSIVE paths AS (
    SELECT 
        id,
        name,
        name AS path
    FROM categories
    WHERE parent_id IS NULL
    
    UNION ALL
    
    SELECT 
        c.id,
        c.name,
        p.path || ' > ' || c.name
    FROM categories c
    JOIN paths p ON c.parent_id = p.id
)
SELECT * FROM paths;

7.3 CTE vs 派生表 #

text
CTE vs 派生表
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   CTE 优点:                                                 │
│   ├── 可读性好                                             │
│   ├── 可重用                                               │
│   ├── 支持递归                                             │
│   └── 逻辑清晰                                             │
│                                                             │
│   派生表优点:                                               │
│   ├── 内联更紧凑                                           │
│   └── 适合简单查询                                         │
│                                                             │
│   建议:                                                     │
│   ├── 复杂查询使用 CTE                                     │
│   ├── 简单查询使用派生表                                   │
│   └── 需要递归必须使用 CTE                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

八、子查询性能 #

8.1 性能建议 #

text
子查询性能优化
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   1. 避免相关子查询                                         │
│   ├── 使用 JOIN 代替                                       │
│   └── 使用窗口函数代替                                     │
│                                                             │
│   2. 使用 EXISTS 代替 IN                                    │
│   ├── EXISTS 找到匹配就停止                                │
│   └── 性能更好                                             │
│                                                             │
│   3. 使用 CTE 提高可读性                                    │
│   ├── 逻辑清晰                                             │
│   └── 便于优化                                             │
│                                                             │
│   4. 确保子查询有索引                                       │
│   ├── 连接列创建索引                                       │
│   └── 加速子查询                                           │
│                                                             │
│   5. 限制子查询结果集                                       │
│   ├── 使用 LIMIT                                           │
│   └── 减少数据处理量                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

8.2 查看执行计划 #

sql
-- 查看子查询执行计划
EXPLAIN SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders);

-- 分析复杂子查询
EXPLAIN ANALYZE
WITH user_stats AS (
    SELECT user_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY user_id
)
SELECT u.name, us.order_count
FROM users u
LEFT JOIN user_stats us ON u.id = us.user_id;

九、总结 #

子查询要点:

类型 说明
标量子查询 返回单个值
列子查询 返回单列多行
EXISTS 存在性检查
相关子查询 引用外部查询
派生表 FROM中的子查询
CTE WITH子句

下一步,让我们学习高级查询!

最后更新:2026-03-27