Справка SQL: Подсчет строк в одном запросе с помощью вложенного SELECT - PullRequest
6 голосов
/ 07 января 2009

Я ищу лучший способ сделать следующий запрос. У меня есть таблица, которая выглядит так:

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

Прямо сейчас я написал это чудовище, которое работает, но оно медленно (я знаю, что оно дважды вытягивает из таблицы всю 2800 строк).

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 помочь мне найти лучший способ? Я думаю, что моя проблема в том, что я не понимаю, как получить четкий список идентификаторов команды, чтобы бросить на запросы подсчета. Бьюсь об заклад, есть лучший способ с лучшим размещением, вложенный SELECT. Заранее спасибо!

Ответы [ 8 ]

9 голосов
/ 07 января 2009

Будет чище, если у вас есть другая команда за столом с team_id и team_name.

SELECT team_id, team_name, 
     sum(team_id = home_team_id) as home_games, 
     sum(team_id = away_team_id) as away_games
 FROM game, team
 GROUP BY team_id

Что происходит: предложение no WHERE вызывает декартово произведение между двумя таблицами; мы группируемся по team_id, чтобы вернуться к одному ряду на команду. Теперь есть все строки из игровой таблицы для каждого team_id, поэтому вам нужно их посчитать, но функция подсчета SQL не совсем правильная (она будет считать все строки или все отдельные строки). Таким образом, мы говорим team_id = home_team_id, который разрешается до 1 или 0, и мы используем сумму, чтобы сложить 1.

Название команды просто потому, что уродливо говорить, что у «команды 200 было 20 домашних игр», когда мы должны сказать, что «у« Город грязи »было 20 домашних игр».

PS. это будет работать, даже если игр нет (часто это проблема в SQL, где есть команда с 0 играми, и эта строка не будет отображаться из-за сбоя соединения).

3 голосов
/ 07 января 2009

Если вам нужен четкий список команд, вам придется выбирать из игрового стола дважды, объединяя домашнюю и выездную команды (теоретически, одна команда может играть во все свои игры в дороге или дома, если у вас есть логика что мешает, то вы можете настроить этот запрос):

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

Это приведет к сканированию одной таблицы.

2 голосов
/ 07 января 2009

Грег

Я думаю, что ваше окончательное решение будет зависеть от языка. Но если вы делали это в 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 вы используете, так что это лучшее, что я могу сделать.

Удачи,

Стью

p.s. Похоже, я дал то же решение, что и MarlonRibunal. У меня просто не было удобной ссылки, поэтому пришлось создавать код вручную. : - /

0 голосов
/ 08 января 2009

Извините, моя ошибка в предложении 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
0 голосов
/ 08 января 2009

Это решение довольно уродливо, но оно должно работать быстро для больших наборов данных:

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  
0 голосов
/ 08 января 2009

Вот еще один пример.

Хотелось бы отметить, что вы должны начинать предложение с таблицы команд, чтобы вы обязательно включили все команды, даже если они еще не играли в игру.

Этот запрос выполняет два ваших запроса как объединения, а не подвыборы, которые должны работать лучше.

- примечание: coalesce похож на ifnull, если вы используете mysql.

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   
0 голосов
/ 07 января 2009
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
0 голосов
/ 07 января 2009

Попробуйте это:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...