【问题标题】:SQL Help: Counting Rows in a Single Query With a Nested SELECTSQL 帮助:使用嵌套 SELECT 计算单个查询中的行数
【发布时间】:2009-01-07 20:33:18
【问题描述】:

我正在寻找一种更好的方法来执行以下查询。我有一个看起来像这样的表:

game_id | home_team_id | away_team_id
1       | 100          | 200
2       | 200          | 300
3       | 200          | 400
4       | 300          | 100
5       | 100          | 400

我想编写一个查询,计算每支球队的主场和客场比赛的数量,并输出以下内容:

team_id | home_games | away_games
100     | 2          | 1
200     | 2          | 1
300     | 1          | 1
400     | 0          | 2

现在,我编写了这个可以运行的怪物,但速度很慢(我知道它会将整个 2,800 行从表中拉出两次)。

SELECT 
  home_team_id as team_id,
  (SELECT count(*) FROM `game` WHERE home_team_id = temp_game.home_team_id) as home_games,
  (SELECT count(*) FROM `game` WHERE home_team_id = temp_game.away_team_id) as away_games
  FROM (SELECT * FROM `game`) as temp_game
  GROUP BY home_team_id

SQL 大师能帮我找出更好的方法吗?我认为我的问题是我不明白如何获得一个不同的团队 ID 列表来抛出计数查询。我敢打赌,有一个更好的方法可以放置更好的嵌套 SELECT。提前致谢!

【问题讨论】:

  • 感谢您的大力帮助。最后,我认为过度设计是愚蠢的,并采用了弗兰克弗林的 2 表方法(添加一个团队表并加入他们)。

标签: sql


【解决方案1】:

如果您有另一个具有 team_id 和 team_name 的表 team 会更干净。

选择团队 ID、团队名称、 sum(team_id = home_team_id) 作为 home_games, sum(team_id = away_team_id) as away_games FROM 游戏,团队 GROUP BY team_id

发生了什么:没有 WHERE 子句导致两个表之间出现笛卡尔积;我们按 team_id 分组以返回每个团队的一行。现在每个 team_id 都有来自游戏表的所有行,因此您需要对它们进行计数,但 SQL 计数函数并不完全正确(它将计算所有行或所有不同的行)。所以我们说 team_id = home_team_id 解析为 1 或 0,我们使用 sum 将 1 相加。

team_name 只是因为当我们应该说“泥城扼杀者队有 20 场主场比赛”时,说“200 队有 20 场主场比赛”是令人讨厌的。

PS。即使没有比赛,这也可以工作(通常是 SQL 中的问题,其中有一个团队有 0 场比赛,并且该行不会显示,因为加入失败)。

【讨论】:

  • 哇,我一定有严重的脑抽筋,因为这完全有道理。我确实有一个团队表......但我想锻炼一下我的大脑(然后被卡住并来到SO!)。 :)
  • 我向你的伟大鞠躬!
  • 不是一个很好的答案,因为它假定了未知或未知的工件。我很高兴它适用于原始海报,但它真的不合适。巧合的是,它适用于原始海报。
【解决方案2】:

如果你想要不同的球队名单,你必须从比赛表中选择两次,将主队和客队联合起来(理论上,如果你有逻辑,一支球队可以在客场或主场进行所有比赛这可以防止这种情况发生,那么您可以调整此查询):

select home_team_id as team_id from game union
select away_team_id as team_id from game

union 运算符将确保您只在返回集中获得不同的元素(除非您使用 union all

从那里,您可以使用左外连接来聚合您的数据:

select
    u.team_id, count(h.game_id) as home_games, count(a.game_id) as away_games
from
    (
        select home_team_id as team_id from game union
        select away_team_id as team_id from game
    ) as u
        left outer join game as h on h.home_team_id = u.team_id
        left outer join game as a on a.away_team_id = u.team_id
group by
    u.team_id

如果您想进一步减少表扫描(以上将产生四个),您可以添加更多代码,但这会花费您。您可以获得带有 team_id 的行列表,以及比赛是在主场还是客场进行:

select
    case ha.home when 0 then g.away_team_id else g.home_team_id end as team_id,
    case ha.home when 0 then 0 else 1 end as home_games,
    case ha.home when 0 then 1 else 0 end as away_games
from
    game as g, (select 0 as home union select 1 as home) as ha

从那里,您可以简单地总结每支球队的主场和客场比赛:

select
    t.team_id, sum(t.home_games) as home_games, sum(t.away_games) as away_games
from
    (
        select
            case ha.home when 0 then g.away_team_id else g.home_team_id end as team_id,
            case ha.home when 0 then 0 else 1 end as home_games,
            case ha.home when 0 then 1 else 0 end as away_games
        from
            game as g, (select 0 as home union select 1 as home) as ha
    ) as t
group by
    t.team_id

这将导致单表扫描。

【讨论】:

    【解决方案3】:

    格雷格,

    我认为您的最终解决方案将是特定于语言的。但如果您在 Oracle 中执行此操作,则只能使用以下命令查询该表一次:

    SELECT game.home_team_id AS team_id,
           SUM(CASE WHEN game.home_team_id = game.away_team_id
                    THEN 1
                    ELSE 0 END) AS home_games,
           SUM(CASE WHEN game.home_team_id <> game.away_team_id
                    THEN 1
                    ELSE 0 END) AS away_games
      FROM game
    GROUP BY game.home_team_id
    ORDER BY game.home_team_id;
    

    你没有说你使用的是什么风格的 SQL,所以这是我能做的最好的。

    祝你好运,

    炖菜

    附言看起来我给出了与 MarlonRibunal 相同的解决方案。我只是没有方便的链接,所以不得不手动创建代码。 :-/

    【讨论】:

    • 我使用的是 MySQL 5.0。我希望它可以在不依赖供应商特定细节的情况下完成......但是哦,好吧!
    • Greg,我尽量把它写成与供应商无关。我知道 CASE 支持多种变体,所以我会说试一试,看看会发生什么。
    • 我试了一下......速度很快,但结果有点偏离。我看到了主场比赛的准确列表(我知道总数应该是多少),但它们是在“客场比赛”的标签下。此外,当我知道它应该具有值时,“家庭游戏”列全为零。知道要调整什么吗?
    • 你确定你是按照我的方式写的吗,这对我来说是正确的。是否愿意以 INSERT 语句的形式提供更大的数据集?你提供的套装没有包含任何主场比赛,所以我无法检查。
    • 不,我的数据集(2008 MLB 赛季)中的每支球队肯定都有主客场比赛。我想知道这是不是供应商的差异?感谢 Stewstools 的帮助,但我选择了 Frank Flynn 给出的第二张桌子概念。
    【解决方案4】:

    试试这个:

    Select Z.teamId, 
        Count(H.Game_Id) HomeGames, 
        Count(A.Game_Id) AwayGames
    From (Select Distinct home_team_id TeamId From Game
            Union 
          Select Distinct away_team_id TeamId From Game) Z
       Left Join Game H On H.home_team_id = Z.TeamId
       Left Join Game A On A.away_team_id = Z.TeamId
    Group By Z.TeamId
    

    【讨论】:

    • Union 还不错,但使用teams 表应该能以更低的成本解决同样的问题。
    • 什么队表?我没有看到他的架构中有一个……团队表会好得多。
    【解决方案5】:
    declare @ts table
    
    (
        team_id int
    )
    
    declare @t table
    (
        id int,
        h int,
        a int
    )
    
    insert into @ts values (100)
    insert into @ts values (200)
    insert into @ts values (300)
    insert into @ts values (400)
    
    insert into @t values (1, 100, 200)
    insert into @t values (2, 200, 300)
    insert into @t values (3, 200, 400)
    insert into @t values (4, 300, 100)
    insert into @t values (5, 100, 400)
    
    select s.team_id, t0.home, t1.away
    from @ts s
        left outer join (select team_id, count(h) as [home] from @ts inner join @t on h = team_id group by team_id) t0 on t0.team_id = s.team_id
        left outer join (select team_id, count(a) as away from @ts inner join @t on a = team_id group by team_id) t1 on t1.team_id = s.team_id
    

    【讨论】:

      【解决方案6】:

      这个解决方案相当丑陋,但它应该可以在大型数据集上快速运行:

      select
        teams.team_id
       ,case when home.home_game_count is null
             then 0
             else home.home_game_count
        end home_game_count  
       ,case when away.away_game_count is null
             then 0
             else away.away_game_count
        end as away_game_count
      from
        ( 
        select home_team_id as team_id from games
        union
        select away_team_id as team_id from games  
        ) teams
        left outer join
        (  
        select home_team_id as team_id, count(*) as home_game_count
        from games
        group by home_team_id
        ) home
        on teams.team_id = home.team_id
        left outer join
        (
        select away_team_id as team_id, count(*) as away_game_count
        from games
        group by away_team_id
        ) away  
        on teams.team_id = away.team_id  
      

      【讨论】:

        【解决方案7】:

        抱歉,我在 away_games 条款中的错误。我更改了比较运算符(改为 )而不是更改结果值。我必须创建额外的数据才能看到问题。

        SELECT team_id,
               teams.team_name,
               SUM(CASE
                       WHEN game.home_team_id = game.away_team_id THEN
                        1
                       ELSE
                        0
                   END) AS home_games,
               SUM(CASE
                       WHEN game.home_team_id = game.away_team_id THEN
                        0
                       ELSE
                        1
                   END) AS away_games
          FROM teams
          LEFT OUTER JOIN game ON game.home_team_id = teams.team_id
         GROUP BY team_id, teams.team_name
        

        【讨论】:

          【解决方案8】:

          这是另一个例子。

          我要指出的是,您应该从 teams 表中开始您的 from 子句,这样您就可以确保包括所有球队,即使他们还没有参加过比赛。

          此查询将您的两个查询作为连接而不是子选择执行,这应该会更好。

          -- 注意:如果你使用 mysql,coalesce 就像 ifnull。

          SELECT  
          team_id as team_id,  
          coalesce(home_game_counts.games,0) home_games,  
          coalesce(away_game_counts.games,0) away_games  
          FROM teams  
          left join (select home_team_id, count(*) games from games group by home_team_id) as   home_game_counts on home_game_counts.home_team_id = teams.team_id  
          left join (select away_team_id, count(*) games from games group by away_team_id) as   away_game_counts on away_game_counts.away_team_id = teams.team_id  
          GROUP BY teams.team_id, home_game_counts.games ,  
          away_game_counts.games   
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-11-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-11-01
            相关资源
            最近更新 更多