I'm looking for a better way to do the following query. I have a table that looks like this:
game_id | home_team_id | away_team_id
1 | 100 | 200
2 | 200 | 300
3 | 200 | 400
4 | 300 | 100
5 | 100 | 400
And I want to write a query that counts the number of home games and away games for each team and outputs the following:
team_id | home_games | away_games
100 | 2 | 1
200 | 2 | 1
300 | 1 | 1
400 | 0 | 2
Right now, I wrote this monstrosity that works, but it's slow (I know it's pulling the entire 2,800 row from the table twice).
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
Can a SQL guru help me knock out a better way? I think my problem is that I don't understand how to get a distinct list of the team IDs to throw at the count queries. I bet there's a better way with a better placed, nested SELECT. Thanks in advance!
If you want the distinct list of teams, you have to select from the game table twice, unioning the home and the away teams (theoretically, one team could play all its games on the road or at home, if you have logic that prevents that, then you could adjust this query):
The
union
operator will make sure you only get distinct elements in the return set (unless you useunion all
)From there, you can use left outer joins to aggregate your data:
If you want to reduce your table scans even further (the above will produce four), you can add more code, but it will cost you. You can get a list of rows with the team_id, and whether or not the game was played at home or away:
From there, you can simply sum up the games at home and away for each team:
This will result in a single table scan.
Greg,
I think your ultimate solution will be language-specific. But if you were doing this in Oracle, you could query the table only once with the following:
You don't say what flavor of SQL you're using so this is the best I can do.
Best of luck,
Stew
p.s. It looks like I've given the same solution as MarlonRibunal. I just didn't have a handy link so had to create the code by hand. :-/
This solution is rather ugly, but it should work quickly across large datasets:
It's cleaner if you have another table team with team_id and team_name.
What's going on: the no WHERE clause causes a Cartesian Product between the two tables; we group by team_id to get back to one row per team. Now there are all the rows from the game table for each team_id so you need to count them but the SQL count function isn't quite right (it would count all the rows or all the distinct rows). So we say team_id = home_team_id which resolves to 1 or 0 and we use sum to add up the 1's.
The team_name is just because it's geeky to say that 'team 200 had 20 home games' when we ought to say that 'Mud City Stranglers had 20 home games'.
PS. this will work even if there are no games (often a problem in SQL where there is a team with 0 games and that row will not show up because the join fails).
Sorry, my mistake in the away_games clause. I changed the comparison operator (to <>) instead of changing the resulting value. I had to create additional data to see the problem.
Try this: