Mysql: Count wins but only once per opponent - mysql

I'm looking for help using sum() in my SQL query:
Task: Count tournament wins of all players. (one number per player) (battles.result = 1 means Player1 wins)
SELECT members.id, members.name,
(
SELECT SUM(battles.result = 1)
FROM battles
WHERE members.id = battles.player1 AND battles.result=1 order by battles.gametime
( as wins,
FROM members
Next: Only count ONE result per two players.
So if there are multiple results of two players, count only the first result (first gametime).
I've already tried using order by battles.player2, but i guess there is a much better solution?

You can easily get the result by doing a join and aggregation instead. Try:
SELECT A.id, A.name, SUM(IFNULL(B.result,0)) wons
FROM members A LEFT JOIN battles B
ON A.id=B.player1
GROUP BY A.id, A.name;

Related

Multiple aggregate count columns SQL

I have a table that roughly looks like the following:
winner_name
loser_name
Person A
Person B
Person A
Person C
Person B
Person A
Person C
Person B
I'm trying to return a table that looks like the following:
player_name
number_wins
number_losses
Person A
2
1
Person B
1
2
Person C
1
1
I can't quite figure out how to get there. I have been able to write a query that returns player_name and either number_wins or number_losses, but not both.
SELECT winner_name AS player_name, COUNT(*) AS number_wins
FROM my_table
GROUP BY player_name
I have looked into using procedures, functions, and subqueries to do this but I haven't found the right solution yet. Any direction would be helpful.
You need to pivot the names into the same column using UNION. Then you can calculate the sum of wins and losses.
SELECT player_name, SUM(win) AS number_wins, SUM(loss) AS number_losses)
FROM (
SELECT winner_name AS player_name, 1 AS win, 0 AS loss
FROM my_table
UNION ALL
SELECT loser_name AS player_name, 0 AS win, 1 AS loss
FROM my_table
) AS x
GROUP BY player_name
Since each aggregate statistic is for a different group (one for winner_name, the other for loser_name), they can't be calculated in the same query, but each query can be run separately and then combined with a JOIN. Simply take each query:
SELECT winner_name AS player, COUNT(loser_name) AS wins
FROM games
GROUP BY winner_name
;
SELECT loser_name AS player, COUNT(winner) AS losses
FROM games
GROUP BY loser_name
;
and join on the common attribute, the player name:
SELECT gw.player, gw.wins, gl.losses
FROM (
SELECT winner_name AS player, COUNT(loser_name) AS wins
FROM games
GROUP BY winner_name
) AS gw
JOIN (
SELECT loser_name AS player, COUNT(winner_name) AS losses
FROM games
GROUP BY loser_name
) AS gl
ON gl.player = gw.player
;
Whether using unions or joins, each distinct group that is the basis for aggregate statistics will require a separate sub-select.

Select multiple rows with MAX value

Given this table:
players(id,team,name,age)
I want to select the oldest player/s for each team
My query so far:
select p.team,p.name,MAX(p.age)
from players p
group by p.team
This query gives me the oldest player for each team but if there are multiple players with the same MAX age it selects only one of those at random.
How can I make it so it selects all of them?
This query:
select p.team, p.name, MAX(p.age)
from players p
group by p.team;
Should return a syntax error. The group by clause is inconsistent with the select, because p.name is not in the group by and not the argument to an aggregation function.
To do what you want, you can use a correlated subquery:
select p.*
from players p
where p.age = (select max(p2.age) from players p2 where p2.team = p.team);

SQL How to Count table for each fields

I have 3 tables which is:
Courses
courses_id
name
QnAs
qna_id
student_id
courses_id
name
question
Students
student_id
name
Now I'm trying to count how many qna's there are for each courses. How do i make the query?
I've tried doing this :
SELECT (SELECT COUNT(qna_id) AS Expr1
FROM QnAs) AS Count
FROM QnAs AS QnAs_1 CROSS JOIN
Courses
GROUP BY Courses.courses_id
It does counts how many QnA's there are but not for each Courses
The output i got is each Courses names and QnAs count number but what i want is the QnA's number for each of the Courses
It seems you merely want to aggregate QNAs by course ID:
select courses_id, count(*)
from qnas
group by courses_id
order by courses_id;
Along with the course names:
select c.course_id, c.name, coalesce(q.cnt, 0) as qna_count
from courses c
left join
(
select courses_id, count(*) as cnt
from qnas
group by courses_id
) q on q.course_id = c.course_id
order by c.course_id;
Why not just use GROUP BY?
SELECT q.courses_id, COUNT(qna_id) as cnt
FROM QnAs q
GROUP BY q.courses_id;
This is not an answer, but just an explanation what your query does.
In your own query you first cross join all QnAs with all courses for no apparent reason, thus getting all possible combinations. So with two courses, each with three QNAs (that makes six QNAs in total), you'd construct 2 x 6 = 12 rows.
For each of these rows you select the total number of rows in the QNA table, which is six in above example. So you'd select 12 rows, all showing the number 6.
But then you group by course ID, thus ending up with two rows only in my example. You should apply an aggregate function on your subquery, e.g. MAX or SUM, but you don't, which makes your query invalid (because you are dealing with many rows, but treat this as if it were a single value). MySQL however silently applies ANY_VALUE, so your query becomes:
SELECT
ANY_VALUE( (SELECT COUNT(*) FROM QnAs) ) AS Count
FROM QnAs AS QnAs_1
CROSS JOIN Courses
GROUP BY Courses.courses_id;
I hope this explanation helps you understand how joins and aggregation work. You may want to set ONLY_FULL_GROUP_BY mode (https://dev.mysql.com/doc/...) in order to have MySQL report the syntax error instead of silently "fixing" the query by applying ANY_VALUE.

Good alternatives of comporting two subquery in the 'Where' clause

I have this schema:
CLUB(Name, Address, City)
TEAM(TeamName, club)
PLAYER(Badge, teamName)
MATCH(matchNumber, player1, player2, club, winner)
I need to make this query:
For each club, find the number of players in that club that have won
at least two games.
I wrote this:
SELECT teamName
From TEAM t join Match m1 on t.club=m1.club
WHERE Q2 >= ALL Q1
Q1:
SELECT Count (Distinct winner)
FROM MATCH
WHERE match m join player p on m. winner=player.badge
GROUP BY teamName
Q2:
SELECT Count (distinct winner)
FROM match m2
WHERE m2.club=m1.club
I don’t know if it is correct, however I heard that using this form where I confront two counts is not the best. Why?
Try something like this:
SELECT club, COUNT(*) as PlayerCount
FROM (SELECT club, winner
FROM match
GROUP BY club, winner
HAVING COUNT(*) > 1) a
GROUP BY club
The inner query should limit results to club/player combinations that have 2 or more wins, and the outer query will count the number of these players per club.
I don’t know if it is correct, however I heard that using this form where I confront two counts is not the best. Why?
Comparing two count subqueries is fine if you need to, but a good rule of thumb is to hit each table as few times as possible. Using multiple subqueries will end up hitting each table multiple times, and will usually result in longer execution times.
Try this query
SELECT t.club, COUNT(*)
FROM TEAM t
JOIN PLAYER p ON p.teamName = t.TeamName
JOIN (
-- Won at least 2 matches.
SELECT club, winner, COUNT(*) AS TheCount
FROM MATCH
GROUP BY club, winner
HAVING COUNT(*) > 1
) w ON w.winner = p.badge AND w.club = t.club
GROUP BY t.club

Joining two tables twice

Writing a query for a basketball database, our table for games has as a winnerID and a loserID, each being a teamID. Tried the following two queries, each correctly giving me the number of wins but giving me the same number for losses.
SELECT team.name as Team_Name, COUNT(team.teamID=winner.winnerID) as Wins, COUNT(team.teamID=loser.loserID) as Losses
FROM team join games winner on winner.winnerID=team.teamID join games loser on loser.loserID=team.teamID
GROUP BY team.name
ORDER BY Wins, Team_Name;
SELECT team.name as Team_Name, COUNT(team.teamID=games.winnerID) as Wins, COUNT(team.teamID=games.loserID) as Losses
FROM (team INNER JOIN games on games.winnerID=team.teamID)
GROUP BY team.name
ORDER BY Wins, team.name;
Help?
EDIT: Forgot to mention, purpose of query is to get number of wins and number of losses of each team.
The COUNT aggregate gets a count of non-NULL values. That means it includes ones and zeros.
Evaluated in a numeric context, an equality comparison returns 1 for TRUE and returns 0 for FALSE, and only returns NULL if either side (or both sides) is NULL.
To add up the ones and ignore the zeros, you could use a SUM aggregate instead.
One of the big problems with the query is the potential to return duplicates, due to the cross join between winner and loser. If a team has 5 wins and 4 losses, the query is going to generate an intermediate set of 20 (= 5 x 4) rows.
To get a count of wins and losses from that, we'd need a unique identifier for a game (for example, a gameid column in the game table that is the PRIMARY KEY.) With that , we could get a count of distinct values of gameid. For example:
COUNT(DISTINCT IF(winner.winnerid=team.teamid,winner.gameid,NULL)) AS wins
There are several query patterns that will get you the number of wins and the number of losses for each team.
Here's one example:
SELECT t.name AS team_name
, COUNT(IF(t.teamid=g.winnerid,1,NULL) AS wins
, COUNT(IF(t.teamid=g.loserid ,1,NULL) AS losses
FROM team
LEFT
JOIN games g
ON ( g.winnerid = t.teamid OR g.loserid = t.teamid )
GROUP
BY t.name
ORDER
BY wins DESC
, t.name
With this, we are only joining to the games table once, so we won't get a cross product. Also note that if teamid is not equal to winnerid, we return a NULL instead of a 0. So a COUNT will include only winners, not all of the rows.
We use an outer join (rather than an inner join), in case there are no related rows in games for the team. That allows us to return a team with counts of zero.
We could use a SUM aggregate instead of a COUNT, For example:
SELECT t.name AS team_name
, IFNULL(SUM(t.teamid=g.winnerid),0) AS wins
, IFNULL(SUM(t.teamid=g.loserid) ,0) AS losses
FROM team
LEFT
JOIN games g
ON ( g.winnerid = t.teamid OR g.loserid = t.teamid )
GROUP
BY t.name
ORDER
BY wins DESC
, t.name
With a SUM() we have a potential to return NULL values. To get those converted to zeros, we use an IFNULL function.
And there are several other query patterns that would get you an equivalent result... e.g. instead of using joins, use correlated subqueries in the SELECT list...
SELECT t.name
, ( SELECT COUNT(1)
FROM game w
WHERE w.winner_id = t.teamid
) AS wins
, ( SELECT COUNT(1)
FROM game l
WHERE l.loserid = t.teamid
) AS losses
FROM team t
ORDER BY wins DESC
, t.name