SQL Server多次内连接同表时如何正确返回无重复计数结果?
解决SQL计数因笛卡尔积膨胀的问题
嘿,我完全懂你遇到的这个坑!你现在得到49而不是期望的7,根本原因是两次关联cat_item_tb表导致了笛卡尔积——每一条ss=0的记录都会和对应ss=1的记录两两配对,7条×7条就变成了49条,自然计数就炸了。而且直接加distinct没用,是因为笛卡尔积已经让数据重复了,再去重也没法回到正确的统计逻辑里。
给你几个靠谱的解决方案,按效率和简洁度排序:
方案1:条件聚合(最推荐)
只关联一次cat_item_tb,用CASE语句分别统计不同ss值的唯一item_id,完全避免笛卡尔积:
SELECT COUNT(DISTINCT CASE WHEN ci.ss = 0 THEN ci.item_id END) AS count_ss0, COUNT(DISTINCT CASE WHEN ci.ss = 1 THEN ci.item_id END) AS count_ss1 FROM cat_tb INNER JOIN item_tb ON cat_tb.cat_id = item_tb.cat_id INNER JOIN cat_item_tb ci ON item_tb.item_id = ci.item_id;
- 原理:通过一次表关联拿到所有相关记录,用
CASE筛选出对应ss的item_id,再用DISTINCT确保每个item_id只被计数一次,完美解决重复和笛卡尔积问题。
方案2:子查询分别统计
如果更习惯拆分逻辑,可以用独立子查询分别计算两个计数,最后合并结果:
SELECT (SELECT COUNT(DISTINCT item_id) FROM cat_item_tb WHERE ss = 0 AND item_id IN (SELECT item_id FROM item_tb WHERE cat_id IN (SELECT cat_id FROM cat_tb))) AS count_ss0, (SELECT COUNT(DISTINCT item_id) FROM cat_item_tb WHERE ss = 1 AND item_id IN (SELECT item_id FROM item_tb WHERE cat_id IN (SELECT cat_id FROM cat_tb))) AS count_ss1;
- 原理:每个子查询单独统计对应
ss的唯一item_id,同时通过IN子句关联cat_tb和item_tb的筛选条件,不会产生交叉配对的问题。
方案3:左连接+去重计数
如果一定要用join的方式,可以用左连接分别关联不同ss的表,再用DISTINCT去重计数:
SELECT COUNT(DISTINCT ci0.item_id) AS count_ss0, COUNT(DISTINCT ci1.item_id) AS count_ss1 FROM cat_tb INNER JOIN item_tb ON cat_tb.cat_id = item_tb.cat_id LEFT JOIN cat_item_tb ci0 ON item_tb.item_id = ci0.item_id AND ci0.ss = 0 LEFT JOIN cat_item_tb ci1 ON item_tb.item_id = ci1.item_id AND ci1.ss = 1;
- 原理:左连接不会强制要求两条表都有匹配,但因为我们用了
DISTINCT,即使左连接产生了重复行,也只会保留唯一的item_id计数。
为什么你之前加DISTINCT没用?
因为你是在已经产生笛卡尔积的结果集上去重,比如原来的SQL里count(distinct cat_item_tb.item_id)确实会返回7,但count(distinct t.item_id)也会返回7,但如果你没加DISTINCT,就会得到49——可能你之前的尝试没把DISTINCT加到正确的位置?不过不管怎样,上面的方案从根源上避免了笛卡尔积,比事后去重更高效。
内容的提问来源于stack exchange,提问作者hussam saleh
相关产品推荐
相关产品推荐

