Select all combinations - mysql

It’s pretty difficult to explain what I try to accomplish with just word. So I tried to make an example which reflects my real project. I have a group of people (Peter, Aaron, Mark, Alicia, Cleo and Mike) who play games (A, B and C) against each other. What I would like to do with my query is to get all combination who played against each other for the particular games.
So for example:
Peter played one A game vs Aaron he won in 3 rounds - > Peter - Aaron - A - 1 - 5 - 1
Mike played two C games vs Cleo and lost twice, in 3 and 4 rounds ->
Mike - Cleo - C - 0 - 3.5 - 2
Example table games:
id playRound player game win rounds
1 1 Peter A 1 5
2 1 Aaron A 0 5
3 1 Alicia B 0 3
4 1 Mark B 1 3
5 1 Mike C 0 4
6 1 Cleo C 1 4
7 2 Alicia A 1 5
8 2 Mark A 0 5
9 2 Peter B 1 3
10 2 Mark B 0 3
11 2 Mike C 0 3
12 2 Cleo C 1 3
13 3 Alicia A 0 4
14 3 Mark A 1 4
15 3 Peter B 0 4
16 3 Aaron B 1 4
17 3 Alicia C 1 5
18 3 Cleo C 0 5
End result:
playerONE playerTwo game avgWinRateOne avgRounds Number
Peter Aaron A 1 5 1
Peter Aaron B 1 3 1
Peter Mark B 0 4 1
Alicia Mark A 0.5 4.5 2
Alicia Mark B 0 3 1
Alicia Cleo C 1 5 1
Mike Cleo C 0 3.5 2
I'm fiddling with this but I Have no idea what im doing
SELECT *
FROM (
SELECT *
FROM `testtable`
WHERE `game` = 'A'
GROUP BY `gameId` , `game` , `player`
)tmp1, (
SELECT *
FROM `testtable`
WHERE `game` = 'A'
GROUP BY `gameId` , `game` , `player`
)tmp2

I think maybe you want this query:
select p1.player, p2.player, p1.game, avg(p1.win), avg(p1.rounds), count(*)
from games p1
inner join games p2
on p1.playround = p2.playround
and p1.game = p2.game
and p1.player != p2.player
group by p1.player, p2.player, p1.game;
It's a relatively basic join, where we associate the games table with itself, to get our game pairings, we can them group by those pairings, in order to use aggregate functions to calculate the averages, and number of games played.
Running it on your sample data gives this output:
mysql> select p1.player, p2.player, p1.game, avg(p1.win), avg(p1.rounds), count(*)
-> from games p1
-> inner join games p2
-> on p1.playround = p2.playround
-> and p1.game = p2.game
-> and p1.player != p2.player
-> group by p1.player, p2.player, p1.game;
+--------+--------+------+-------------+----------------+----------+
| player | player | game | avg(p1.win) | avg(p1.rounds) | count(*) |
+--------+--------+------+-------------+----------------+----------+
| Aaron | Peter | A | 0.0000 | 5.0000 | 1 |
| Aaron | Peter | B | 1.0000 | 4.0000 | 1 |
| Alicia | Cleo | C | 1.0000 | 5.0000 | 1 |
| Alicia | Mark | A | 0.5000 | 4.5000 | 2 |
| Alicia | Mark | B | 0.0000 | 3.0000 | 1 |
| Cleo | Alicia | C | 0.0000 | 5.0000 | 1 |
| Cleo | Mike | C | 1.0000 | 3.5000 | 2 |
| Mark | Alicia | A | 0.5000 | 4.5000 | 2 |
| Mark | Alicia | B | 1.0000 | 3.0000 | 1 |
| Mark | Peter | B | 0.0000 | 3.0000 | 1 |
| Mike | Cleo | C | 0.0000 | 3.5000 | 2 |
| Peter | Aaron | A | 1.0000 | 5.0000 | 1 |
| Peter | Aaron | B | 0.0000 | 4.0000 | 1 |
| Peter | Mark | B | 1.0000 | 3.0000 | 1 |
+--------+--------+------+-------------+----------------+----------+
14 rows in set (0.00 sec)

Related

Creating Medalist MySQL view from a table

I need a help to create a view in MySQL.
I have a table in the name of competitions like below:
+---------+-----+-----+-----+
|id| name |rank1|rank2|rank3|
+--+------+-----+-----+-----+
| 1| cmpt1| 4 | 3 | 9 |
| 2| cmpt2| 3 | 7 | 8 |
| 3| cmpt3| 4 | 1 | 2 |
| 4| cmpt4| 5 | 8 | 4 |
| 5| cmpt5| 9 | 3 | 2 |
| 6| cmpt6| 1 | 8 | 2 |
+--+------+-----+-----+-----+
the rank1,2,3 values refer to the player id who has such rank at the end of that competition.
Now I want to create a MySQL view to show each player's total medals. Rank 1, 2, and 3 received gold, silver, and bronze medal respectively.
The output of the view will be like following table:
+------+------------+-------------+-------------+
|player| gold_medals|silver_medals|bronze_medals|
+------+------------+-------------+-------------+
| 1 | 4 | 7 | 1 |
| 2 | 7 | 0 | 9 |
| 3 | 1 | 4 | 6 |
| 4 | 0 | 2 | 8 |
| 5 | 2 | 8 | 0 |
| 6 | 3 | 1 | 1 |
+------+------------+-------------+-------------+
Thanks in advance
I assumed you have another table for list players :
select p.playerid
, count(case when playerid = rank1 then 1 end) gold_medals
, count(case when playerid = rank2 then 1 end) silver_medals
, count(case when playerid = rank3 then 1 end) bronze_medals
from
players p
left join ranks r
on p.playerid in (rank1, rank2, rank3)
group by p.playerid
playerid | gold_medals | silver_medals | bronze_medals
-------: | ----------: | ------------: | ------------:
1 | 1 | 1 | 0
2 | 0 | 0 | 3
3 | 1 | 2 | 0
4 | 2 | 0 | 1
5 | 1 | 0 | 0
6 | 0 | 0 | 0
7 | 0 | 1 | 0
8 | 0 | 2 | 1
9 | 1 | 0 | 1
db<>fiddle here
You can unpivot and aggregate:
select playerid,
sum(ranking = 1) as num_gold,
sum(ranking = 2) as num_silver,
sum(ranking = 3) as num_bronze
from ((select rank1 as playerid, 1 as ranking
from ranks
) union all
(select rank2, 2 as ranking
from ranks
) union all
(select rank3, 3 as ranking
from ranks
)
) p
group by playerid;
Note: This only includes players who have a ranking. Your question doesn't include a source of all players, so that seems sufficient.
Here is a db<>fiddle.
Note that older versions of MySQL (pre-5.7 I think) don't support subqueries in the FROM clause of views. Happily that restriction is no longer in force.

How to use JOIN with Conditions

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

mysql grouped ranking with ties

so i have data in a table like this:
id total group_id
1897 738 1
2489 716 2
2325 715 3
1788 702 2
1707 699 3
2400 688 3
2668 682 2
1373 666 1
1494 666 1
1564 660 1
2699 659 1
1307 648 4
1720 645 4
2176 644 1
1454 644 4
2385 639 3
1001 634 2
2099 634 4
1006 632 1
2587 630 3
1955 624 3
1827 624 4
2505 623 4
2062 621 3
1003 618 1
2286 615 4
2722 609 4
how can i rank the ids per group based on the total and giving the same rank when there is a tie?
i have tried this solution below but it doesnt take care of the ties.
SELECT g1.admission_no
, g1.total
, g1.stream_id
, COUNT(*) AS rn
FROM ranktest AS g1
JOIN ranktest AS g2
ON (g2.total, g2.admission_no) >= (g1.total, g1.admission_no)
AND g1.stream_id = g2.stream_id
GROUP BY g1.admission_no
, g1.stream_id
, g1.total
ORDER BY g1.stream_id
, total ;
expected
id total group_id rank
1897 738 1 1
2489 716 2 1
2325 715 3 1
1788 702 2 2
1707 699 3 2
2400 688 3 3
2668 682 2 3
1373 666 1 2
1494 666 1 2
1564 660 1 3
2699 659 1 4
1307 648 4 1
1720 645 4 2
2176 644 1 4
1454 644 4 3
2385 639 3 4
1001 634 2 4
2099 634 4 4
1006 632 1 5
2587 630 3 5
1955 624 3 6
1827 624 4 5
2505 623 4 6
2062 621 3 6
1003 618 1 6
2286 615 4 7
2722 609 4 8
If original order is not very important you can start from:
http://sqlfiddle.com/#!9/a15a2/10
SELECT
ranktest.*,
IF(#rank IS NULL,#rank:=1, IF(#prev!=group_id,#rank:=1,#rank:=#rank+1) ) rank,
#prev:=group_id
FROM ranktest
ORDER BY group_id, total
but keep in mind that this is not very efficient query from performance perspective.
From the MySQL Manual Page entitled User-Defined Variables:
In the following statement, you
might think that MySQL will evaluate #a first and then do an
assignment second:
SELECT #a, #a:=#a+1, ...;
However, the order of evaluation for expressions involving user
variables is undefined.
This is why so few people write these answers correctly and safely. The object is not to hit the desired results once, but to do so again and again in real-world environments using best practices and guaranteed results.
If you don't read the Obligatory Doc, and implement it, your results are not guaranteed. There is no lazy way to do this and one shouldn't even bother :p
select id,total,group_id,rank
from
( select id,total,group_id,
#rn:=if( group_id!=#curr_grp, greatest(#grp_rank:=1,0),
if(total=#prev_grp_total,#grp_rank,greatest(#grp_rank:=#grp_rank+1,0) ) ) as rank,
#curr_grp:=group_id,
#prev_grp_total:=total
from trans01
cross join (select #grp_rank:=0,#curr_grp:=0,#prev_grp_total:=-1) xParams
order by group_id,total desc
)xDerived;
+------+-------+----------+------+
| id | total | group_id | rank |
+------+-------+----------+------+
| 1897 | 738 | 1 | 1 |
| 1373 | 666 | 1 | 2 |
| 1494 | 666 | 1 | 2 |
| 1564 | 660 | 1 | 3 |
| 2699 | 659 | 1 | 4 |
| 2176 | 644 | 1 | 5 |
| 1006 | 632 | 1 | 6 |
| 1003 | 618 | 1 | 7 |
| 2489 | 716 | 2 | 1 |
| 1788 | 702 | 2 | 2 |
| 2668 | 682 | 2 | 3 |
| 1001 | 634 | 2 | 4 |
| 2325 | 715 | 3 | 1 |
| 1707 | 699 | 3 | 2 |
| 2400 | 688 | 3 | 3 |
| 2385 | 639 | 3 | 4 |
| 2587 | 630 | 3 | 5 |
| 1955 | 624 | 3 | 6 |
| 2062 | 621 | 3 | 7 |
| 1307 | 648 | 4 | 1 |
| 1720 | 645 | 4 | 2 |
| 1454 | 644 | 4 | 3 |
| 2099 | 634 | 4 | 4 |
| 1827 | 624 | 4 | 5 |
| 2505 | 623 | 4 | 6 |
| 2286 | 615 | 4 | 7 |
| 2722 | 609 | 4 | 8 |
+------+-------+----------+------+
came up with this answer after googling a bit..not sure is its the best but it works for my case:
SELECT id, group_id, total,
#std:=CASE WHEN #grp <> group_id THEN concat(left(#grp:=group_id, 0), 1) ELSE if(#prev=total,#std,#std+1) END AS rn,#prev:=total
FROM
(SELECT #std:= -1) s,
(SELECT #grp:= -1,#prev:=null) c,
(SELECT *
FROM table
ORDER BY group_id, total desc
) s

How do i perform grouped ranking in MySQL with row ties?

ID_STUDENT | ID_CLASS | GRADE | RANK
2 | 1 | 90 | 1
1 | 1 | 90 | 1
3 | 1 | 90 | 1
4 | 1 | 70 | 4
6 | 2 | 90 | 1
1 | 2 | 80 | 2
5 | 2 | 78 | 3
7 | 3 | 90 | 1
6 | 3 | 50 | 2
How should i sort and rank the data to get the above result? Thanks in advance
One method is with a subquery:
select sc.*,
(select count(*) + 1
from studentclass sc2
where sc2.grade > sc.grade and sc2.id_class = sc.id_class
) as rank
from studentclass sc order by id_class, grade;
In ANSI SQL (and most other databases), this is provided by the rank() function. You might be interested in the differences between rank(), dense_rank(), and row_number().

MySql: Summing the latest amounts banked for each category and type

I have four MySql tables (simplified here):
Table 1: factions (just a list to reference)
id | name
1 | FactionName1
2 | FactionName2
Table 2: currencies (just a list to reference)
id | name
1 | Currency1
2 | Currency2
3 | Currency3
Table 3: events (just a list to reference)
id | name | date
1 | Evebt1 | 2013-10-16
2 | Event2 | 2013-10-18 (Note: date out of order)
3 | Event3 | 2013-10-17
Table 4: event_banking (data entered after each event, remaining amount of each currency for each group)
id | faction_id | currency_id | event_id | amount
1 | 1 | 1 | 1 | 10
2 | 1 | 1 | 2 | 20
3 | 1 | 1 | 3 | 30
4 | 1 | 2 | 1 | 40
5 | 1 | 2 | 2 | 50
6 | 1 | 2 | 3 | 60
7 | 1 | 3 | 1 | 70
8 | 1 | 3 | 2 | 80
9 | 1 | 3 | 3 | 90
10 | 2 | 1 | 1 | 100
11 | 2 | 1 | 2 | 110
12 | 2 | 1 | 3 | 120
13 | 2 | 2 | 1 | 130
14 | 2 | 2 | 2 | 140
15 | 2 | 2 | 3 | 150
16 | 2 | 3 | 1 | 160
17 | 2 | 3 | 3 | 170
Note: Faction 2 didn't bank Currency 3 for Event 2
What I'm looking to be able to do is to get, for each currency, the total of the last banked (date wise) for each faction. (ie How much of each currency is currently banked in total if all factions are merged)
So, I need a table looking something like:
currency_id | total
1 | 130 (eg 20 + 110)
2 | 190 (eg 50 + 140)
3 | 250 (eg 80 + 170) <- Uses Event 3 for Group 2 as Event 2 doesn't exist
I can do basic joins etc, but I'm struggling to be able to filter the results so that I get the latest results for each Faction x Currency x Event so I can then sum them together to get the final total amounts for each currency.
I've tried various permutations of LEFT OUTER JOINs, GROUP BYss & HAVING COUNTs, and had some interesting (but incorrect results), and a variety of different error codes, but nothing remotely close to what I need.
Can anyone help?
I guess you can go on with something like this:
select eb.currency_id, sum(amount) as total
from events e
inner join (
select faction_id, currency_id, max(date) as md
from event_banking eb
inner join events e
on eb.event_id = e.id
group by faction_id, currency_id
) a
on e.date = a.md
inner join event_banking eb
on e.id = eb.event_id
and a.faction_id = eb.faction_id
and a.currency_id = eb.currency_id
group by currency_id;
Here is SQL Fiddle