正确做法是先用子查询按msg_id分组求最小display,再left join该结果,最后关联原子表精确匹配:select a.*, b.title, b.url, b.display from operational_message_config a left join (select msg_id, min(display) as min_display from operational_message_body group by msg_id) c on a.id = c.msg_id left join operational_message_body b on b.msg_id = a.id and b.display = c.min_display。

LEFT JOIN 时子表多条匹配,怎么只留 display 最小的那条?
直接用 LEFT JOIN + 子查询聚合是最稳的路,尤其当子表字段有业务排序逻辑(比如 display 越小越靠前)时。别用 INNER JOIN 套 GROUP BY,否则主表没对应子记录就会丢行。
正确写法是分两步:先用子查询按 msg_id 分组求出每个主键对应的最小 display,再用这个结果跟主表 LEFT JOIN,最后再连回原子表做精确匹配:
SELECT a.*, b.title, b.url, b.display FROM operational_message_config a LEFT JOIN ( SELECT msg_id, MIN(display) AS min_display FROM operational_message_body GROUP BY msg_id ) c ON a.id = c.msg_id LEFT JOIN operational_message_body b ON b.msg_id = a.id AND b.display = c.min_display;
-
LEFT JOIN第一层必须用子查询,不能直接JOIN原子表,否则会爆炸式膨胀 - 子查询里
GROUP BY的字段必须和外层ON条件完全一致,否则关联失效 - 第二层
LEFT JOIN的ON条件要同时满足msg_id和display,缺一不可
MySQL 5.7 不支持 ROW_NUMBER(),还能不能按时间取最新一条?
不能用窗口函数就老实用子查询 + ORDER BY ... LIMIT 1,但注意:这个写法只能在子查询里用,不能直接塞进 ON 条件里——MySQL 不允许 LIMIT 出现在被驱动表的 JOIN 条件中。
稳妥做法是把子表最新记录抽成独立子查询,再 LEFT JOIN:
SELECT u.*, o.order_date, o.product
FROM users u
LEFT JOIN (
SELECT user_id, order_date, product
FROM orders o1
WHERE order_date = (
SELECT MAX(order_date)
FROM orders o2
WHERE o2.user_id = o1.user_id
)
LIMIT 1
) o ON u.id = o.user_id;
-
MAX(order_date)要配合WHERE子查询,避免LIMIT在 JOIN 中非法使用 - 如果
order_date可能重复,得加额外唯一字段(比如id)做二次筛选,否则可能随机返回某条 - 这个写法在数据量大时性能较差,建议给
(user_id, order_date)加联合索引
为什么用 GROUP BY + MAX() 直接选字段会出错?
因为 MySQL 5.7 默认开启 ONLY_FULL_GROUP_BY,SELECT 列表里只要有一个非分组字段没套聚合函数,就会报错:Expression #3 of SELECT list is not in GROUP BY clause。
常见翻车写法:
SELECT a.id, a.name, b.product, MAX(b.order_date) FROM users a LEFT JOIN orders b ON a.id = b.user_id GROUP BY a.id, a.name;
这段看着像取最新订单,实际 b.product 是随机值——MySQL 允许它编译通过,但结果不可控。
- 要么把所有非分组字段都包进聚合函数,比如
MAX(b.product)(但语义已变) - 要么关掉
ONLY_FULL_GROUP_BY(不推荐,掩盖问题) - 要么改用前面提到的子查询或关联子查询方式,确保字段来源明确
MyBatis 里一对多查一条,resultMap 怎么写不踩坑?
MyBatis 的 <resultmap></resultmap> 不会自动帮你“去重”或“截断”,它只是把 SQL 结果集按列名映射到对象属性。如果你的 SQL 返回了 5 条相同用户 + 不同订单的记录,List<order></order> 就真会塞进去 5 条。
所以关键不在 XML 配置,而在 SQL 本身必须只返回一条子记录:
- SQL 里不能写
LEFT JOIN orders,得换成上面任一单条取法(子查询 / 关联子查询) -
<resultmap></resultmap>中ordersOfUsers字段对应的列名,必须和 SQL 里实际 select 的字段名完全一致(比如o_title、o_url),否则映射为空 - 如果子表字段和主表重名(比如都有
id),必须用别名区分,否则 MyBatis 会覆盖或映射错
真正容易被忽略的是:业务上“取一条”是语义需求,不是 ORM 功能,得由 SQL 层保证结果集结构干净。否则光调 limit 1 或靠 Java 层 dedup,既慢又不可靠。











