join结果重复是因一对多关系导致主表行被复制,属语义正常现象;应据业务需求选聚合函数、窗口函数或exists,而非盲目用distinct。

JOIN 查询结果为什么总是重复?
一对多关系下,用 INNER JOIN 或 LEFT JOIN 查主表和从表时,主表记录会按从表匹配行数重复出现——这不是 bug,是 JOIN 的语义本身决定的。比如一个订单(主)对应多个订单项(从),查出来就是“1 条订单 + 3 行订单项” → 返回 3 行,订单字段全重复。
常见错误是没意识到这点,直接在应用层去重或强行 GROUP BY 却忘了聚合从表字段,导致数据丢失或报错 ERROR 1055(MySQL 严格模式下非聚合列不能出现在 SELECT 中)。
- 确认业务是否真需要“展开式”结果:比如导出明细报表,重复是合理的;如果要“一行订单 + 所有子项汇总”,就得换思路
- 避免对主表字段盲目加
DISTINCT——它只去重整行,无法解决一对多带来的逻辑冗余 - 若必须单行展示,优先考虑用聚合函数(
GROUP_CONCAT、STRING_AGG、JSON_AGG)把从表字段合并
LEFT JOIN vs INNER JOIN:选哪个取决于“要不要空子项”
主表是“一”的那方(如 orders),从表是“多”的那方(如 order_items)。用 LEFT JOIN 能保留所有主表记录,哪怕它没有关联子项;INNER JOIN 只返回至少有一个子项的主表记录。
典型场景:查所有订单及其商品数量,但有些订单可能还没录入商品(状态为“已创建未提交”),这时必须用 LEFT JOIN,否则这些订单就查不到了。
-
LEFT JOIN order_items ON orders.id = order_items.order_id:主表全量,从表字段为NULL表示无匹配 -
INNER JOIN order_items ON orders.id = order_items.order_id:结果集天然过滤掉“无子项”的主记录 - WHERE 条件写在 JOIN 后要注意:如果加了
WHERE order_items.status = 'shipped',会把LEFT JOIN退化成INNER JOIN效果(因为NULL不满足条件),应改用ON ... AND order_items.status = 'shipped'
性能陷阱:JOIN 多张从表时笛卡尔积爆炸
当主表同时 JOIN 两个独立的从表(比如 orders JOIN order_items 再 JOIN order_logs),如果一个订单既有 5 条子项又有 3 条日志,结果就是 5 × 3 = 15 行——不是线性增长,是乘法级膨胀。
这种写法在小数据量下看不出问题,一旦订单平均子项数 >10、日志数 >5,查询就明显变慢,甚至 OOM。
- 优先拆成两个独立查询:先查主表+子项,再查主表+日志,应用层关联(适合主键可 hash 关联的场景)
- 若必须单次查出,用子查询或 CTE 预聚合从表,例如
(SELECT GROUP_CONCAT(product_name) FROM order_items WHERE order_id = o.id)替代直接 JOIN - 检查执行计划:
EXPLAIN看 rows 估算值,如果某张 JOIN 表的 rows 是“主表 × 平均子项数”的量级,基本就是笛卡尔积了
PostgreSQL 和 MySQL 的聚合写法差异
想把一对多转成一行,得靠聚合函数,但语法不同。MySQL 用 GROUP_CONCAT,PostgreSQL 用 STRING_AGG 或 JSON_AGG,SQL Server 用 STRING_AGG(2017+)或 FOR XML(旧版)。
例如合并所有商品名:
SELECT o.id, o.order_no, GROUP_CONCAT(oi.product_name SEPARATOR ', ') AS items -- MySQL -- STRING_AGG(oi.product_name, ', ') AS items -- PostgreSQL FROM orders o LEFT JOIN order_items oi ON o.id = oi.order_id GROUP BY o.id, o.order_no;
- MySQL 的
GROUP_CONCAT默认长度限制 1024,超长会被截断,需提前设SET SESSION group_concat_max_len = 1000000 - PostgreSQL 的
STRING_AGG支持ORDER BY子句(STRING_AGG(oi.product_name, ', ' ORDER BY oi.id)),MySQL 直到 8.0 才支持类似写法 - 如果要保持结构化(比如后续解析为数组),PostgreSQL 的
JSON_AGG(ROW_TO_JSON(oi))比拼字符串更可靠
实际中真正麻烦的不是语法,而是搞不清“到底要展开还是聚合”——业务方一句“把订单和明细一起查出来”,没说清是要 Excel 表格式展开,还是要 API 返回的嵌套 JSON。这个模糊点不澄清,后面无论怎么调 JOIN 都会反复返工。










