如何在SQL中无冗余连接存在一对多关系的多张表?
解决SQL多表连接冗余行并紧凑对应展示的方案
这问题我处理类似场景时也碰到过——直接基于Day_KEY做多表连接,因为EstHours和ActHours对同一个日期有多条记录,会产生笛卡尔积式的冗余行,确实闹心。结合你试过的方案,给你一个能保留所有行且实现同行对应展示的解决思路:
核心思路
给EstHours和ActHours按Day_KEY分组,分别添加行号标识,然后通过Day_KEY+行号做FULL OUTER JOIN,这样就能把同日期下的预估工时和实际工时一一对应,同时不会丢失任意一边的多余行。
先明确你的示例表结构(整理成更清晰的表格)
Day表
| Day_KEY | Day_Label |
|---|---|
| 1 | Mon |
| 2 | Tues |
| 3 | Wed |
| 4 | Thur |
EstHours表
| EstHours_KEY | Day_KEY | Est_Hours |
|---|---|---|
| 1 | 1 | 2 |
| 2 | 1 | 1 |
| 3 | 1 | 3 |
ActHours表
| ActHours_KEY | Day_KEY | Act_Hours |
|---|---|---|
| 1 | 1 | 3 |
| 2 | 1 | 2 |
| 3 | 1 | 2 |
具体SQL实现
SELECT d.Day_KEY, d.Day_Label, est.Est_Hours, act.Act_Hours FROM Day d LEFT JOIN ( -- 给每个日期的预估工时添加行号 SELECT Day_KEY, Est_Hours, ROW_NUMBER() OVER (PARTITION BY Day_KEY ORDER BY EstHours_KEY) AS rn FROM EstHours ) est ON d.Day_KEY = est.Day_KEY -- 通过Day_KEY+行号做全外连接,保留两边所有行 FULL OUTER JOIN ( -- 给每个日期的实际工时添加行号 SELECT Day_KEY, Act_Hours, ROW_NUMBER() OVER (PARTITION BY Day_KEY ORDER BY ActHours_KEY) AS rn FROM ActHours ) act ON d.Day_KEY = act.Day_KEY AND est.rn = act.rn -- 确保只保留Day表中存在的日期(如果不需要可以去掉) WHERE d.Day_KEY IS NOT NULL -- 按日期+行号排序,保证结果有序 ORDER BY d.Day_KEY, COALESCE(est.rn, act.rn);
为什么这个方案可行?
- 行号的作用:通过
ROW_NUMBER() OVER (PARTITION BY Day_KEY ORDER BY ...)给每个日期下的多条记录赋予唯一的顺序编号,让原本无序的多对多关系变成“一对一”的对应关系。 - FULL OUTER JOIN的优势:解决了你之前用行号关联时丢失行的问题——不管
EstHours还是ActHours的记录更多,多出的行都会被保留,对应另一边的字段会显示NULL。 - 同行展示:相比
UNION的上下合并,这个方案直接把预估和实际工时放在同一行,完全符合你想要的紧凑对应效果。
小提示
- 行号排序的依据(
ORDER BY EstHours_KEY/ORDER BY ActHours_KEY)可以根据你的业务需求调整,比如按记录创建时间、工时大小等,保证行的对应顺序符合预期。 - 如果你的数据库不支持
FULL OUTER JOIN(比如MySQL),可以用LEFT JOIN+UNION ALL的方式模拟,核心逻辑还是基于行号的关联。
内容的提问来源于stack exchange,提问作者l3ai
相关产品推荐
相关产品推荐

