How to use JOIN with Conditions - mysql

In MySQL on the command-line, I am trying to print the names of teams that have scored more than 3 goals in a single game, whether it was the home team, the away team, or both.
I have two relations:
Team
+---------+-----------+------+----+
| name | shortName | abbr | id |
+---------+-----------+------+----+
Game
+---------+--------------+--------------+------------+------------+
| game_id | home_team_id | away_team_id | score_home | score_away |
+---------+--------------+--------------+------------+------------+
(home_team_id and away_team_id are both foreign keys for Team.id)
I started by trying to find the game_ids where a team had scored more than 3 goals:
> SELECT game_id, score_home, score_away
FROM Game
WHERE score_home > 3 OR score_away > 3;
+---------+------------+------------+
| game_id | score_home | score_away |
+---------+------------+------------+
| 7 | 6 | 2 |
| 35 | 3 | 4 |
| 70 | 4 | 1 |
| 71 | 2 | 5 |
| 84 | 5 | 1 |
| 88 | 6 | 2 |
| 97 | 1 | 5 |
| 103 | 6 | 1 |
+---------+------------+------------+
So I'm pretty sure there should only be about 8 teams at most that have scored more than 3 goals. I then tried INNER JOINS but I'm not quite sure how this works with two different foreign keys and conditions but this was my attempt:
> SELECT Team.name as Team_Name, Game.game_id, Game.score_home, Game.score_away
-> FROM Team
-> INNER JOIN Game ON Team.id=home_team_id OR Team.id=away_team_id
-> WHERE score_home > 3 OR score_away > 3;
+-------------------+---------+------------+------------+
| Team_Name | game_id | score_home | score_away |
+-------------------+---------+------------+------------+
| Arsenal | 71 | 2 | 5 |
| Everton | 7 | 6 | 2 |
| Manchester City | 70 | 4 | 1 |
| Manchester City | 84 | 5 | 1 |
| Manchester City | 103 | 6 | 1 |
| Norwich City | 88 | 6 | 2 |
| Tottenham Hotspur | 70 | 4 | 1 |
| Tottenham Hotspur | 97 | 1 | 5 |
| Newcastle United | 88 | 6 | 2 |
| Newcastle United | 103 | 6 | 1 |
| West Ham United | 35 | 3 | 4 |
| Leicester City | 71 | 2 | 5 |
| Sunderland | 7 | 6 | 2 |
| Bournemouth | 35 | 3 | 4 |
| Bournemouth | 84 | 5 | 1 |
| Bournemouth | 97 | 1 | 5 |
+-------------------+---------+------------+------------+
It's giving me both the home team name and the away team when I only want the team that scored higher than 3 points. Please help.

Get all the team IDs in one column in a subquery using UNION and then join the teams to it.
SELECT t.name
FROM (SELECT g.home_team_id team_id
FROM game g
WHERE score_home > 3
UNION
SELECT g.away_team_id team_id
FROM game g
WHERE score_away > 3) x
INNER JOIN team t
ON t.id = x.team_id;

One approach uses correlated subqueries:
select t.name
from team t
where exists (select 1
from game g
where g.home_team_id = t.id and
g.score_home > 3
) and
exists (select 1
from game g
where g.away_team_id = t.id and
g.score_away > 3
) ;
This query can take advantage of indexes on game(home_team_id, score_home) and game(away_team_id, score_away).

This following query will return Home and Away in separate row if both scored more than 3 goals in a single match-
SELECT
T.Name as [Team_Name],
A.Team_Type,
A.Game_id,
A.Score
FROM
(
SELECT 'Home' AS [Team_Type],game_id AS Game_id,home_team_id as team_ID, score_home as Score WHERE score_home>3
UNION ALL
SELECT 'Away' AS [Team_Type], game_id AS Game_id,away_team_id as team_ID, score_away as Score WHERE score_away>3
)A
INNER JOIN Team T
ON T.id = A.team_ID
ORDER BY A.Game_id

Related

Group by a row and select multiple normalised columns from the same table

I have tried a quick search, but can't find anything that can help (or don't know the correct terminology)
Here's an ERD of my tables:
Here's some sample data from relevant tables:
+------------+----------------+-------+
| sport_id | sport_name | type |
+------------+----------------+-------+
| 1 | 100M | track |
| 2 | 200M | track |
| 3 | Javelin | field |
+---+------------+------------+-------+
+------------+----------------+-------------+
| pupil_id | pupil_name | pupil_class |
+------------+----------------+-------------+
| 1 | John Doe | 1A |
| 2 | Lisa Lawson | 1K |
| 3 | Jamie Wilson | 1F |
+---+------------+------------+-------------+
+----------+----------+----------+-------+
| pupil_id | sport_id | attempt | score |
+----------+----------+----------+-------+
| 1 | 3 | 1 | 20 |
| 1 | 3 | 2 | 30 |
| 1 | 3 | 3 | 40 |
| 2 | 3 | 1 | 22 |
| 2 | 3 | 2 | 32 |
| 2 | 3 | 3 | 33 |
| 3 | 3 | 1 | 23 |
| 3 | 3 | 2 | 33 |
| 3 | 3 | 3 | 43 |
+----------+----------+----------+-------+
What I want to do is end up with the pupil_name and all of their attempts in one row, for a selected sport_id, for example WHERE sport.sport_id = 3:
+--------------+--------+--------+--------+
| pupil_name | score1 | score2 | score3 |
+--------------+--------+--------+--------+
| John Doe | 20 | 30 | 40 |
| Lisa Lawson | 22 | 32 | 42 |
| Jamie Wilson | 23 | 33 | 43 |
+---+------------+------------+-----------+
I have the following query, which merely joins the tables, and I tried using a subquery to get the scores, but I cannot pass the pupil_id into the subquery (hence returns NULL). This also returns the pupil_name three times:
SELECT p.pupil_name,
(SELECT score
FROM result
WHERE attempt = 1
AND pupil_id = p.pupil_id) AS score1
FROM pupil p,
sport s,
result r
WHERE p.pupil_id = r.pupil_id
AND s.sport_id = r.sport_id
AND r.sport_id = 3;
Example result:
+--------------+--------+
| pupil_name | score1 |
+--------------+--------+
| John Doe | NULL |
| John Doe | NULL |
| John Doe | NULL |
+---+------------+------+
How should I approach this? Can I use JOIN?
Solution 1: 3 INNER JOIN's
Here's one solution using 3 simple INNER JOIN's:
SELECT p.pupil_name
,r1.score AS score1
,r2.score AS score2
,r3.score AS score3
FROM pupil p
INNER JOIN result r1
ON p.pupil_id = r1.pupil_id
AND r1.attempt = 1
INNER JOIN result r2
ON p.pupil_id = r2.pupil_id
AND r2.attempt = 1
INNER JOIN result r3
ON p.pupil_id = r3.pupil_id
AND r3.attempt = 1
INNER JOIN sport s
ON r1.sport_id = s.sport_id
AND r2.sport_id = s.sport_id
AND r3.sport_id = s.sport_id
WHERE s.sport_id = 3
;
Solution 2: 1 INNER JOIN and GROUP BY
This solution uses only 1 INNER JOIN to the result table, and then aggregates values in a clever way:
SELECT p.pupil_name
,MAX(CASE WHEN r.attempt = 1 THEN r.score END) AS score1
,MAX(CASE WHEN r.attempt = 2 THEN r.score END) AS score2
,MAX(CASE WHEN r.attempt = 3 THEN r.score END) AS score3
FROM pupil p
INNER JOIN result r
ON p.pupil_id = r.pupil_id
INNER JOIN sport s
ON r.sport_id = s.sport_id
WHERE s.sport_id = 3
GROUP BY p.pupil_name
;

mysql query using inner joins and subquery

These are the tables:
professor:
+-------+--------+--------+--------+------+
| empid | name | status | salary | age |
+-------+--------+--------+--------+------+
| 1 | Arun | 1 | 2000 | 23 |
| 2 | Benoy | 0 | 3000 | 25 |
| 3 | Chacko | 1 | 1000 | 36 |
| 4 | Divin | 0 | 5000 | 32 |
| 5 | Edwin | 1 | 2500 | 55 |
| 7 | George | 0 | 1500 | 46 |
+-------+--------+--------+--------+------+
works:
+----------+-------+---------+
| courseid | empid | classid |
+----------+-------+---------+
| 1 | 1 | 10 |
| 2 | 2 | 9 |
| 3 | 3 | 8 |
| 4 | 4 | 10 |
| 5 | 5 | 9 |
| 6 | 1 | 9 |
| 2 | 3 | 10 |
| 2 | 1 | 7 |
+----------+-------+---------+
course:
+----------+------------+--------+
| courseid | coursename | points |
+----------+------------+--------+
| 1 | Maths | 100 |
| 2 | Science | 80 |
| 3 | English | 85 |
| 4 | Social | 90 |
| 5 | Malayalam | 99 |
| 6 | Arts | 40 |
+----------+------------+--------+
The question is :
Return list of employees who have taught course Maths or Science but
not both
The query which I wrote is :
select distinct professor.name from professor
inner join works
on professor.empid=works.empid
where works.courseid in
(select courseid from course where coursename ='Maths' or coursename='Science');
The output I received is:
Arun
Benoy
Chacko
Here the employee 'Arun' shouldnt have been displayed as he as taught both Maths and Science.
Please help me out !!
You may use an aggregate COUNT() to check that the total number of DISTINCT courses taught is exactly 1, while still filtering to the two different types of courses. That ensures that only one, never both, is returned.
Because the IN () limits all rows initially returned only to the two desired courses, professors can have a maximum of 2 possible different courses via COUNT(DISTINCT coursename). A HAVING clause then prohibits those with 2 from the final result set.
SELECT
DISTINCT professor.name
FROM
professor
INNER JOIN works ON professor.empid = works.empid
/* Join against course to get the course names */
INNER JOIN course ON works.courseid = course.courseid
WHERE
/* Restrict only to Maths, Science */
course.coursename IN ('Maths', 'Science')
GROUP BY professor.name
/* Only those with exactly one type of course */
HAVING COUNT(DISTINCT course.coursename) = 1
Here is a demonstration: http://sqlfiddle.com/#!2/2e9610/2
You want to use an xor here instead of an or.
select distinct professor.name from professor
inner join works
on professor.empid=works.empid
where works.courseid in
(select courseid from course where coursename ='Maths' xor coursename='Science');

How can I SELECT rows from a table when I MAX(ColA) and GROUP BY ColB

I found this question which is very similar but I'm still having some troubles.
So I start with table named Scores
id | player | time | scoreA | scoreB |
~~~|~~~~~~~~|~~~~~~|~~~~~~~~|~~~~~~~~|
1 | John | 10 | 70 | 80 |
2 | Bob | 22 | 75 | 85 |
3 | John | 52 | 55 | 75 |
4 | Ted | 39 | 60 | 90 |
5 | John | 35 | 90 | 90 |
6 | Bob | 27 | 65 | 85 |
7 | John | 33 | 60 | 80 |
I would like to select the best average score for each player along with the information from that record. To clarify, best average score would be the highest value for (scoreA + scoreB)/2.
The results would look like this
id | player | time | scoreA | scoreB | avg_score |
~~~|~~~~~~~~|~~~~~~|~~~~~~~~|~~~~~~~~|~~~~~~~~~~~|
5 | John | 35 | 90 | 90 | 90 |
2 | Bob | 22 | 75 | 85 | 80 |
4 | Ted | 39 | 60 | 90 | 75 |
Based on the question I linked to above, I tried a query like this,
SELECT
s.*,
avg_score
FROM
Scores AS s
INNER JOIN (
SELECT
MAX((scoreA + scoreB)/2) AS avg_score,
player,
id
FROM
Scores
GROUP BY
player
) AS avg_s ON s.id = avg_s.id
ORDER BY
avg_score DESC,
s.time ASC
What this actually gives me is,
id | player | time | scoreA | scoreB | avg_score |
~~~|~~~~~~~~|~~~~~~|~~~~~~~~|~~~~~~~~|~~~~~~~~~~~|
1 | John | 10 | 70 | 80 | 90 |
2 | Bob | 22 | 75 | 85 | 80 |
4 | Ted | 39 | 60 | 90 | 75 |
As you can see, it has gotten the correct max avg_score, from record 5, but gets the rest of the information from another record, record 1. What am I missing? How do I ensure that the data all comes from the same record? I'm getting the correct avg_score but I want the rest of the data associated with that record, record 5 in this case.
Thanks in advance!
SELECT x.*
, (scoreA+scoreB)/2 avg_score
FROM scores x
JOIN
( SELECT player, MAX((scoreA+scoreB)/2) max_avg_score FROM scores GROUP BY player) y
ON y.player = x.player
AND y.max_avg_score = (scoreA+x.scoreB)/2;
Try
SELECT s.*,
q.avg_score
FROM scores s JOIN
(
SELECT player,
MAX((scoreA + scoreB)/2) AS avg_score
FROM scores
GROUP BY player
) q ON s.player = q.player
AND (s.scoreA + s.scoreB)/2 = q.avg_score
ORDER BY q.avg_score DESC, s.time ASC
Sample output:
| ID | PLAYER | TIME | SCOREA | SCOREB | AVG_SCORE |
----------------------------------------------------
| 5 | John | 35 | 90 | 90 | 90 |
| 2 | Bob | 22 | 75 | 85 | 80 |
| 4 | Ted | 39 | 60 | 90 | 75 |
Here is SQLFiddle demo

Select rows with alternate ordered field from another table

Given a *students_exam_rooms* table:
+------------+---------+---------+
| student_id | room_id | seat_no |
+------------+---------+---------+
| 1 | 30 | 1001 |
| 2 | 30 | 1002 |
| 3 | 31 | 2001 |
| 4 | 32 | 2002 |
| 5 | 33 | 3001 |
| 6 | 33 | 3002 |
| 7 | 34 | 4001 |
| 8 | 34 | 4002 |
+------------+---------+---------+
And *students_tbl*:
+------------+-------------+------+
| student_id | studen_name | year |
+------------+-------------+------+
| 1 | Eric | 1 |
| 2 | Mustafa | 1 |
| 3 | Michael | 2 |
| 4 | Andy | 2 |
| 5 | Rafael | 3 |
| 6 | Mark | 3 |
| 7 | Jack | 4 |
| 8 | peter | 4 |
+------------+-------------+------+
How can I select from *students_exam_rooms* ordering by *students_tbl.year* but with one after one like this:
+--------------+------+
| student_name | year |
+--------------+------+
| Eric | 1 |
| Michael | 2 |
| Rafael | 3 |
| Jack | 4 |
| Mustafa | 1 |
| Andy | 2 |
| Mark | 3 |
| Peter | 4 |
+--------------+------+
I'm assuming that you want to order by the "occurrence-count" of the year then the year, e.g. all the first-occurrences of all years first, sorted by year, then all second-occurrences of all years also sorted by year, and so on. That would be a perfect case for emulating other RDBMS' analytic / windowing functions:
select *
from (
select
s.studen_name,
s.year,
ser.*,
(
select 1 + count(*)
from students_tbl s2
where s.year = s2.year
and s.student_id > s2.student_id
) rank
from students_tbl s
JOIN students_exam_rooms ser
ON s.student_id = ser.student_id
) i_dont_really_want_to_name_this
order by rank, year
Here it is against a slightly tweaked version of JW's fiddle: http://www.sqlfiddle.com/#!2/27c91/1
Emulating Analytic (AKA Ranking) Functions with MySQL is a good article that gives more background and explanation.
try any of these below:
SELECT a.studen_name, a.year
FROM students_tbl a
INNER JOIN students_exam_rooms b
ON a.student_id = b.student_id
ORDER BY REVERSE(b.seat_no),
a.year
SQLFiddle Demo
by using Modulo
SELECT a.studen_name, a.year
FROM students_tbl a
INNER JOIN students_exam_rooms b
ON a.student_id = b.student_id
ORDER BY CASE WHEN MOD(b.seat_no, 2) <> 0 THEN 0 ELSE 1 END,
a.year
SQLFiddle Demo
Looks to me like you're trying to sort first by seat and then by year. Looking at your students_exam_rooms table, it looks like you started with a simple seat number and prepended year * 1000. So, if we omit the year, it looks like this:
> select * from fixed_students_exam_rooms;
+------------+---------+---------+
| student_id | room_id | seat_no |
+------------+---------+---------+
| 1 | 30 | 1 |
| 2 | 30 | 2 |
| 3 | 31 | 1 |
| 4 | 32 | 2 |
| 5 | 33 | 1 |
| 6 | 33 | 2 |
| 7 | 34 | 1 |
| 8 | 34 | 2 |
+------------+---------+---------+
And if you had that table, your query is simple:
select
student_name, year
from
modified_student_exame_rooms
left join students_tbl using (student_id)
order by
seat_no, year
;
Using the table as you currently have it, it's only slightly more complicated, assuming the "core seat number" doesn't excede 999.
select
student_name, year
from
modified_student_exame_rooms
left join students_tbl using (student_id)
order by
convert(substr(seat_no, 2), unsigned),
year
;

Complex 3 way SQL join (where table 3 has to join table 2 before joining table 1)

I have three existing SQL tables we will call "teams", "miles", and "riders". Leaving out the fluff, their structure looks like this:
Table: teams
------------+-------------+---------+
| team_name | captains_id | team_id |
------------+-------------+---------+
| superbads | 11 | 1 |
| superflys | 12 | 2 |
------------+-------------+---------+
Table: riders
--------------+-----------+----------+
| rider_name | team_id | rider_id |
--------------+-----------+----------+
| donatello | 1 | 10 |
| leonardo | 1 | 11 |
| michelangelo| 2 | 12 |
| raphael | 2 | 13 |
--------------+-----------+----------+
Table: miles
--------------+-----------+----------+
| rider_id | miles | id |
--------------+-----------+----------+
| 10 | 100 | 1 |
| 10 | 62 | 2 |
| 11 | 110 | 3 |
| 11 | 100 | 4 |
| 12 | 8 | 5 |
| 12 | 22 | 6 |
| 13 | 29 | 7 |
| 13 | 2 | 8 |
--------------+-----------+----------+
I need to return a list of teams with total miles generated by that team (I also need to return the team captain's name, but that's a bit easier).
The difficulty is that I need to join miles on riders, sum the "miles" field, and then join that on teams somehow.
Changing the table structure is pretty much out, as this is an existing application. This is a LAMP environment, so manipulating PHP arrays after the query is an option if needed.
This should do it:
select t.team_id, t.team_name, t.captains_id, sum(m.miles) as total_miles
from teams t
inner join riders r on r.team_id = t.team_id
inner join miles m on m.rider_id = r.rider_id
group by t.team_id, t.team_name, t.captains_id