SQL左连接3表仅保留首表数据 解决部分关联ID导致的重复问题
问题根因
重复记录来自关联条件不完整产生的笛卡尔积:
InvoiceTable的唯一键是I_invoiceId + I_shopId,第一次关联时仅用IDE_invoiceId = I_invoiceId做匹配,会把同一个发票ID下所有店铺的记录全部关联出来,从这一步开始就已经产生了多余行。- 第二次关联虽然在
ShopTable的关联条件中加了IDE_companyId = S.S_companyId,但因为用的是左连接,没有匹配上companyId的InvoiceTable记录依然会被保留(对应Shop表字段返回null),还是会出现重复行。
解决方案
根据「仅返回第一张表数据、必须关联全部3张表」的需求,有两种可靠写法,都不会产生重复记录:
方案1:内连接写法(需要取关联表字段时用)
直接用内连接替代左连接,把companyId的匹配条件放在关联逻辑里,只会保留三张表完全匹配的记录,从根源避免无效匹配产生的多余行:
SELECT IDE.* FROM invoiceDetailsTable IDE INNER JOIN invoiceTable I ON IDE.IDE_invoiceId = I.I_invoiceId INNER JOIN ShopTable S ON I.I_shopId = S.S_shopId AND IDE.IDE_companyId = S.S_companyId WHERE IDE.IDE_price IS NULL;
如果必须保留左连接逻辑(比如要保留没有匹配到店铺/发票的明细记录),只需要在where条件中补一层过滤,排除掉companyId不匹配的无效关联行即可:
SELECT IDE.* FROM invoiceDetailsTable IDE LEFT JOIN invoiceTable I ON IDE.IDE_invoiceId = I.I_invoiceId LEFT JOIN ShopTable S ON I.I_shopId = S.S_shopId AND IDE.IDE_companyId = S.S_companyId WHERE IDE.IDE_price IS NULL -- 过滤关联不匹配的无效行,同时保留左表无匹配的场景 AND (I.I_invoiceId IS NULL OR S.S_shopId IS NOT NULL);
方案2:EXISTS写法(仅取第一张表字段时优先用)
如果不需要返回Invoice表和Shop表的字段,用EXISTS做关联判断性能更好,完全不会产生笛卡尔积,也天然不会出现重复行:
SELECT * FROM invoiceDetailsTable IDE WHERE IDE.IDE_price IS NULL AND EXISTS ( SELECT 1 FROM invoiceTable I INNER JOIN ShopTable S ON I.I_shopId = S.S_shopId AND S.S_companyId = IDE.IDE_companyId WHERE I.I_invoiceId = IDE.IDE_invoiceId );
注意事项
- 不要遇到重复就直接加
DISTINCT去重,这是治标不治本的方案,会带来额外的排序去重性能开销,本质问题还是关联条件没有对齐各表的唯一键规则。 - 多表关联时必须明确每张表的唯一键,不能只用非唯一字段做关联,本题中就是通过companyId做桥接,定位到每个发票明细对应的唯一店铺、唯一发票记录,才避免了一对多匹配产生的重复。
内容的提问来源于stack exchange,提问作者user1841243
相关产品推荐
相关产品推荐

