'How do i crack this SQL Soccer Matches assignment?

I came across this problem recently, i'm a sql newbie preparing for an interview and i need some help understanding how to connect the tables without using joins (since there is no common column)to get the desired result

create table teams
(team_id integer not null,
team_name varchar(30) not null,
unique(team_id)
);

Team_id .  Team_name
10 .       Give
20 .       Never
30 .       You
40 .       up
50 .       Gonna



create table matches 
(match_id integer not null,
host_team integer not null,
guest_team integer not null,
host_goals integer not null,
guest_goals integer not null,
unique(match_id)
);


Match_id   Host_team  Guest_team   Host_goals  Guest_goals
1 .        30 .       20 .         1 .         0 
2 .        10 .       20 .         1 .         2
3 .        20 .       50 .         2 .         2
4 .        10 .       30 .         1 .         0
5 .        30 .       50 .         0 .         1

Compute the total number of points each team has scored after all the matches the rules are as follows

if a team wins a match(scores more goals than the other team) it gets 3 points.

if a team draws a match(scores exactly same number of goals as other team) it gets one point

if a team loses a match(scores fewer goals than other team) it gets no points.

write a query that returns a ranking of all teams(team_id) described in the table teams. for each team provide its name and number of points it received after all the matches(num_points). The table should be ordered by num_points in desc order. in case of a tie order the rows by team_id

Saw a solution that uses union all but it doesn't provide the answer, tried modifying it myself too.

select a.team_id,a.team_name,3*count(1) from teams a, matches b
where a.team_id = b.host_team
and b.host_goals > b.guest_goals
group by a.team_id,a.team_name

union all

select a.team_id,a.team_name,count(1) from teams a, matches b
where a.team_id = b.host_team
and b.host_goals = b.guest_goals
group by a.team_id,a.team_name

union all

select a.team_id,a.team_name,3*count(1) from teams a, matches b
where a.team_id = b.guest_team
and b.host_goals < b.guest_goals
group by a.team_id,a.team_name

union all

select a.team_id,a.team_name,count(1) from teams a, matches b
where a.team_id = b.guest_team
and b.host_goals = b.guest_goals
group by a.team_id,a.team_name;






Solution should look like
team_id    team_name    num_points
20         Never        4
50         Gonna        4
10         Give         3
30         You          3
40         Up           0


Solution 1:[1]

I suggest the following:

WITH cteHostPoints AS (SELECT HOST_TEAM AS TEAM,
                              CASE
                                WHEN HOST_GOALS > GUEST_GOALS THEN 3
                                WHEN HOST_GOALS = GUEST_GOALS THEN 1
                                ELSE 0
                              END AS POINTS
                         FROM MATCHES),
     cteGuestPoints AS (SELECT GUEST_TEAM AS TEAM,
                               CASE
                                 WHEN GUEST_GOALS > HOST_GOALS THEN 3
                                 WHEN GUEST_GOALS = HOST_GOALS THEN 1
                                 ELSE 0
                               END AS POINTS
                          FROM MATCHES),
     cteAllPoints AS (SELECT TEAM, POINTS FROM cteHostPoints
                      UNION ALL
                      SELECT TEAM, POINTS FROM cteGuestPoints)
SELECT t.TEAM_ID, t.TEAM_NAME, COALESCE(SUM(ap.POINTS), 0) AS TOTAL_POINTS
  FROM TEAMS t
  LEFT OUTER JOIN cteAllPoints ap
    ON ap.TEAM = t.TEAM_ID
  GROUP BY t.TEAM_ID, t.TEAM_NAME
  ORDER BY COALESCE(SUM(POINTS), 0) DESC, t.TEAM_ID

dbfiddle here

Solution 2:[2]

Tried this with MySQL using just a bunch of case statements.

    select team_id, team_name, 
coalesce(sum(case when team_id = host_team then 
    (
    case when host_goals > guest_goals then 3
    when host_goals = guest_goals then 1
    when host_goals < guest_goals then 0
    end
    ) 
when team_id = guest_team then
    (
    case when guest_goals > host_goals then 3
    when guest_goals = host_goals then 1
    when guest_goals < host_goals then 0
    end
    )
    end), 0) as num_points
from Teams
left join Matches
on 
Teams.team_id = Matches.host_team
or Teams.team_id = Matches.guest_team
group by team_id, team_name
order by num_points desc, team_id;

Solution 3:[3]

Here goes my suggestion

WITH points_per_game AS(
SELECT 
    t.team_id as team_id,
    t.team_name as team_name,
    CASE
        WHEN t.team_id = m.host_team
            AND host_goals > guest_goals 
        THEN 3
        WHEN t.team_id = m.host_team 
            AND host_goals < guest_goals 
        THEN 0 
        WHEN t.team_id = guest_team
            AND guest_goals > host_goals
        THEN 3 
        WHEN t.team_id = guest_team
            AND guest_goals < host_goals
        THEN 0
        WHEN guest_goals IS NULL
        THEN 0
        ELSE 1
    END as game_points
FROM teams t
LEFT JOIN matches m ON t.team_id = m.host_team 
    OR t.team_id = m.guest_team
) 
SELECT team_id, team_name, SUM(game_points) 
FROM points_per_game
GROUP BY 1,2
ORDER BY SUM(game_points) DESC, team_id ASC

Solution 4:[4]

MySql Query.

select a.team_id, b.team_name, a.num_points
from (
select a.team_id
  , sum(a.wins_point) as sum_wins_point
  , sum(a.draws_point) as sum_draws_point
  , sum(a.wins_point + a.draws_point) as num_points
from (
select b.team_type
  , a.match_id
  , (
    case when b.team_type = 'host' then a.host_team else a.guest_team end 
  ) as team_id
  , (
    case when b.team_type = 'host' then
      case when a.host_goals > a.guest_goals then 3 else 0 end
    else 
      case when a.host_goals < a.guest_goals then 3 else 0 end
    end 
  ) as wins_point
  , (
    case when b.team_type = 'host' then
      case when a.host_goals = a.guest_goals then 1 else 0 end
    else 
      case when a.host_goals = a.guest_goals then 1 else 0 end
    end 
  ) as draws_point
from matches a
cross join (
  select 'host' as team_type 
  union all
  select 'guest' as team_type 
) b
) a
group by a.team_id
) a
inner join teams b on a.team_id = b.team_id

Solution 5:[5]

You've been Rick-Roll'd

create table #teams
(team_id integer not null,
team_name varchar(30) not null,
unique(team_id)
);

insert #teams (
   team_id
 , team_name
)
values
(  10 , 'Give'),
(  20 , 'Never'),
(  30 ,  'You' ),
(  40 ,  'up'  ),
(  50 ,  'Gonna');



create table #matches 
(match_id integer not null,
host_team integer not null,
guest_team integer not null,
host_goals integer not null,
guest_goals integer not null,
unique(match_id)
);

insert #matches (
   match_id
 , host_team
 , guest_team
 , host_goals
 , guest_goals
)
values
(  1 , 30 , 20 , 1 , 0 ),
(  2 , 10 , 20 , 1 , 2 ),
(  3 , 20 , 50 , 2 , 2 ),
(  4 , 10 , 30 , 1 , 0 ),
(  5 , 30 , 50 , 0 , 1 );

with
      cte_1 as (
         select match_id
            ,  host_team as team_id
            ,  host_goals as score
            ,  case
                  when host_goals > guest_goals then 3
                  when host_goals = guest_goals then 1
                  else 0
               end as points
            from #matches 
         union all
         select match_id
            ,  guest_team as team_id
            ,  guest_goals as score
            ,  case
                  when guest_goals > host_goals then 3
                  when host_goals = guest_goals then 1
                  else 0
               end as points
            from #matches)
   ,  cte_2 as (
         select 
            team_id,
            sum(points) as points
            from cte_1
            group by cte_1.team_id
      )
select * 
   from #teams t
   left outer join cte_2 p on t.team_id = p.team_id
   order by p.points desc


drop table #matches
drop table #teams```

Solution 6:[6]

  1. Normalize the matches data from "1 row has 2 columns" to "2 rows have 1 column".
    • More precisely: "1 composite attribute" instead of "1 column", as we're treating the ( Host_team, Host_goals ) tuple as a single composite attribute.
    • This is done using a UNION. This could also be done by an UNPIVOT if your RDBMS supports it.
  2. Compute scores based on who won the match.
  3. Aggregate that data by team.
  4. Finally, JOIN with teams data at the very end, using COALESCE to provide a score of zero for teams that haven't played any games.

Here's step 1:

SELECT
    match_id,
    'host'     AS side,
    host_team  AS team_id,
    host_goals AS goals,
    CASE WHEN host_goals > guest_goals THEN 1 WHEN host_goals = guest_goals THEN 0 ELSE -1 END AS victory
FROM
    matches

UNION ALL

SELECT
    match_id,
    'guest'     AS side,
    guest_team  AS team_id,
    guest_goals AS goals,
    CASE WHEN guest_goals > host_goals THEN 1 WHEN guest_goals = host_goals THEN 0 ELSE -1 END AS victory
FROM
    matches

Here's step 2:

SELECT
    match_id,
    side,
    sq.team_id,
    CASE victory WHEN 1 THEN ( 3 ) WHEN 0 THEN ( 1 ) ELSE ( 0 ) END AS points
FROM
    (
        SELECT
            match_id,
            'host'     AS side,
            host_team  AS team_id,
            host_goals AS goals,
            CASE WHEN host_goals > guest_goals THEN 1 WHEN host_goals = guest_goals THEN 0 ELSE -1 END AS victory
        FROM
            matches

        UNION ALL

        SELECT
            match_id,
            'guest'     AS side,
            guest_team  AS team_id,
            guest_goals AS goals,
            CASE WHEN guest_goals > host_goals THEN 1 WHEN guest_goals = host_goals THEN 0 ELSE -1 END AS victory
        FROM
            matches
    ) AS sq

Here's step 3:

SELECT
    sq.team_id,
    SUM( CASE victory WHEN 1 THEN ( 3 ) WHEN 0 THEN ( 1 ) ELSE ( 0 ) END ) AS sumPoints
FROM
    (
        SELECT
            match_id,
            'host'     AS side,
            host_team  AS team_id,
            host_goals AS goals,
            CASE WHEN host_goals > guest_goals THEN 1 WHEN host_goals = guest_goals THEN 0 ELSE -1 END AS victory
        FROM
            matches

        UNION ALL

        SELECT
            match_id,
            'guest'     AS side,
            guest_team  AS team_id,
            guest_goals AS goals,
            CASE WHEN guest_goals > host_goals THEN 1 WHEN guest_goals = host_goals THEN 0 ELSE -1 END AS victory
        FROM
            matches
    ) AS sq
GROUP BY
    sq.team_id

And step 4:

SELECT
    teams.team_id,
    team_name,
    COALESCE( num_points, 0 ) AS num_points
FROM
    teams
    LEFT OUTER JOIN
    (
        SELECT
            sq.team_id,
            SUM( CASE victory WHEN 1 THEN ( 3 ) WHEN 0 THEN ( 1 ) ELSE ( 0 ) END ) AS num_points
        FROM
            (
                SELECT
                    match_id,
                    'host'     AS side,
                    host_team  AS team_id,
                    host_goals AS goals,
                    CASE WHEN host_goals > guest_goals THEN 1 WHEN host_goals = guest_goals THEN 0 ELSE -1 END AS victory
                FROM
                    matches

                UNION ALL

                SELECT
                    match_id,
                    'guest'     AS side,
                    guest_team  AS team_id,
                    guest_goals AS goals,
                    CASE WHEN guest_goals > host_goals THEN 1 WHEN guest_goals = host_goals THEN 0 ELSE -1 END AS victory
                FROM
                    matches
            ) AS sq
        GROUP BY
            sq.team_id
    ) AS agg ON teams.team_id = agg.team_id
ORDER BY
    num_points DESC

Step 5:

With the full query structure, we can simplify it by moving common expressions like victory to outer queries to reduce syntactical repetition:

SELECT
    teams.team_id,
    team_name,
    COALESCE( num_points, 0 ) AS num_points
FROM
    teams
    LEFT OUTER JOIN
    (
        SELECT
            sq.team_id,
            SUM(
                CASE
                    WHEN goals > rival_goals THEN 3
                    WHEN goals = rival_goals THEN 1 ELSE 0
                END
            ) AS num_points
        FROM
            (
                SELECT
                    match_id,
                    'host'      AS side,
                    host_team   AS team_id,
                    host_goals  AS goals,
                    guest_goals AS rival_goals
                FROM
                    matches

                UNION ALL

                SELECT
                    match_id,
                    'guest'     AS side,
                    guest_team  AS team_id,
                    guest_goals AS goals,
                    host_goals  AS rival_goals
                FROM
                    matches
            ) AS sq
        GROUP BY
            sq.team_id
    ) AS agg ON teams.team_id = agg.team_id
ORDER BY
    num_points DESC

The outer query can be combiend with the agg inner-query - though it does make the query harder to follow - and I'm not a fan of adding additional GROUP BY columns just to avoid an outer-query. Also we can remove the columns added to make it easier to understand the intermediate data (like side and match_id):

SELECT
    sq.team_id,
    teams.team_name,
    COALESCE(
        SUM(
            CASE
                WHEN goals > rival_goals THEN 3
                WHEN goals = rival_goals THEN 1 ELSE 0
            END
        ),
        0
    ) AS num_points
FROM
    teams
    LEFT OUTER JOIN
    (
        SELECT
            host_team   AS team_id,
            host_goals  AS goals,
            guest_goals AS rival_goals
        FROM
            matches

        UNION ALL

        SELECT
            guest_team  AS team_id,
            guest_goals AS goals,
            host_goals  AS rival_goals
        FROM
            matches
    ) AS sq ON teams.team_id = sq.team_id
GROUP BY
    sq.team_id,
    teams.team_name -- This additional GROUP BY column is necessary to avoid having to use an outer-query
ORDER BY
    num_points DESC

Solution 7:[7]

You can try the query below:

SELECT
    T.team_id,
    T.Team_name,
    IFNULL( h_points, 0 ) + IFNULL( g_points, 0 ) AS 'Total Points' 
FROM
    teams T 
    LEFT JOIN
    --- first sub-query calculating host_team points
    (
        SELECT
            host_team,
            SUM(
                CASE
                    WHEN Host_goals > Guest_goals THEN 3
                    WHEN Host_goals = Guest_goals THEN 1
                    WHEN Host_goals < Guest_goals THEN 0
                END
            ) AS h_points 
        FROM
            matches 
        GROUP BY
            host_team
    ) H ON T.Team_id=H.host_team 
    LEFT JOIN
    --- second sub-query calculating guest_team points
    (
        SELECT
            guest_team,
            SUM(
                CASE
                    WHEN Host_goals < Guest_goals THEN 3
                    WHEN Host_goals = Guest_goals THEN 1
                    WHEN Host_goals > Guest_goals THEN 0
                END
            ) AS g_points 
        FROM
            matches 
        GROUP BY
            guest_team
    ) G ON T.Team_id=G.guest_team 
ORDER BY
    IFNULL( h_points, 0 ) + IFNULL( g_points, 0 ) DESC, t.team_id ASC;;

I took the approach of calculating host and guest points separately then being in a LEFT JOIN from teams table. On the outer query, I perform this formula IFNULL( h_points, 0 ) + IFNULL( g_points, 0 ) to add up the host+guest teams points. The IFNULL is to replace any NULL value with 0 and the same formula I use for the ORDER BY ... DESCENDING

Solution 8:[8]

Here is a relatively simple solution using common table expression.

Step 1 - Create a CTE that identifies a winner and calculate winning team score, in second part it identifies losing team and losing score. Only reason to do this as we have to give each team 1 point in case of draw. If you run the table within the CTE it gives a temp result set like following:

match_id, host_team, guest_team, winning team, winner_score, losing_team, losing score

+---+----+----+----+---+----+---+
| 1 | 30 | 20 | 30 | 3 | 20 | 0 |
| 2 | 10 | 20 | 20 | 3 | 10 | 0 |
| 3 | 20 | 50 | 20 | 1 | 50 | 1 |
| 4 | 10 | 30 | 10 | 3 | 30 | 0 |
| 5 | 30 | 50 | 50 | 3 | 30 | 0 |
+---+----+----+----+---+----+---+ 

Step 2: CTE helps to organize this data into in different rows.. first query the winning team id, and winner score, and then union with losing team id and losing score.

+----+---+
| 50 | 3 |
| 20 | 3 |
| 50 | 1 |
| 30 | 3 |
| 10 | 3 |
| 20 | 1 |
+----+---+ 

Step 3: Fetch the outcome from second CTE, join with team table and group by on Team_id, and Team_name. Since there is one team that didn't play any game it score was coming up as null and to avoid sorting issue (and perfectly match with the expected results) used COALESCE function. Results looks like following:

+----+-------+---+
| 20 | Never | 4 |
| 50 | Gonna | 4 |
| 10 |  Give | 3 |
| 30 |   You | 3 |
| 40 |    Up | 0 |
+----+-------+---+ 
with score as 
(
SELECT match_id, host_team, guest_team, 
            cast (
                case
                    when host_goals > guest_goals then host_team
                    when host_goals = guest_goals then host_team
                    else guest_team
                 end as integer   
                    ) as winner,
            cast (
                case
                    when host_goals > guest_goals then 3
                    when host_goals = guest_goals then 1
                    else 3
                 end as integer   
                    ) as winner_score,
            cast (
                case
                    when host_goals < guest_goals then host_team
                    when host_goals = guest_goals then guest_team
                    else guest_team
                 end as integer   
                    ) as loser,
            cast (
                case
                    when host_goals < guest_goals then 0
                    when host_goals = guest_goals then 1
                    else 0
                 end as integer   
                    ) as loser_score        
    from matches
),

 flat_table as(
select winner as team_id,  winner_score as final_score from score
union
select loser as team_id, loser_score as final_score from score where loser_score > 0
)

select t.team_id, t.team_name, 
        sum(COALESCE(f.final_score, 0)) as total_score
from teams t
left join flat_table f on t.team_id = f.team_id
group by t.team_id, t.team_name
order by total_score desc, team_id asc

Solution 9:[9]

Here is my solution in sqlite:

step 1: Get the points for any given team (i.e. team_id = 40).

select ifnull(sum(CASE 
     WHEN 
        (host_team = 40 and host_goals > guest_goals) or (guest_team = 40 and guest_goals > host_goals)
        THEN 3
    WHEN host_goals = guest_goals
        THEN 1
    ELSE 0 
    END), 0) as points
from matches
where host_team = 40 or guest_team = 40;

step 2: use the previous query to calculate the scores of all teams

select team_id, team_name, (
select ifnull(sum(CASE 
     WHEN 
        (host_team = team.team_id and host_goals > guest_goals) or (guest_team = team.team_id and guest_goals > host_goals)
        THEN 3
    WHEN host_goals = guest_goals
        THEN 1
    ELSE 0 
    END), 0) as points
from matches
where host_team = team.team_id or guest_team = team.team_id
) as points
from teams team
order by points desc, team_id asc;

Solution 10:[10]

My SQL query is as follows:

WITH points AS (
SELECT host_name, SUM(num_points) AS num_points
FROM(
      SELECT host_name, 
             SUM(CASE
                   WHEN host_goals>guest_goals THEN 3
                   WHEN host_goals=guest_goals THEN 1
                 ELSE 0
                 END) AS num_points
      FROM matches
      GROUP BY host_name
      
      UNION
      
      SELECT guest_name, 
             SUM(CASE
                   WHEN guest_goals>host_goals THEN 3
                   WHEN host_goals=guest_goals THEN 1
                 ELSE 0
                 END) AS num_points
      FROM matches
      GROUP BY guest_name) 
GROUP BY host_name
)

SELECT t.team_id, t.team_name, 
       CASE 
          WHEN p.num_points IS NOT null THEN p.num_points
          ELSE 0
       END AS num_points
FROM teams t FULL JOIN points p 
             ON t.team_id=p.host_name
ORDER BY p.num_points DESC

Solution 11:[11]

IN SQL SERVER

  SELECT  team_id,team_name,CASE WHEN host_goals>guest_goals
  THEN 3
  WHEN host_goals=guest_goals 
  THEN 1
  ELSE 0 END as Points INTO #X
  FROM teams 
  LEFT join matches ON host_team = team_id
  UNION ALL
  SELECT  team_id,team_name,CASE WHEN guest_goals>host_goals
  THEN 3
  WHEN guest_goals=host_goals 
  THEN 1
  WHEN host_goals>guest_goals 
  THEN 0 
  ELSE 0 END as Points from teams 
  LEFT join matches  on guest_team = team_id;

  SELECT  team_id,team_name,SUM(Points) AS num_points FROM #X
  GROUP BY team_id,team_name
  ORDER BY SUM(Points) DESC,team_id ASC

Solution 12:[12]

MySQL:

select teams.team_id,
teams.team_name,
ifnull(sum(team_points.points), 0) num_points

from teams 
left join (
select host_matches.host_team team_id, 
    case 
    when host_matches.host_goals>host_matches.guest_goals then 3 
    when host_matches.host_goals = host_matches.guest_goals then 1
    else 0 end points
from matches host_matches

union all

select guest_matches.guest_team team_id,
    case 
    when guest_matches.host_goals<guest_matches.guest_goals then 3 
    when guest_matches.host_goals = guest_matches.guest_goals then 1
    else 0 end
from matches guest_matches
) team_points on team_points.team_id = teams.team_id

group by teams.team_id, teams.team_name
order by num_points desc, team_id asc;