致敬读者
博主相关
文章前言
MySQL 中复杂 SQL 的核心部分:多表联查和子查询。这是数据库操作中处理关联数据的强大工具。
核心目标: 从多个相互关联的表中组合和提取所需的数据。
当你的数据模型设计良好(遵循规范化原则)时,数据会分散在多个表中,通过主键-外键关系连接。JOIN 操作就是用来基于这些关系将多个表中的行组合起来。
SELECT 列名列表
FROM 表1
[INNER] JOIN 表2 ON 表1.关联字段 = 表2.关联字段
[WHERE 条件];
-- INNER 关键字通常可省略
customers
表有 customer_id
,orders
表有 customer_id
外键)SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
-- 结果只包含那些在customers表中有记录且在orders表中至少有一个订单的客户。
NULL
。SELECT 列名列表
FROM 表1
LEFT [OUTER] JOIN 表2 ON 表1.关联字段 = 表2.关联字段
[WHERE 条件];
-- OUTER 关键字通常可省略
SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- 结果包含所有客户。对于没有订单的客户,o.order_id 和 o.order_date 会是 NULL。
LEFT JOIN
相反。返回右表 (表2) 的所有行,即使在左表 (表1) 中没有匹配的行。对于右表中存在而左表中没有匹配的行,左表相关的列将显示为 NULL
。SELECT 列名列表
FROM 表1
RIGHT [OUTER] JOIN 表2 ON 表1.关联字段 = 表2.关联字段
[WHERE 条件];
-- OUTER 关键字通常可省略
SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id;
-- 结果包含所有订单。如果某个订单的 customer_id 在 customers 表中找不到,则 c.customer_id 和 c.name 会是 NULL。
RIGHT JOIN
在实际应用中不如 LEFT JOIN
常见,因为通常可以通过调整表顺序使用 LEFT JOIN
达到相同目的。NULL
。如果两个表中有匹配的行,则进行连接。SELECT 列名列表
FROM 表1
LEFT JOIN 表2 ON 表1.关联字段 = 表2.关联字段
UNION [ALL] -- 通常用 UNION 去重,如果确定不会有重复或需要保留重复则用 UNION ALL
SELECT 列名列表
FROM 表1
RIGHT JOIN 表2 ON 表1.关联字段 = 表2.关联字段
WHERE 表1.关联字段 IS NULL; -- 排除掉左连接中已包含的匹配行
SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
UNION
SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL; -- 只取右连接中左表为NULL的部分(即orders有而customers没有的行)
表1行数 * 表2行数
。通常不是你想要的结果,除非明确需要所有组合。SELECT 列名列表
FROM 表1
CROSS JOIN 表2;
-- 或者使用隐式连接(不推荐):
SELECT 列名列表
FROM 表1, 表2;
SELECT p.product_name, s.size_name
FROM products p
CROSS JOIN sizes s;
JOIN
子句连接多个表。SELECT ...
FROM 表1
JOIN 表2 ON 条件
JOIN 表3 ON 条件 -- 条件可以是表2和表3的关系,或者表1和表3的关系(较少见)
...
[WHERE ...];
SELECT c.name, o.order_date, p.product_name, od.quantity
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id;
SELECT e1.employee_name AS Employee, e2.employee_name AS Manager
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.employee_id;
-- 使用 LEFT JOIN 是因为顶级经理没有上级(manager_id 为 NULL)
SELECT ... FROM table1 NATURAL JOIN table2; -- 避免使用
USING
简化 ON
。SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
JOIN orders o USING (customer_id); -- 等价于 ON c.customer_id = o.customer_id
子查询是指嵌套在另一个 SQL 查询(主查询)内部的查询。子查询的结果被外部查询使用。
SELECT
子句(标量子查询)FROM
子句(派生表/内联视图)WHERE
子句(最常用)HAVING
子句INSERT
/ UPDATE
/ DELETE
语句的 VALUES
或 SET
部分SELECT
列表、WHERE
条件中的比较运算符右侧)。SELECT product_name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);
SELECT
列表中使用(为每行计算一个相关值)SELECT order_id, order_date,
(SELECT COUNT(*) FROM order_details od WHERE od.order_id = o.order_id) AS item_count
FROM orders o;
IN
, ANY
/SOME
, ALL
运算符一起用在 WHERE
或 HAVING
子句中。SELECT customer_id, name
FROM customers
WHERE customer_id IN (
SELECT DISTINCT o.customer_id
FROM orders o
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id
WHERE p.product_name = 'Coffee'
);
> ANY
等价于 > (SELECT MIN(price) FROM ... WHERE category='Electronics')
)SELECT product_name, price
FROM products
WHERE category <> 'Electronics'
AND price > ANY (
SELECT price
FROM products
WHERE category = 'Electronics'
);
> ALL
等价于 > (SELECT MAX(price) FROM ... WHERE category='Electronics')
)SELECT product_name, price
FROM products
WHERE category <> 'Electronics'
AND price > ALL (
SELECT price
FROM products
WHERE category = 'Electronics'
);
SELECT employee_id, name, department, job_level
FROM employees
WHERE (department, job_level) = (
SELECT department, job_level
FROM employees
WHERE employee_id = 123
)
AND employee_id <> 123; -- 排除自己
FROM
子句中,并且必须有别名。SELECT p.product_id, p.product_name, p.category, p.price, cat_avg.avg_price
FROM products p
JOIN (
SELECT category, AVG(price) AS avg_price
FROM products
GROUP BY category
) cat_avg ON p.category = cat_avg.category
WHERE p.price > cat_avg.avg_price;
WHERE
中使用相关子查询)SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id -- 关联条件
GROUP BY o.customer_id
HAVING SUM(o.total_amount) > 1000
);
-- 或者更高效的方式可能是使用 JOIN + GROUP BY + HAVING
SELECT
列表中使用相关子查询 (如之前的 item_count
例子)EXISTS (subquery)
: 如果子查询返回至少一行,则结果为 TRUE
。NOT EXISTS (subquery)
: 如果子查询返回零行,则结果为 TRUE
。EXISTS
就立即返回 TRUE
,不需要处理所有结果。IN
示例,但可能更高效)SELECT customer_id, name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id -- 关联条件
);
SELECT customer_id, name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id -- 关联条件
);
ON
或 USING
)。避免隐式连接(逗号分隔表名)和 NATURAL JOIN
,它们容易出错且不清晰。FROM table AS alias
或 FROM table alias
)。这能极大提高可读性和避免列名歧义。ON
子句中的列) 和 WHERE
子句中频繁过滤的列上有索引。INNER JOIN
通常比 OUTER JOIN
快。JOIN
。IN
子查询可能效率低下,考虑用 JOIN
或 EXISTS
替代。FROM
中的子查询)可能会阻止某些优化。有时可以用 WITH
(Common Table Expression - CTE) 在 MySQL 8.0+ 中更清晰地表达。ON
) 或 WHERE
子句中使用涉及可能为 NULL
的列进行比较时(如 col1 = col2
),如果 col1
或 col2
为 NULL
,该行通常不会匹配(因为 NULL = NULL
是 UNKNOWN
/NULL
)。如果需要匹配 NULL
,需使用 IS NULL
显式处理。WITH
子句,MySQL 8.0+) 或临时视图(如果支持)将查询步骤模块化。JOIN
或子查询。使用 LIMIT
测试大数据集查询的性能。JOIN
更自然。EXISTS
/NOT EXISTS
)可能更直观或更高效。EXPLAIN
) 是确定哪种方式性能更好的最终手段。掌握多表联查 (INNER JOIN
, LEFT JOIN
, RIGHT JOIN
, FULL JOIN
) 和子查询(标量、列、行、表子查询、相关/非相关、EXISTS
/NOT EXISTS
)是进行复杂数据库查询的基础。理解它们的工作原理、适用场景以及性能影响至关重要。通过实践、关注索引、编写清晰的 SQL 并利用 EXPLAIN
分析,你将能够高效地从关联的数据库表中提取所需的信息。记住,清晰性和性能往往是相辅相成的。
文末寄语