如何使用内部联接在SQL Server 2008中获得所需的结果

时间:2017-04-03 13:55:40

标签: sql-server-2008

我有2个名为PlayersTeams的表。大约有100行数据。

  • Players列:Player_ID,Player_Name,Team_ID,Country_ID,Captain_ID,Matches_Played

  • Teams列:Team_ID,Team_Name,Manager_ID,Matches_Won,Matches_Lost,Country_ID

Players表:

  --------------------------------------------------------------------------
  | Player_ID  Player_Name  Team_Id  Country_ID  Captain_ID  Matches_Played|
  -------------------------------------------------------------------------- 
  |    1         Ronaldo       1         1           1            250      |
  |    2         Messi         2         2           2            220      |
  |    3         Marcelo       1         1           1            185      |
  |    4         Suarez        2         2           2            193      |
  --------------------------------------------------------------------------

我希望使用 INNER JOIN 在每个团队中找到玩过最多游戏的玩家。

期望的结果:

  --------------------------------------------------------------------------
  | Player_ID  Player_Name  Team_Id  Country_ID  Captain_ID  Matches_Played|
  -------------------------------------------------------------------------- 
  |    1         Ronaldo       1         1           1            250      |
  |    2         Messi         2         2           2            220      |
  --------------------------------------------------------------------------  

我尝试使用的查询:

SELECT 
    p.Player_Name, t.Team_Name, src.Matches_Played AS Matches_Played
FROM 
    Players p
INNER JOIN 
    Teams t ON p.Team_ID = t.Team_ID
INNER JOIN 
    (SELECT Team_ID, MAX(Matches_Played) AS Matches_Played
     FROM Players
     GROUP BY Team_ID) src ON t.Team_ID = src.Team_ID
                           AND p.Team_ID = src.Team_ID;

此查询返回整个表格,每个玩家旁边的MAXMatches_Played相同。

我如何修复查询以获得所需的结果?

3 个答案:

答案 0 :(得分:3)

如果我理解你的问题,我想你可以尝试:

SELECT p.Player_Name, t.Team_Name, src.Matches_Played AS Matches_Played
FROM Players p
INNER JOIN Teams t
ON p.Team_ID = t.Team_ID
INNER JOIN (
            SELECT Team_ID, MAX(Matches_Played) AS Matches_Played
            FROM Players
            GROUP BY Team_ID)src
ON p.Team_ID = src.Team_ID
AND p.Matches_Played = src.Matches_Played;

答案 1 :(得分:3)

你根本不需要加入来做这件事。从Sql Server 2005开始,有一个名为APPLY运算符的东西可以更好地用于此查询:

SELECT p.Player_Name, t.Team_Name, p.Matches_Played
FROM Teams t
CROSS APPLY (
     SELECT TOP 1 Player_Name, Matches_Played 
     FROM Players p 
     WHERE p.Team_ID = t.Team_ID 
     ORDER BY Matches_Played DESC
) p

但是如果这是一个由于某种原因需要使用JOIN的作业,则需要分两步完成。首先找到目标玩家的匹配数,然后获得该记录的完整行:

SELECT p.Player_Name, t.Team_Name, p.Matches_Played
FROM Teams t
INNER JOIN (
     SELECT Team_ID, MAX(Matches_Played) as Max_Played
     FROM Players
     GROUP BY Team_ID
) played ON played.Team_ID = t.Team_ID
INNER JOIN Players p ON p.Team_ID = played.Team_ID AND p.Matches_Played = played.Max_Played

请注意,如果出现平局,这可能会显示每个团队不止一行,但问题并不能说明在这种情况下该怎么做。

另请注意,对于两个查询,我都是从Teams表而不是Players表开始的。查询优化器应该能够以任何一种方式解决它,但我认为对于这个查询来说,它使逻辑意义上的程序员开始考虑为每个团队记录找到匹配,特别是当我们看到{{1}时} option永远不会在查询的根目录中使用Players表。

最后,我怀疑还有第三个解决方案会使用窗口函数(有序的row_number + parition by),这可能会更好。

答案 2 :(得分:0)

我认为这种情况将是使用ROW_NUMBER的好地方。

基础数据:

CREATE TABLE #Players (Player_ID INT
                      ,Player_Name VARCHAR(50)
                      ,Team_ID INT
                      ,Country_ID INT
                      ,Captain_ID INT
                      ,Matches_Played INT)

INSERT INTO #Players (Player_ID, Player_Name, Team_ID, Country_ID, Captain_ID, Matches_Played)
VALUES (1, 'Renaldo', 1, 1, 1, 250)
      ,(2, 'Messi', 2, 2, 2, 220)
      ,(3, 'Marcelo', 1, 1, 1, 185)
      ,(4, 'Suarez', 2, 2, 2, 193);

然后,我使用了一个基本的Select语句,并将其加入到另一个使用ROW_NUMBER()的select语句中

SELECT   p.Player_ID
        ,p.Player_Name
        ,p.Team_ID
        ,p.Country_ID
        ,p.Captain_ID
        ,p.Matches_Played
FROM #Players p
INNER JOIN (SELECT Player_ID
                  ,ROW_NUMBER() OVER (PARTITION BY Team_ID ORDER BY Matches_Played DESC) AS rnk
  FROM #Players) AS p1 ON p1.Player_ID = p.Player_ID AND rnk = 1

这是使用ROW_NUMBER()为团队中的每个球员分配位置。如果您在同一支球队中有5名球员,它将以1到5的顺序排列,其中1场比赛最多,5场最少。然后,当您在rnk = 1上加入时,您只会加入每个团队中最多玩游戏的玩家。

如果这会让您感到困惑,则可以将语句放入JOIN中,也可以采用其他方法。

具有CTE(公用表表达式):

WITH CTE (Player_ID, Rnk) AS
    (SELECT Player_ID
           ,ROW_NUMBER() OVER (PARTITION BY Team_ID ORDER BY Matches_Played DESC)
     FROM #Players)
SELECT   p.Player_ID
        ,p.Player_Name
        ,p.Team_ID
        ,p.Country_ID
        ,p.Captain_ID
        ,p.Matches_Played
FROM #Players p
INNER JOIN CTE ON cte.Player_ID = p.Player_ID AND rnk = 1

带有临时表:

SELECT Player_ID
      ,ROW_NUMBER() OVER (PARTITION BY Team_ID ORDER BY Matches_Played DESC) AS rnk
INTO #RankTable
FROM #Players

SELECT   p.Player_ID
        ,p.Player_Name
        ,p.Team_ID
        ,p.Country_ID
        ,p.Captain_ID
        ,p.Matches_Played
FROM #Players p
INNER JOIN #RankTable r ON r.Player_ID = p.Player_ID AND rnk = 1