INNER JOIN、LEFT JOIN、RIGHT JOIN 完整指南
连接查询是将两个或多个表基于共同的字段关联起来,形成一个临时的结果集。连接的核心是通过连接条件来匹配不同表中的记录。
连接的工作原理:内连接返回两个表中满足连接条件的记录,是最常用的连接方式。内连接只包含匹配的记录,不匹配的记录会被过滤掉。
-- 示例1: 基本内连接
SELECT users.username, orders.order_id, orders.total_amount
FROM users
INNER JOIN orders ON users.id = orders.user_id;
-- 示例2: 使用表别名简化
SELECT u.username, o.order_id, o.total_amount, o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.total_amount > 100;
-- 示例3: 使用USING子句(当连接字段名相同时)
SELECT u.username, o.order_id, o.total_amount
FROM users u
INNER JOIN orders o USING (id);
-- 示例3: 多表连接
SELECT
u.username,
o.order_id,
p.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.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id;
左连接返回左表的所有记录,即使右表中没有匹配的记录。对于右表中没有匹配的记录,右表的字段会显示为NULL。
-- 示例4: 查询所有用户及其订单(包括没有订单的用户)
SELECT u.username, o.order_id, o.total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- 示例5: 查询没有下过订单的用户
SELECT u.username, u.email
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.order_id IS NULL;
-- 示例6: 查询用户及其最新订单
SELECT u.username, o.order_id, o.total_amount, o.order_date
FROM users u
LEFT JOIN (
SELECT user_id, MAX(order_id) AS latest_order
FROM orders
GROUP BY user_id
) lo ON u.id = lo.user_id
LEFT JOIN orders o ON lo.user_id = o.user_id AND lo.latest_order = o.order_id;
右连接返回右表的所有记录,即使左表中没有匹配的记录。对于左表中没有匹配的记录,左表的字段会显示为NULL。
-- 示例7: 查询所有订单及对应用户信息
SELECT u.username, o.order_id, o.total_amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
-- 示例8: 查询所有订单,包括没有关联用户的订单(如匿名订单)
SELECT COALESCE(u.username, '匿名用户') AS 用户名, o.order_id, o.total_amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
注意: 右连接可以通过交换表的顺序转换为左连接,因此在实际应用中左连接更为常用。
交叉连接返回两个表的笛卡尔积,即左表的每一行与右表的每一行都组合成一条记录。
-- 示例7: 笛卡尔积(慎用)
SELECT u.username, p.product_name
FROM users u
CROSS JOIN products p
LIMIT 10;
-- 示例8: 生成日期序列
SELECT DATE_ADD('2024-01-01', INTERVAL n DAY) AS date
FROM (
SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
) AS numbers
CROSS JOIN (
SELECT 0 AS m UNION ALL SELECT 7 UNION ALL SELECT 14 UNION ALL SELECT 21
) AS weeks
WHERE DATE_ADD('2024-01-01', INTERVAL (n + m) DAY) <= '2024-01-31';
注意: 交叉连接会产生大量数据,使用时务必谨慎,尤其是对大表操作。
自连接是表与自身进行连接,常用于处理层级关系、树状结构或同一表内的关联查询。
-- 示例9: 查询员工及其上级
SELECT
e1.name AS 员工姓名,
e2.name AS 上级姓名
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.employee_id;
-- 示例10: 查询同一分类下的商品对比
SELECT
p1.product_name AS 商品1,
p2.product_name AS 商品2,
p1.price AS 价格1,
p2.price AS 价格2
FROM products p1
INNER JOIN products p2 ON p1.category = p2.category
WHERE p1.product_id < p2.product_id;
-- 示例11: 查询部门同事
SELECT
e1.name AS 员工1,
e2.name AS 员工2,
e1.department
FROM employees e1
INNER JOIN employees e2 ON e1.department = e2.department
WHERE e1.employee_id < e2.employee_id;
-- 示例:使用EXPLAIN分析连接查询
EXPLAIN SELECT
u.username,
o.order_id,
o.total_amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.total_amount > 100
ORDER BY o.order_date DESC;
-- 优化示例:添加索引
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
ALTER TABLE orders ADD INDEX idx_total_amount (total_amount);
ALTER TABLE orders ADD INDEX idx_order_date (order_date);
-- 示例10: 使用子查询
SELECT username, email
FROM users
WHERE id IN (
SELECT DISTINCT user_id
FROM orders
WHERE total_amount > 1000
);
-- 等价的连接查询(性能更好)
SELECT DISTINCT u.username, u.email
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.total_amount > 1000;
-- 查询用户的订单统计
SELECT
u.username,
u.email,
COUNT(o.order_id) AS 订单数量,
COALESCE(SUM(o.total_amount), 0) AS 总消费金额,
MAX(o.order_date) AS 最后下单时间
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username, u.email
ORDER BY 总消费金额 DESC;
-- 查询商品销售明细
SELECT
p.product_name AS 商品名称,
c.category_name AS 分类,
COUNT(oi.order_item_id) AS 销售次数,
SUM(oi.quantity) AS 总销量,
SUM(oi.quantity * oi.price) AS 总销售额
FROM products p
LEFT JOIN categories c ON p.category_id = c.category_id
LEFT JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id, p.product_name, c.category_name
ORDER BY 总销售额 DESC
LIMIT 20;