MySQL使用sum和left join将结果加倍

时间:2012-11-28 14:09:43

标签: mysql sum left-join

我有三个表格设置:儿童,玩具和游戏,每个都有唯一的主键:id_kid,id_toy和id_game。每个孩子可以有多个玩具和游戏,但每个玩具或游戏只有一个孩子。

玩具和游戏的购买列有3种状态:-1,0,1 表结构是这样的:

kids

id_kid
kid_name
etc

games

id_game
id_kid_games --> links with id_kid in kids_table (maybe not the best name, I know)
game_name
bought --> can be -1,0,1

toys

id_toy
id_kid_toys --> links with id_kid in kids_table
toy_name
bought --> can be -1,0,1

对于每个孩子,我正在尝试使用下面的查询获得总共购买和未购买的玩具和游戏,但结果加倍:

SELECT kids.*, 

COUNT(DISTINCT toys.id_toy) AS total_toys, 
SUM(CASE toys.bought WHEN 1 THEN 1 ELSE 0 END) AS toys_bought, 
SUM(CASE toys.bought WHEN -1 THEN 1 ELSE 0 END) AS toys_not_bought, 

COUNT(DISTINCT games.id_game) AS total_games, 
SUM(CASE games.bought WHEN 1 THEN 1 ELSE 0 END) AS games_bought, 
SUM(CASE games.bought WHEN -1 THEN 1 ELSE 0 END) AS games_not_bought 

FROM kids as k 
LEFT JOIN toys t ON k.id_kid = t.id_kid_toys
LEFT JOIN games g ON k.id_kid = g.id_kid_games
GROUP BY k.id_kid
ORDER BY k.name ASC

一个孩子有2个玩具和4个游戏,都买了,结果是2个玩具总数(正确),4个游戏(正确),8个玩具买,8个游戏买。 (都错了)

如果可能,请帮助解答 - 不使用子选择。 谢谢。

1 个答案:

答案 0 :(得分:2)

当您从两个不相关的关系(孩子加入玩具,孩子加入游戏)中选择数据时,子查询是自然的方式。由于可以使用不相关的子查询,因此这不应该特别慢。

尝试此查询是否足够有效:

与原始查询相比,它基本上只是颠倒了加入和分组的顺序。

SELECT kids.*, t.total_toys, t.toys_bought, t.toys_not_bought,
               g.total_games, g.games_bought, g.games_not_bought
FROM kids
LEFT JOIN (SELECT id_kids_toys,
                  COUNT(*) AS total_toys,
                  SUM(CASE bought WHEN 1 THEN 1 ELSE 0 END) as toys_bought,
                  SUM(CASE bought WHEN -1 THEN 1 ELSE 0 END) as toys_not_bought
           FROM toys
           GROUP BY id_kids_toys) AS t
ON t.id_kids_toys = kids.id_kid
LEFT JOIN (SELECT id_kids_games,
                  COUNT(*) AS total_games,
                  SUM(CASE bought WHEN 1 THEN 1 ELSE 0 END) as games_bought,
                  SUM(CASE bought WHEN -1 THEN 1 ELSE 0 END) as games_not_bought
           FROM games
           GROUP BY id_kids_games) AS g
ON g.id_kids_games = kids.id_kid
ORDER by kids.name;

如果你坚持避免使用子查询,那么查询效率可能会低得多:

SELECT kids.*, 
COUNT(DISTINCT toys.id_toy) AS total_toys, 

-- sum only toys joined to first game
SUM(IF(g2.id_game IS NULL AND bought = 1, 1, 0)) AS toys_bought, 
SUM(IF(g2.id_game IS NULL AND bought = -1, 1, 0)) AS toys_not_bought, 

-- sum only games joined to first toy
COUNT(DISTINCT games.id_game) AS total_games, 
SUM(IF(t2.id_toy IS NULL AND bought = 1, 1, 0)) AS games_bought, 
SUM(IF(t2.id_toy IS NULL AND bought = -1, 1, 0)) AS games_not_bought 

FROM kids as k 
LEFT JOIN toys t ON k.id_kid = t.id_kid_toys
LEFT JOIN games g ON k.id_kid = g.id_kid_games

-- select only rows where either game or toy is the first one for this kid
LEFT JOIN toys t2 on k.id_kid = t.id_kid_toys AND t2.id_toy < t.id_toy
LEFT JOIN games g2 ON k.id_kid = g.id_kid_games AND g2.id_game < g.id_game 
WHERE t2.id_toy IS NULL OR g2.id_game IS NULL

GROUP BY k.id_kid
ORDER BY k.name ASC

通过确保每个孩子只计算加入第一个玩具的游戏,并且只计算加入第一个游戏的玩具。