Solution to #13 of SQL Join in SQLZoo - mysql

The link is http://sqlzoo.net/wiki/The_JOIN_operation. Question No.13.
Now I can list all the matches as long as there is at least one goal with the code:
SELECT mdate,
team1,
SUM(CASE WHEN teamid = team1 THEN 1 ELSE 0 END) AS score1,
team2,
SUM(CASE WHEN teamid = team2 THEN 1 ELSE 0 END) AS score2 FROM
game JOIN goal ON (id = matchid)
GROUP BY id
ORDER BY mdate, matchid, team1, team2
However there are some games the score of which is 0:0. My code can't display these games, and can't find other available solutions. Really hope someone can help me with this problem.

SELECT mdate,
team1,
SUM(CASE WHEN teamid = team1 THEN 1 ELSE 0 END) AS score1,
team2,
SUM(CASE WHEN teamid = team2 THEN 1 ELSE 0 END) AS score2 FROM
game LEFT JOIN goal ON (id = matchid)
GROUP BY mdate,team1,team2
ORDER BY mdate, matchid, team1, team2
You want to GROUP BY columns without aggregate functions in the SELECT and use a LEFT JOIN.

Try LEFT JOIN insted of JOIN. Because you need to show all games even if there are no goals.

SELECT G.mDate
,G.Team1
,ISNULL( (SELECT COUNT(*) FROM goal WHERE teamid = G.Team1), 0) AS Team1_Goals
,G.Team2
,ISNULL( (SELECT COUNT(*) FROM goal WHERE teamid = G.Team2), 0) AS Team2_Goals
FROM Game G
WHERE EXISTS (SELECT 1
FROM goal
WHERE matchid = G.matchid)

If you are using MSSQL engine remove the ORDER BY clause from the accepted answer

Related

Why are these JOIN statements affecting my order clause in different ways?

I'm familiarizing myself with SQL on sqlzoo, and the final question (#13) was giving issues until I successfully guessed the solution. I initially suspected I had written my ORDER BY clause wrong or that improper data type was to blame, but then I looked up a solution provided by mjsqu here. I only changed the JOIN statement and removed the ORDER BY clause to match their code. Frankly, I still don't understand why the ORDER BY statement was failing to sort the date in the first place.
See here for data and problem set: https://sqlzoo.net/wiki/The_JOIN_operation
SELECT mdate,
team1,
SUM(CASE WHEN teamid=team1 THEN 1 ELSE 0 END) score1,
team2,
SUM(CASE WHEN teamid=team2 THEN 1 ELSE 0 END) score2
FROM game JOIN goal ON matchid = id
GROUP BY mdate, matchid, team1, team2
ORDER BY mdate, matchid, team1, team2
Solution code:
SELECT mdate,
team1,
SUM(CASE WHEN teamid = team1 THEN 1 ELSE 0 END) score1,
team2,
SUM(CASE WHEN teamid = team2 THEN 1 ELSE 0 END) score2
FROM game LEFT JOIN goal ON matchid = id
GROUP BY mdate, matchid, team1, team2
The difference between the two queries is for games where there are no goals. INNER JOIN won't return any rows for those games. LEFT JOIN will return a row for those games, but all the columns from the goals table will be NULL.
Since you're grouping and ordering by goal.matchid, this will group all those games with no goals together when the other grouping columns are the same. You should use game.id instead of goal.matchid in both GROUP BY and ORDER BY.
SELECT mdate,
team1,
SUM(CASE WHEN teamid = team1 THEN 1 ELSE 0 END) score1,
team2,
SUM(CASE WHEN teamid = team2 THEN 1 ELSE 0 END) score2
FROM game LEFT JOIN goal ON matchid = id
GROUP BY mdate, id, team1, team2
ORDER BY mdate, id, team1, team2

Count Group By and Separate If Included in Both Group

Not sure if this question is duplicated yet or not.
I have a simplified table below
User
Interest
Jason
Art
Jason
Sport
Sam
Sport
Sam
Art
Steve
Sport
Desmond
Sport
Tania
Art
Here's the result that I want to achieve
Interest
Count
Art
2
Sport
2
Both
2
I Managed to make a subquery to achieve the value for the Both data by this query
SELECT COUNT(USER) FROM (
SELECT User, COUNT(DISTINCT Interest) as interest_type FROM table WHERE interest_type = 2)
But for the user that are exclusively have Interest in Art and in Sport it's not separated.
You could use conditional aggregation here:
WITH cte AS (
SELECT User,
CASE WHEN COUNT(CASE WHEN Interest = 'Art' THEN 1 END) > 0 AND
COUNT(CASE WHEN Interest = 'Sport' THEN 1 END) > 0
THEN 'Both'
WHEN COUNT(CASE WHEN Interest = 'Art' THEN 1 END) > 0
THEN 'Art'
ELSE 'Sport' END AS Interest
FROM yourTable
GROUP BY User
)
SELECT Interest, COUNT(*) AS Count
FROM cte
GROUP BY Interest;
On MySQL or BigQuery, we can shorten the above to:
WITH cte AS (
SELECT User,
CASE WHEN SUM(Interest = 'Art') > 0 AND SUM (Interest = 'Sport') > 0
THEN 'Both'
WHEN SUM(Interest = 'Art') > 0
THEN 'Art'
ELSE 'Sport' END AS Interest
FROM yourTable
GROUP BY User
)
SELECT Interest, COUNT(*) AS Count
FROM cte
GROUP BY Interest;
Assuming your database supports the over() clause:
select
case when num_interests = 1 then interest else 'both' end as interest
, count(distinct user) as "Count"
from (
select
interest
, user
, count(*) over(partition by user) as num_interests
from yourTable
) d
group by
case when num_interests = 1 then interest else 'both' end

Join 2 tables and display duplicated row value into new column

I've been spending 2 days working on this and cannot find anything help.
I have 2 tables.
1. players
id
name
1
AA
2
BB
3
CC
4
DD
2. matching
id
player_id
match_id
date
has_opponent
1
1
1
2021-06-23
0
2
2
1
2021-06-23
0
3
3
2
2021-06-21
0
4
4
3
2021-06-22
1
Expected Result
match_id
date
first_opponent_name
second_opponent_name
1
2021-06-23
AA
BB
2
2021-06-21
CC
3
2021-06-22
DD
DD
I want to join these 2 tables with new columns ( first_opponent_name & second_opponent_name )
if it meets condition as below:
If there is a duplicated match_id but it has different player_id, then display their names in 2 separated columns.
If there is only 1 match_id and it has 1 player_id and has_opponent column is 0, then this should display only first_opponent_name
If there is only 1 match_id and it has 1 player_id and has_opponent column is 1, then first_opponent_name and second_opponent _name should be displayed as the same value.
Not tested but I think this will work, basically it joins the table to itself on the different scenarios and then checks which scenario it's matched.
SELECT
m.match_id
,m.date
,p1.name AS first_opponent_name
,CASE WHEN p2.name IS NOT NULL THEN p2.name
WHEN nop2.match_id IS NOT NULL THEN ''
WHEN samep2.match_id IS NOT NULL THEN p1.name END AS second_opponent_name
FROM
matching AS m
INNER JOIN players AS p1
ON m.player_id = p1.player_id
LEFT JOIN matching AS hasp2
ON m.match_id = hasp2.match_id
AND m.player_id <> hasp2.player_id
LEFT JOIN players AS p2
ON hasp2.player_id = p2.player_id
LEFT JOIN (
SELECT
match_id
FROM
matching
WHERE
has_opponent = 0
GROUP BY
match_id
HAVING COUNT(*) = 1
) AS nop2
ON m.match_id = nop2.match_id
LEFT JOIN (
SELECT
match_id
FROM
matching
WHERE
has_opponent = 1
) AS samep2
ON m.match_id = samep2.match_id
You could arguably leave off either the hasp2 or samep2 join and just assume that if the other two scenarios are not met, depends on how well you trust the data to know if that's a viable option.
WITH cte AS ( SELECT DISTINCT
match_id,
`date`,
MIN(player_id) OVER (PARTITION BY match_id) p1,
MAX(player_id) OVER (PARTITION BY match_id) p2,
has_opponent
FROM matching )
SELECT match_id,
`date`,
p1.name first_opponent_name,
CASE WHEN has_opponent OR (p1 != p2) THEN p2.name END second_opponent_name
FROM cte
JOIN players p1 ON cte.p1 = p1.id
JOIN players p2 ON cte.p2 = p2.id
ORDER BY 1
If CTE and window functions are not supported then
SELECT match_id,
`date`,
p1.name first_opponent_name,
CASE WHEN has_opponent OR (p1 != p2) THEN p2.name END second_opponent_name
FROM ( SELECT match_id,
`date`,
MIN(player_id) p1,
MAX(player_id) p2,
has_opponent
FROM matching
GROUP BY match_id,
`date`,
has_opponent ) cte
JOIN players p1 ON cte.p1 = p1.id
JOIN players p2 ON cte.p2 = p2.id
ORDER BY 1
https://dbfiddle.uk/?rdbms=mysql_8.0&rdbms2=mysql_5.7&fiddle=4b947a2e7d31d50dfc1242b194b204fe
The query assumes that the same match_id strictly matches the same date and match_id.
The only tricky part is dealing with multiple players which is well-suited to using window functions and row_number(); Then it's just a case of aggregating these with a conditional case:
select match_Id, date,
Max(case when cnt=1
then name
else
case when rn=1 then name end
end) first_opponent_name,
Max(case when cnt=1 then
case when has_opponent=1 then name else '' end
else
case when rn=2 then name end
end) second_opponent_name
from (
select m.match_id, m.date, m.has_opponent, p.name,
Row_Number() over(partition by match_id order by player_id)rn,
Count(*) over (partition by match_id) cnt
from matching m
join players p on p.id=m.player_id
)x
group by match_id, date
See working example Fiddle

SQL Query for sorting and getting unique count

I have a table which consists of the following details
Customer
Deal
DealStage
A
D1
Lost
A
D2
Won
A
D3
Contacted
B
D4
Conatcted
B
D5
Lost
C
D6
Lost
D
D7
Lost
I have to develop a query where I should get the unique highest stage for each customer. The Stage priority is Won > Contacted > Lost. For Example, A is having three deals which are Won, Lost, and Contacted. So I should be considering Won. Similarly Contacted for B and Lost for C and D
Is it possible to get an Output like
Customer
Highets Stage
A
Won
B
Contacted
C
Lost
D
Lost
By this, I can generate a pivot table that looks like
Stage
CustomerCount
Won
1
Contacted
1
Lost
2
Thanks in Advance
One option uses aggregation and field():
select customer,
case min(field(deal_stage, 'Won', 'Contacted', 'Lost'))
when 1 then 'Won'
when 2 then 'Contacted'
when 3 then 'Lost'
end as highest_stage
from mytable
group by customer
Actually we could combine this with elt():
select customer,
elt(
min(field(deal_stage, 'Won', 'Contacted', 'Lost')),
'Won', 'Contacted', 'Lost'
) as highest_stage
from mytable
group by customer
You can then generate the final result with another level of aggregation:
select highest_stage, count(*)
from (
select customer,
elt(
min(field(deal_stage, 'Won', 'Contacted', 'Lost')),
'Won', 'Contacted', 'Lost'
) as highest_stage
from mytable
group by customer
) t
group by highest_stage
Use windows function as follows:
select * from
(select t.*,
row_number() over (partition by customer
order by case when dealstage = 'Won' then 1
when dealstage = 'Contacted' then 2
when dealstage = 'Lost' then 3
end
) as rn
from your_table t)
where rn = 1;
These are really two different problems. I would, in fact, recommend different approaches to the two. For the first, conditional aggregation:
select customer,
coalesce(max(case when state = 'Won' then state end),
max(case when state = 'Contacted' then state end),
max(case when state = 'Lost' then state end)
) as biggest_state
from t
group by customer;
However, for your final result, I would recommend a correlated subquery:
select t.state, count(*)
from t
where t.state = (select t2.state
from t2
where t2.customer = t.customer
order by field(state, 'Won', 'Contact', 'Lost')
limit 1
)
group by t.state;
Note: This assumes that the original data does not have duplicate rows. If it does, then count(distinct) is one adjustment.

MySql GROUP BY Max Date

I have a table called votes with 4 columns: id, name, choice, date.
****id****name****vote******date***
****1*****sam*******A******01-01-17
****2*****sam*******B******01-05-30
****3*****jon*******A******01-01-19
My ultimate goal is to count up all the votes, but I only want to count 1 vote per person, and specifically each person's most recent vote.
In the example above, the result should be 1 vote for A, and 1 vote for B.
Here is what I currently have:
select name,
sum(case when uniques.choice = A then 1 else 0 end) votesA,
sum(case when uniques.choice = B then 1 else 0 end) votesB
FROM (
SELECT id, name, choice, max(date)
FROM votes
GROUP BY name
) uniques;
However, this doesn't work because the subquery is indeed selecting the max date, but it's not including the correct choice that is associated with that max date.
Don't think "group by" to get the most recent vote. Think of join or some other option. Here is one way:
SELECT v.name,
SUM(v.choice = 'A') as votesA,
SUM(v.choice = 'B') as votesB
FROM votes v
WHERE v.date = (SELECT MAX(v2.date) FROM votes v2 WHERE v2.name = v.name)
GROUP BY v.name;
Here is a SQL Fiddle.
Your answer are close but need to JOIN self
Subquery get Max date by name then JOIN self.
select
sum(case when T.vote = 'A' then 1 else 0 end) votesA,
sum(case when T.vote = 'B' then 1 else 0 end) votesB
FROM (
SELECT name,Max(date) as date
FROM T
GROUP BY name
) AS T1 INNER JOIN T ON T1.date = T.date
SQLFiddle
Try this
SELECT
choice,
COUNT(1)
FROM
votes v
INNER JOIN
(
SELECT
id,
max(date)
FROM
votes
GROUP BY
name
) tmp ON
v.id = tmp.id
GROUP BY
choice;
Something like this (if you really need count only last vote of person)
SELECT
sum(case when vote='A' then cnt else 0 end) voteA,
sum(case when vote='B' then cnt else 0 end) voteB
FROM
(SELECT vote,count(distinct name) cnt
FROM (
SELECT name,vote,date,max(date) over (partition by name) maxd
FROM votes
)
WHERE date=maxd
GROUP BY vote
)
PS. MySQL v 8
select
name,
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
group by name
Or output just one row for the total counts of VoteA and VoteB:
select
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
Based on #d-shish solution, and since introduction (in MySQL 5.7) of ONLY_FULL_GROUP_BY, the GROUP BY statement must be placed in subquery like this :
SELECT v.`name`,
SUM(v.`choice` = 'A') as `votesA`,
SUM(v.`choice` = 'B') as `votesB`
FROM `votes` v
WHERE (
SELECT MAX(v2.`date`)
FROM `votes` v2
WHERE v2.`name` = v.`name`
GROUP BY v.`name` # << after
) = v.`date`
# GROUP BY v.`name` << before
Otherwise, it won't work anymore !