多表连接 #

一、JOIN概述 #

1.1 连接类型 #

text
JOIN 连接类型
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   INNER JOIN (内连接)                                       │
│   └── 只返回匹配的行                                       │
│                                                             │
│   LEFT JOIN (左连接)                                        │
│   └── 返回左表所有行,右表匹配或NULL                       │
│                                                             │
│   RIGHT JOIN (右连接)                                       │
│   └── 返回右表所有行,左表匹配或NULL                       │
│                                                             │
│   FULL JOIN (全连接)                                        │
│   └── 返回所有行,匹配或NULL                               │
│                                                             │
│   CROSS JOIN (交叉连接)                                     │
│   └── 笛卡尔积                                             │
│                                                             │
│   NATURAL JOIN (自然连接)                                   │
│   └── 自动匹配同名列                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 示例表 #

sql
-- 用户表
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100)
);

-- 订单表
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id),
    order_no VARCHAR(50),
    amount DECIMAL(10, 2),
    status VARCHAR(20),
    created_at TIMESTAMP DEFAULT NOW()
);

-- 订单项表
CREATE TABLE order_items (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    order_id UUID REFERENCES orders(id),
    product_name VARCHAR(100),
    quantity INT,
    price DECIMAL(10, 2)
);

-- 插入测试数据
INSERT INTO users (id, name, email) VALUES
    ('11111111-1111-1111-1111-111111111111', 'Alice', 'alice@example.com'),
    ('22222222-2222-2222-2222-222222222222', 'Bob', 'bob@example.com'),
    ('33333333-3333-3333-3333-333333333333', 'Carol', 'carol@example.com');

INSERT INTO orders (id, user_id, order_no, amount, status) VALUES
    ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', 'ORD001', 100.00, 'completed'),
    ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-1111-1111-1111-111111111111', 'ORD002', 200.00, 'pending'),
    ('cccccccc-cccc-cccc-cccc-cccccccccccc', '22222222-2222-2222-2222-222222222222', 'ORD003', 150.00, 'completed');

二、INNER JOIN #

2.1 基本语法 #

sql
-- INNER JOIN 只返回匹配的行
SELECT u.name, o.order_no, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- 简写 JOIN (默认是 INNER JOIN)
SELECT u.name, o.order_no, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;

-- 使用 WHERE 代替 ON
SELECT u.name, o.order_no, o.amount
FROM users u, orders o
WHERE u.id = o.user_id;

2.2 多表连接 #

sql
-- 三表连接
SELECT 
    u.name,
    o.order_no,
    oi.product_name,
    oi.quantity,
    oi.price
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN order_items oi ON o.id = oi.order_id;

-- 多表连接示例
SELECT 
    u.name AS customer,
    o.order_no,
    o.amount AS order_total,
    oi.product_name,
    oi.quantity * oi.price AS item_total
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
WHERE o.status = 'completed';

2.3 连接条件 #

sql
-- 单条件连接
SELECT u.name, o.order_no
FROM users u
JOIN orders o ON u.id = o.user_id;

-- 多条件连接
SELECT u.name, o.order_no, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id AND o.status = 'completed';

-- 使用 USING (列名相同)
SELECT u.name, o.order_no
FROM users u
JOIN orders o USING (user_id);  -- 如果两表都有 user_id 列

三、LEFT JOIN #

3.1 基本语法 #

sql
-- LEFT JOIN 返回左表所有行
SELECT u.name, o.order_no, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

-- 结果包含没有订单的用户
-- Carol 没有订单,order_no 和 amount 为 NULL

3.2 过滤NULL值 #

sql
-- 找出没有订单的用户
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;

-- 等价于 NOT EXISTS
SELECT u.name
FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o
    WHERE o.user_id = u.id
);

-- 等价于 NOT IN
SELECT u.name
FROM users u
WHERE u.id NOT IN (
    SELECT user_id FROM orders
);

3.3 条件位置 #

sql
-- 条件在 ON 中
SELECT u.name, o.order_no, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'completed';
-- 返回所有用户,但只显示 completed 订单

-- 条件在 WHERE 中
SELECT u.name, o.order_no, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.status = 'completed';
-- 只返回有 completed 订单的用户,变成 INNER JOIN

四、RIGHT JOIN #

4.1 基本语法 #

sql
-- RIGHT JOIN 返回右表所有行
SELECT u.name, o.order_no, o.amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

-- RIGHT JOIN 可以改写为 LEFT JOIN
SELECT u.name, o.order_no, o.amount
FROM orders o
LEFT JOIN users u ON u.id = o.user_id;

五、FULL JOIN #

5.1 基本语法 #

sql
-- FULL JOIN 返回所有行
SELECT u.name, o.order_no, o.amount
FROM users u
FULL JOIN orders o ON u.id = o.user_id;

-- 结果包含:
-- 1. 有用户的订单
-- 2. 没有订单的用户 (order_no, amount 为 NULL)
-- 3. 没有用户的订单 (name 为 NULL)

5.2 找出不匹配的行 #

sql
-- 找出不匹配的行
SELECT u.name, o.order_no
FROM users u
FULL JOIN orders o ON u.id = o.user_id
WHERE u.id IS NULL OR o.user_id IS NULL;

六、CROSS JOIN #

6.1 基本语法 #

sql
-- CROSS JOIN 笛卡尔积
SELECT u.name, o.order_no
FROM users u
CROSS JOIN orders o;

-- 等价于
SELECT u.name, o.order_no
FROM users u, orders o;

-- 结果行数 = users行数 × orders行数

6.2 使用场景 #

sql
-- 生成所有组合
-- 例如: 所有用户与所有产品的组合
SELECT u.name, p.product_name
FROM users u
CROSS JOIN products p;

-- 生成日期序列
SELECT 
    d.date,
    u.name
FROM (
    SELECT generate_series(
        CURRENT_DATE - INTERVAL '6 days',
        CURRENT_DATE,
        INTERVAL '1 day'
    )::date AS date
) d
CROSS JOIN users u;

七、自连接 #

7.1 基本语法 #

sql
-- 员工表
CREATE TABLE employees (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100),
    manager_id UUID REFERENCES employees(id)
);

-- 自连接查找员工和经理
SELECT 
    e.name AS employee,
    m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

-- 查找同一经理下的员工
SELECT 
    e1.name,
    e2.name AS colleague
FROM employees e1
JOIN employees e2 ON e1.manager_id = e2.manager_id
WHERE e1.id != e2.id;

八、连接性能 #

8.1 连接算法 #

text
JOIN 算法
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   Hash Join:                                                │
│   ├── 构建哈希表                                           │
│   ├── 适合大表连接                                         │
│   └── 等值连接                                             │
│                                                             │
│   Merge Join:                                               │
│   ├── 已排序数据                                           │
│   ├── 适合有序数据连接                                     │
│   └── 范围连接                                             │
│                                                             │
│   Lookup Join:                                              │
│   ├── 索引查找                                             │
│   ├── 适合小表驱动大表                                     │
│   └── 适合有索引的表                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

8.2 性能优化 #

sql
-- 查看执行计划
EXPLAIN SELECT u.name, o.order_no
FROM users u
JOIN orders o ON u.id = o.user_id;

-- 确保连接列有索引
CREATE INDEX idx_orders_user_id ON orders(user_id);

-- 小表驱动大表
-- 让小表作为驱动表
SELECT /*+ HASH_JOIN(o u) */ u.name, o.order_no
FROM orders o
JOIN users u ON u.id = o.user_id;

8.3 连接优化建议 #

text
JOIN 优化建议
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   1. 使用索引                                               │
│   ├── 连接列创建索引                                       │
│   └── 加速连接操作                                         │
│                                                             │
│   2. 减少连接表数量                                         │
│   ├── 避免过多表连接                                       │
│   └── 考虑反范式设计                                       │
│                                                             │
│   3. 选择合适的连接类型                                     │
│   ├── INNER JOIN 最快                                      │
│   └── LEFT/FULL JOIN 较慢                                  │
│                                                             │
│   4. 过滤条件放在 WHERE                                     │
│   ├── 减少连接数据量                                       │
│   └── 提高查询效率                                         │
│                                                             │
│   5. 使用覆盖索引                                           │
│   ├── 避免回表                                             │
│   └── 提高查询速度                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

九、复杂连接示例 #

9.1 多表统计 #

sql
-- 用户订单统计
SELECT 
    u.name,
    COUNT(o.id) AS order_count,
    COALESCE(SUM(o.amount), 0) AS total_amount,
    COALESCE(AVG(o.amount), 0) AS avg_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY total_amount DESC;

9.2 分层查询 #

sql
-- 订单明细统计
SELECT 
    u.name AS customer,
    o.order_no,
    COUNT(oi.id) AS item_count,
    SUM(oi.quantity * oi.price) AS calculated_total,
    o.amount AS order_amount
FROM users u
JOIN orders o ON u.id = o.user_id
LEFT JOIN order_items oi ON o.id = oi.order_id
GROUP BY u.id, u.name, o.id, o.order_no, o.amount
HAVING SUM(oi.quantity * oi.price) != o.amount;

十、总结 #

JOIN 语句要点:

类型 说明
INNER JOIN 只返回匹配行
LEFT JOIN 返回左表所有行
RIGHT JOIN 返回右表所有行
FULL JOIN 返回所有行
CROSS JOIN 笛卡尔积
自连接 表与自己连接

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

最后更新:2026-03-27