如何从重复行执行查询?多表关联行转列实现求助
解决一对多关联导致的多行结果问题
嘿,我来帮你搞定这个问题!你遇到的是一对多关联查询里非常常见的重复行问题——因为一个人可能对应多条电话记录(家庭、工作各一条),直接JOIN后每条电话都会生成一行结果。要把同一人的不同类型电话合并到同一行,核心思路就是行转列,用「条件聚合」的方法几乎所有数据库都能支持,下面给你详细拆解:
先明确关键表结构假设(你可以对应自己的实际表调整)
我先默认你的表有这些核心字段(如果和你的实际结构不一样,替换成对应的字段名就行):
person表:person_id(主键)、name(对应你要的NAME)、mrn_nb(对应MRN_NB)phone表:phone_id(主键)、person_id(外键关联person表)、phone_type(用来区分电话类型,比如'HOME'代表家庭电话,'WORK'代表工作电话)、phone_number(电话号码)
通用SQL写法(支持MySQL、SQL Server、PostgreSQL等绝大多数数据库)
SELECT p.name AS NAME, p.mrn_nb AS MRN_NB, -- 筛选出家庭电话,用MAX聚合忽略NULL值 MAX(CASE WHEN ph.phone_type = 'HOME' THEN ph.phone_number END) AS HOME_PHONE_NBR, -- 筛选出工作电话 MAX(CASE WHEN ph.phone_type = 'WORK' THEN ph.phone_number END) AS WORK_PHONE_NBR FROM person p -- 用LEFT JOIN保证没有电话的人也能出现在结果里 LEFT JOIN phone ph ON p.person_id = ph.person_id -- 按person的唯一标识分组,确保每个人只返回一行 GROUP BY p.person_id, p.name, p.mrn_nb;
代码逻辑解释
- LEFT JOIN:哪怕某个人没有家庭或工作电话,依然会出现在结果中,对应的电话字段会显示为
NULL,不会漏掉数据。 - CASE WHEN:根据电话类型匹配对应的号码,非目标类型的记录会返回
NULL,相当于给不同类型的电话做标记。 - MAX()聚合函数:配合
GROUP BY,把同一人的多条记录合并成一行,自动过滤掉NULL值,只保留有效的电话号码。 - GROUP BY:必须包含
person表的主键和所有非聚合字段(比如name、mrn_nb),这样才能保证每个人员只返回一条结果。
针对不同数据库的简化写法
如果你的数据库有专用的行转列工具,还能简化代码:
- MySQL:如果一个人可能有多个同类型电话(比如两个家庭电话),可以用
GROUP_CONCAT把它们合并成逗号分隔的字符串:
SELECT p.name AS NAME, p.mrn_nb AS MRN_NB, GROUP_CONCAT(DISTINCT CASE WHEN ph.phone_type = 'HOME' THEN ph.phone_number END SEPARATOR ', ') AS HOME_PHONE_NBR, GROUP_CONCAT(DISTINCT CASE WHEN ph.phone_type = 'WORK' THEN ph.phone_number END SEPARATOR ', ') AS WORK_PHONE_NBR FROM person p LEFT JOIN phone ph ON p.person_id = ph.person_id GROUP BY p.person_id, p.name, p.mrn_nb;
- SQL Server:可以用
PIVOT运算符实现行转列:
SELECT NAME, MRN_NB, HOME AS HOME_PHONE_NBR, WORK AS WORK_PHONE_NBR FROM ( -- 先整理出基础的人员+电话数据 SELECT p.name AS NAME, p.mrn_nb AS MRN_NB, ph.phone_type, ph.phone_number FROM person p LEFT JOIN phone ph ON p.person_id = ph.person_id ) AS SourceTable -- 把phone_type的不同值转成列 PIVOT ( MAX(phone_number) FOR phone_type IN (HOME, WORK) ) AS PivotTable;
- PostgreSQL:用
FILTER子句简化条件聚合逻辑:
SELECT p.name AS NAME, p.mrn_nb AS MRN_NB, MAX(ph.phone_number) FILTER (WHERE ph.phone_type = 'HOME') AS HOME_PHONE_NBR, MAX(ph.phone_number) FILTER (WHERE ph.phone_type = 'WORK') AS WORK_PHONE_NBR FROM person p LEFT JOIN phone ph ON p.person_id = ph.person_id GROUP BY p.person_id, p.name, p.mrn_nb;
小提示
如果你的phone表没有phone_type字段,而是用其他方式区分电话类型(比如单独的is_home布尔字段),只需要调整CASE WHEN里的判断条件就行,比如改成CASE WHEN ph.is_home = 1 THEN ph.phone_number END。
内容的提问来源于stack exchange,提问作者Jessica Gibbons
相关产品推荐
相关产品推荐

