如何构建含多映射表的SQL查询?多表关联结果重复问题求助
我有一个Person主表,以及Location、Nationality两个关联表,每个关联关系对应一张映射表(Person_Location、Person_Nationality),已对场景进行简化处理。需求是查询每个人的信息,返回每行包含Person名称、Location名称、Nationality名称;若关联表无数据,需返回该行并以空值填充缺失字段。当前使用INNER JOIN关联Person、Person_Location、Location时正常,但加入Person_Nationality和Nationality的关联后,返回行数远超预期,尝试重复关联逻辑未解决,寻求正确实现方法。现有SQL代码如下:
SELECT p.name, lo.name as location, na.name as nationality FROM person p INNER JOIN person_location pl ON p.id = pl.person_id INNER JOIN location lo ON pl.location_id = lo.id INNER JOIN person_nationality pn ON p.id = pn.person_id INNER JOIN nationality na ON pm.nationality_id = na.id
行数暴增的核心是多对多关联产生了笛卡尔积:当一个人同时关联多个Location和多个Nationality时,INNER JOIN会把这两组数据做全组合匹配,比如1个人有2个地点、3个国籍,会返回2×3=6行,远超出预期。另外,INNER JOIN会过滤掉没有关联地点或国籍的Person记录,不符合“缺失字段用空值填充”的需求。
用LEFT JOIN保留所有Person记录,同时根据需求选择是否允许笛卡尔积:
场景1:保留地点与国籍的所有组合(补全空值)
如果需要展示所有地点和国籍的组合,同时保留无关联数据的Person,将所有INNER JOIN替换为LEFT JOIN,修正原代码中的笔误(pm改为pn):
SELECT p.name, COALESCE(lo.name, '') AS location, COALESCE(na.name, '') AS nationality FROM person p LEFT JOIN person_location pl ON p.id = pl.person_id LEFT JOIN location lo ON pl.location_id = lo.id LEFT JOIN person_nationality pn ON p.id = pn.person_id LEFT JOIN nationality na ON pn.nationality_id = na.id
注:
COALESCE用于将空值替换为指定内容(比如空字符串),可根据需求调整。
场景2:避免笛卡尔积,地点/国籍独立展示
如果不希望出现地点与国籍的组合行,让每个地点或国籍单独对应一行(无关联字段为空),用UNION ALL合并结果:
-- 关联地点的记录 SELECT p.name, lo.name AS location, '' AS nationality FROM person p LEFT JOIN person_location pl ON p.id = pl.person_id LEFT JOIN location lo ON pl.location_id = lo.id UNION ALL -- 关联国籍的记录(排除已在上面出现的重复Person行) SELECT p.name, '' AS location, na.name AS nationality FROM person p LEFT JOIN person_nationality pn ON p.id = pn.person_id LEFT JOIN nationality na ON pn.nationality_id = na.id WHERE NOT EXISTS ( SELECT 1 FROM person_location pl WHERE pl.person_id = p.id ) UNION ALL -- 无地点也无国籍的Person记录 SELECT p.name, '' AS location, '' AS nationality FROM person p WHERE NOT EXISTS ( SELECT 1 FROM person_location pl WHERE pl.person_id = p.id ) AND NOT EXISTS ( SELECT 1 FROM person_nationality pn WHERE pn.person_id = p.id )
这种方式会让每个地点对应一行(国籍为空)、每个国籍对应一行(地点为空),无关联数据的Person单独一行,不会出现行数暴增。
内容的提问来源于stack exchange,提问作者Remy

