2009-01-07 26 views
6

Estoy buscando una mejor manera de hacer la siguiente consulta. Tengo una tabla que tiene este aspecto:Ayuda de SQL: contar filas en una sola consulta con un anidado SELECCIONAR

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

Y quiero escribir una consulta que cuenta el número de juegos en casa y fuera de juegos para cada equipo y emite la siguiente:

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

este momento , Escribí esta monstruosidad que funciona, pero es lenta (sé que está sacando las 2.800 filas de la mesa dos veces).

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 

¿Puede un gurú de SQL ayudarme a noquear de una mejor manera? Creo que mi problema es que no entiendo cómo obtener una lista distinta de las identificaciones del equipo para arrojar al conteo de las consultas. Apuesto a que hay una mejor manera con un SELECT mejor ubicado y anidado. ¡Gracias por adelantado!

+0

Gracias por su gran ayuda. Al final, decidí que era una tontería sobre-diseñar y fui con el enfoque de 2 mesas de Frank Flynn (agregando una mesa de equipo y uniéndome a ellos). – Greg

Respuesta

9

Es más limpio si tiene otro equipo de mesa con team_id y 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 

¿Qué está pasando ?: la cláusula WHERE no causa un producto cartesiano entre las dos tablas; agrupamos por team_id para volver a una fila por equipo. Ahora hay todas las filas de la tabla de juego para cada team_id, por lo que debe contarlas, pero la función de conteo de SQL no es del todo correcta (contaría todas las filas o todas las filas distintas). Entonces decimos team_id = home_team_id que se resuelve en 1 o 0 y usamos suma para sumar los 1.

El team_name es simplemente porque es friki decir que 'el equipo 200 tuvo 20 partidos en casa' cuando deberíamos decir que 'Mud City Stranglers tuvo 20 partidos en casa'.

PS. esto funcionará incluso si no hay juegos (a menudo un problema en SQL donde hay un equipo con 0 juegos y esa fila no aparecerá porque la unión falla).

+0

Guau, debo tener un calambre cerebral serio porque tiene mucho sentido. De hecho, tengo una mesa de equipo ... pero quería ejercitar mi cerebro un poco (¡y luego me quedé atascado y llegué a SO!). :) – Greg

+0

¡Me inclino ante tu asombro! –

+1

No es realmente una buena respuesta, ya que supone artefactos que no se conocen ni se presentan. Estoy encantado de que funcionó para el póster original, pero realmente no es apto. Es solo una coincidencia que funcionó para el póster original. – casperOne

3

Si desea una lista distinta de equipos, debe seleccionar dos veces de la mesa de juego, uniendo los equipos de casa y de lejos (teóricamente, un equipo podría jugar todos sus juegos en la carretera o en casa, si tiene lógica que impide que, entonces usted podría ajustar esta consulta):

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

el operador union se asegurará de que sólo recibe distintos elementos en el conjunto de retorno (a menos que utilice union all)

A partir de ahí, se puede utilice uniones externas izquierdas para agregar sus datos:

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 

Si desea reducir los escaneos de su tabla aún más (lo anterior producirá cuatro), puede agregar más código, pero le costará. Usted puede obtener una lista de filas con el team_id, y si es o no el juego fue jugado en casa o fuera:

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 

A partir de ahí, sólo tiene que resumir los partidos en casa y fuera de cada equipo:

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 

Esto dará como resultado una exploración de una sola tabla.

2

Greg,

Creo que su solución definitiva será específica del idioma. Pero si estuviera haciendo esto en Oracle, se puede consultar la tabla de una sola vez con lo siguiente:

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; 

Usted no dice qué sabor de SQL que está utilizando así que esto es lo mejor que puedo hacer.

mejor de la suerte,

Guiso

P. S. Parece que he dado la misma solución que MarlonRibunal. Simplemente no tenía un enlace útil, así que tuve que crear el código a mano.: -/

+0

Estoy usando MySQL 5.0. Esperaba que se pudiera hacer sin apoyarse en detalles específicos del proveedor ... ¡pero bueno! – Greg

+0

Greg, Traté de escribir esto como proveedor neutral como pude. Sé que CASE es compatible con múltiples variantes, por lo que podría intentarlo y ver qué sucede. –

+0

Lo intenté ... es rápido pero los resultados fueron un poco triviales. Veo una lista precisa de los juegos en casa (sé cuáles deberían ser los totales '), pero están bajo la etiqueta de' juegos ausentes '. Además, la columna de 'juegos en el hogar' es todo ceros, cuando sé que debería tener valores. ¿Alguna idea sobre qué retocar? – Greg

0

Prueba esto:

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 
+0

La unión no está mal, pero usar la tabla de equipos debería resolver el mismo problema a un costo menor. –

+0

¿qué tabla de equipo? No vi que tuviera uno en su esquema ... una mesa de equipo sería mucho mejor. –

0
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

Aquí hay otro ejemplo.

Me gustaría señalar que debes comenzar con tu cláusula from de la tabla de equipos, de modo que estarás seguro de incluir a todos los equipos, incluso si aún no han jugado un juego.

Esta consulta hace sus dos consultas como uniones en lugar de subselecciones, que deberían funcionar mejor.

- nota: coalesce es como ifnull en caso de que esté utilizando 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

Esta solución es bastante feo, pero debería funcionar rápidamente a través de grandes conjuntos de datos:

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

Lo siento, mi error en la cláusula away_games. Cambié el operador de comparación (a <>) en lugar de cambiar el valor resultante. Tuve que crear datos adicionales para ver el problema.

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 
Cuestiones relacionadas