MySQL JOIN 与 SUM 和 3 个表

Posted

技术标签:

【中文标题】MySQL JOIN 与 SUM 和 3 个表【英文标题】:MySQL JOIN with SUM and 3 tables 【发布时间】:2012-06-17 22:47:23 【问题描述】:

我有以下表格(球员、赛事、分数)

我想要一份每个球员在 2012 年获得多少积分的列表。 我希望列表中包含所有球员,即使有些球员在 2012 年还没有参加比赛。

所以理论上它应该打印:

Ola Hansen     6  
Tove Svendson  0  
Kari Pettersen 0

我试过了:

SELECT *, sum(Points) as Points FROM Players
LEFT OUTER JOIN Scores ON P_Id=Player
LEFT OUTER JOIN Events ON Event=E_Id AND Year='2012' 
GROUP by P_Id ORDER by Points DESC

但这算了所有年份的所有分数。

【问题讨论】:

我猜这是 mysql,因为没有其他 RDBMS 会允许这种可怕的 GROUP BY 和聚合? 【参考方案1】:

由于人们(包括我自己)喜欢将“最重要”或“主题”表放在首位,因此您可以通过首先发生事件和分数之间的连接并作为 INNER JOIN 来完成您要查找的内容:

SELECT
   P.P_Id,
   P.LastName,
   P.FirstName,
   IsNull(Sum(P.Points), 0) TotalPoints
FROM
   Players P
   LEFT JOIN (
      Events E
      INNER JOIN Scores S
         ON E.Event = S.E_Id
         AND E.Year = '2012'
   ) ON P.P_Id = S.Player
GROUP BY P.P_Id, P.LastName, P.FirstName -- no silly MYSQL implicit grouping for me!
ORDER BY TotalPoints DESC

强制连接顺序的诀窍在于,实际上是 ON 子句的位置执行此操作 - 您可以删除括号,它应该仍然有效。但我更喜欢使用它们,因为我认为额外的清晰度是必须的。

通过将 INNER JOIN 放在最前面,将 OUTER 放在最后,这也是强制执行连接顺序的一种完全有效的方法。只是,您希望最后一张桌子决定最后一组的成员资格。因此,将其切换为 RIGHT JOIN 即可:

SELECT
   P.P_Id,
   P.LastName,
   P.FirstName,
   IsNull(Sum(P.Points), 0) TotalPoints
FROM
   Events E
   INNER JOIN Scores S
      ON E.Event = S.E_Id
      AND E.Year = '2012'
   RIGHT JOIN Players P
      ON S.Player = P.P_Id
GROUP BY P.P_Id, P.LastName, P.FirstName
ORDER BY TotalPoints DESC

我觉得有必要补充一点,在我看来,在不同表中使用不同名称作为 PK 与 FK 的列是对最佳实践的严重违反。应该是所有表中的“P_Id”,或者所有表中的“Player”,不能不一致。

【讨论】:

他不是在谈论一组主键表中的愚蠢,他是说他不喜欢引用表中的列名与主键表中的列名不同,在这种情况下,如果玩家.P_Id 是 Pk,在 Score 中应该是 Scores.P_Id 而不是 Scores.Player,我同意 erike。维护标准 哦,哈哈,你说的是group by,在MysQl中你可以通过一个“*”来进行group by,这真的很傻,应该是列 @Mr.我可以接受GROUP BY * 甚至GROUP BY Column, REMAINDER,因为这样会使其明确,但隐式分组很可怕。【参考方案2】:
SELECT P.FIRSTNAME, P.LASTNAME,  A.Points as Points 
FROM Players P
LEFT OUTER JOIN 
(
   SELECT S.Player, sum(Points) 
   FROM Scores S, Events E 
   WHERE S.Event=E.E_Id 
   AND E.Year='2012' 
   GROUP BY S.Player
) A 
ON A.PLAYER = P.P_ID
ORDER by Points DESC

基本上,您需要在事件和分数之间使用INNER JOIN 来强制将 2012 年的分数和 LEFT OUTER JOIN 与此子结果相加,因为您希望所有玩家 + 他们在 2012 年的分数。

【讨论】:

【参考方案3】:

试试这个:

select firstName, lastName, sum(if(year is null, 0, points)) points from p
left join s on p_id = player
left join e on e_id = event and year = 2012
group by p_id, lastName, firstName
order by points desc

我想它应该比子查询执行得更好......但便携性会降低。试试看!

这里是fiddle。

【讨论】:

【参考方案4】:

分数和事件需要在内连接将它们外连接到玩家。

我们可以使用子查询或括号来强制这个特定的连接“优先级”,但最好只使用 SQL 文本中的 JOIN 顺序,然后小心地将最后一个 JOIN“定向”到玩家(在这种情况下是正确的) )。

COALESCE 仅用于将 NULL 转换为 0。

SELECT
    P_Id, LastName, FirstName, COALESCE(SUM(Points), 0) TotalPoints
FROM
    Scores
    JOIN Events
        ON Event = E_Id AND Year = 2012
    RIGHT JOIN Players
        ON P_Id = Player
GROUP BY
    P_Id, LastName, FirstName
ORDER BY
    TotalPoints DESC;

这会产生:

P_ID    LASTNAME    FIRSTNAME   TOTALPOINTS
1       Hansen      Ola         6
2       Svendson    Tove        0
3       Pettersen   Kari        0

你可以在这个SQL Fiddle玩它。

【讨论】:

以上是关于MySQL JOIN 与 SUM 和 3 个表的主要内容,如果未能解决你的问题,请参考以下文章

MySQL LEFT JOIN 3 个表

来自第二个表的 MySQL INNER JOIN (TOP10)

MySQL INNER JOIN 3 个表的计数和总数

MySql JOIN 3 个表并获取具有相同值的子行

MYSQL - 将 SUM 与 JOIN 结合使用

在 Google bigquery 中加入 3 个表