MySQL 查询语句不是靠死记语法,而是靠理解 WHERE、JOIN 和执行顺序——写错的地方,90% 出现在 ON 和 WHERE 混用、NULL 判断遗漏、或 GROUP BY 字段不全上。
SELECT 基本结构和执行顺序为什么必须搞清
很多人以为
SELECT是从左往右执行的,其实 MySQL 实际执行顺序是:
FROM→
ON→
JOIN→
WHERE→
GROUP BY→
HAVING→
SELECT→
ORDER BY→
LIMIT。这个顺序直接决定你能用什么、不能用什么。
WHERE里不能用
SELECT中定义的别名(比如
SELECT price * 1.1 AS final_price WHERE final_price > 100会报错)
ON条件只影响 JOIN 的匹配逻辑,
WHERE条件则是在 JOIN 完成后过滤整行——左连接时放错位置会导致丢失 NULL 行
GROUP BY后,
SELECT中所有非聚合字段都必须出现在
GROUP BY列表里(否则在严格模式下直接报错
ERROR 1055)
WHERE 和 HAVING 的区别到底在哪
WHERE过滤的是“行”,
HAVING过滤的是“组”。它们不是可互换的替代品,选错就查不到想要的结果。 想查“订单金额大于 500 的用户”,用
WHERE order_amount > 500想查“平均订单金额大于 500 的用户”,必须用
GROUP BY user_id+
HAVING AVG(order_amount) > 500
HAVING可以引用聚合函数(
COUNT()、
AVG()等),
WHERE不可以
HAVING性能通常更差——它是在分组后才计算,数据量已缩小但计算开销更大
LEFT JOIN 中 ON 和 WHERE 放条件的后果完全不同
这是线上最常引发数据缺失的坑。举个例子:查所有用户及其最近一笔订单,但有些用户没下单。
SELECT u.id, u.name, o.order_no FROM user u LEFT JOIN `order` o ON u.id = o.user_id AND o.status = 'paid'
上面写法能保留所有用户,未下单或订单非 paid 的用户,
o.order_no为
NULL;但如果把条件挪到
WHERE:
SELECT u.id, u.name, o.order_no FROM user u LEFT JOIN `order` o ON u.id = o.user_id WHERE o.status = 'paid'
结果只剩下了有 paid 订单的用户——
LEFT JOIN彻底退化成
INNER JOIN。
ON后的条件决定“哪些右表记录能连上来”
WHERE后的条件是对最终结果集做筛选,
NULL值在
=判断中永远为 false 要过滤右表,优先放
ON;要全局过滤(含左表字段),才用
WHERE
NULL 值处理不当会让查询结果静默出错
MySQL 中
NULL不等于任何值,包括它自己。所以
WHERE status != 'done'不会命中
status IS NULL的行,很容易漏数据。 判断空值必须用
IS NULL或
IS NOT NULL,不能用
= NULL聚合函数如
COUNT(col)会自动忽略
NULL,但
COUNT(*)统计所有行 排序时
ORDER BY col DESC默认把
NULL排在最前(升序则在最后),需要显式控制用
ORDER BY col DESC, col IS NULL连接字段含
NULL时,
ON a.id = b.id永远不匹配,建议提前用
COALESCE(a.id, 0)或补默认值
真正难的不是写出语法正确的 SELECT,而是预判数据里有没有 NULL、JOIN 后会不会产生意料外的空行、GROUP BY 是否覆盖了所有非聚合字段——这些地方一松懈,查出来的数看着对,其实已经错了。
