當我們使用mysql的join功能從多張表中取出數據並使用sum分別對取出的數據求和時
會發現sum出來的值是不對的,往往是正確值的整數倍
為什么會出現這樣的情況呢
復現
假設有兩張表:user_buy 和user_sell,分別記錄了用戶在某天的購買和出售金額,
結構如下:
CREATE TABLE `user_buy` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(10) unsigned NOT NULL COMMENT '用戶id',
`amount` int(10) unsigned default '0' COMMENT '數量',
`init_time` date not null comment '日期',
PRIMARY KEY (`id`),
KEY `uid` (`uid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用戶購買統計';
CREATE TABLE `user_sell` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(10) unsigned NOT NULL COMMENT '用戶id',
`amount` int(10) unsigned default '0' COMMENT '數量',
`init_time` date not null comment '日期',
PRIMARY KEY (`id`),
KEY `uid` (`uid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用戶出售統計';
數據如下:
現在我想求用戶在一段時間里面的購買總金額 - 出售總金額,並根據差值排序,取前30個用戶
一番思考后,我寫下了如下sql
select ub.uid, sum(ub.amount) - sum(us.amount) total
from user_buy ub
left join user_sell us on ub.uid = us.uid
group by ub.uid
order by total
limit 30
得到如下結果:
很明顯,這個結果是不對的
用戶11的正確值應該是(100+200) - (10 + 20) = 270
用戶22的正確值應該是(300+400) -(30 + 40) = 630
sql得出的結果是正確值的2倍!
猜想
由於按照uid字段進行聚合,且uid也是兩個表關聯的聯結字段,因此會出現以下情況:
1.user_buy中的某個uid在user_sell中存在,且在user_sell中有n條記錄時,會使得sum(ub.amount)的值變為正常值的n倍
2.user_sell中的某個uid在user_buy中存在,且在user_buy中有m條記錄時,會使得sum(us.amount)的值變為正常值的m倍
驗證
修改數據,在user_sell中增加一條uid = 11的數據
繼續用上面的sql查詢:
用戶11的正確值應該是(100+200) - (10 + 20+30) = 240
780 怎么來的?
(100+200)* 3 - (10 + 20 +30)* 2 = 780
1260同理,猜想正確
解決方案
為了避免聯表字段同時滿足多條記錄的情況
先用子查詢在各自表中完成數據的聚合,將數據存放在臨時表中,再聯合臨時表
此時兩個臨時表中的數據對聚合字段uid來說都是唯一的
sql如下:
select ub.uid, sum(ub.amount) - sum(us.amount) total
from (select uid, sum(amount) as amount from user_buy group by uid) as ub
left join (select uid, sum(amount) as amount from user_sell group by uid) as us on ub.uid = us.uid
group by ub.uid
order by total
limit 30
結果: