Solving max Scoring player from each group in mySQL - mysql

Refer to another Stack Overflow question here, however the answers there didn't include the group_id 3 player.
I tried to replicate the answer in MySQL but I am not familiar with PostgreSQL. Anyone can show how to proceed it in MySQL?
The question is to return the max scored player as winner_id from each group
create table players (
player_id integer not null unique,
group_id integer not null
);
create table matches (
match_id integer not null unique,
first_player integer not null,
second_player integer not null,
first_score integer not null,
second_score integer not null
);
insert into players values(20, 2);
insert into players values(30, 1);
insert into players values(40, 3);
insert into players values(45, 1);
insert into players values(50, 2);
insert into players values(65, 1);
insert into matches values(1, 30, 45, 10, 12);
insert into matches values(2, 20, 50, 5, 5);
insert into matches values(13, 65, 45, 10, 10);
insert into matches values(5, 30, 65, 3, 15);
insert into matches values(42, 45, 65, 8, 4);
matches table
match_id | first_player | second_player | first_score | second_score
----------+--------------+---------------+-------------+--------------
1 | 30 | 45 | 10 | 12
2 | 20 | 50 | 5 | 5
13 | 65 | 45 | 10 | 10
5 | 30 | 65 | 3 | 15
42 | 45 | 65 | 8 | 4
Expected output
group_id | winner_id
----------+-----------
1 | 45
2 | 20
3 | 40

I presume that since you can't use the solution to the other question that you are using MySQL 5.7 or below. In that case, you have to simulate the ROW_NUMBER/PARTITION functionality, which you can do with a LEFT JOIN from a derived table of scores per player with itself, joining on the score being greater than that in the first table. Any player who has no scores greater in the joined table clearly has the highest score. Since there can be ties, we then take the minimum of the player_id values from that table (when there is no tie, this has no effect).
SELECT group_id, MIN(player_id) AS player_id
FROM (
SELECT t1.group_id, t1.player_id
FROM (
SELECT p.player_id, p.group_id,
SUM(CASE WHEN m.first_player = p.player_id THEN m.first_score
ELSE m.second_score
END) AS score
FROM players p
LEFT JOIN matches m ON m.first_player = p.player_id OR m.second_player = p.player_id
GROUP BY p.player_id, p.group_id
) t1
LEFT JOIN (
SELECT p.player_id, p.group_id,
SUM(CASE WHEN m.first_player = p.player_id THEN m.first_score
ELSE m.second_score
END) AS score
FROM players p
LEFT JOIN matches m ON m.first_player = p.player_id OR m.second_player = p.player_id
GROUP BY p.player_id, p.group_id
) t2 ON t2.group_id = t1.group_id AND t2.score > t1.score
GROUP BY t1.group_id, t1.player_id
HAVING COUNT(t2.player_id) = 0
) w
GROUP BY group_id
Output:
group_id player_id
1 45
2 20
3 40
Demo on db-fiddle

Related

MySQL COUNT occurrences from several columns

I have the following table of matches of a 4 player game called games.
+---------+---------+---------+---------+---------+
| game_id | player1 | player2 | player3 | player4 |
+---------+---------+---------+---------+---------+
| 1001 | john | dave | NULL | NULL |
| 1002 | dave | john | mike | tim |
| 1003 | mike | john | dave | NULL |
| 1004 | tim | dave | NULL | NULL |
+---------+---------+---------+---------+---------+
There are two questions I want to be able to answer:
Who played in the most games? (Dave)
What pair of players played the most games together? (John & Dave)
For #1 I tried to adapt the answer I found here: mySQL query to find the most repeated value but it only seems to be able to answer the question for a single column. Meaning I could learn who was player1 the most, but not who played in the most games as any player:
SELECT player1 p1, COUNT(*) p1 FROM games
GROUP BY p1
ORDER BY p1 DESC;
Is there a way to join these columns together or would I have to handle this in application code?
Not sure where to start for #2. I'm wondering if my table structure should instead consolidate players to a single column:
+----+---------+--------+
| id | game_id | player |
+----+---------+--------+
| 1 | 1001 | john |
| 2 | 1001 | dave |
| 3 | 1002 | john |
| 4 | 1002 | dave |
| 5 | 1002 | mike |
| 6 | 1002 | tim |
+----+---------+--------+
Your best bet is normalizing database. This is a many-to-many relationship and needs a linked table to connect a game to its corresponding players. Then computations would be much more easier. Nevertheless, you could use a derived table for question one that unites all columns into one:
SELECT `player`,
COUNT(*) as `count`
FROM
(
SELECT `player1` `player`
FROM `games`
UNION ALL
SELECT `player2` `player`
FROM `games`
UNION ALL
SELECT `player3` `player`
FROM `games`
UNION ALL
SELECT `player4` `player`
FROM `games`
) p
GROUP BY `player` HAVING `player` IS NOT NULL
ORDER BY `count` DESC
See live demo here
For the second question you have to have an inner join on derived table:
SELECT `p`.`player`,
`p2`.`player`,
count(*) AS count
FROM
(
SELECT `game_id`, `player1` `player`
FROM `games`
UNION ALL
SELECT `game_id`, `player2` `player`
FROM `games`
UNION ALL
SELECT `game_id`, `player3` `player`
FROM `games`
UNION ALL
SELECT `game_id`, `player4` `player`
FROM `games`
) p
INNER JOIN
(
SELECT `game_id`, `player1` `player`
FROM `games`
UNION ALL
SELECT `game_id`, `player2` `player`
FROM `games`
UNION ALL
SELECT `game_id`, `player3` `player`
FROM `games`
UNION ALL
SELECT `game_id`, `player4` `player`
FROM `games`
) p2
ON `p`.`game_id` = `p2`.`game_id` AND `p`.`player` < `p2`.`player`
WHERE `p`.`player` IS NOT NULL AND `p2`.`player` IS NOT NULL
GROUP BY `p`.`player`, `p2`.`player`
ORDER BY `count` DESC
See live demo here
I would start with restructuring your design and introduce 3 tables
1) Player
which will have player data and their unique ids
CREATE TABLE players
(`id` int, `name` varchar(255))
;
INSERT INTO players
(`id`, `name`)
VALUES
(1, 'john'),
(2, 'dave'),
(3, 'mike'),
(4, 'tim');
2) Games which will have game data and their unique ids
CREATE TABLE games
(`id` int, `name` varchar(25))
;
INSERT INTO games
(`id`, `name`)
VALUES
(1001, 'G1'),
(1002, 'G2'),
(1003, 'G3'),
(1004, 'G4');
3) player_games to relate these 2 entities as many to many relationship via junction table which will hold game id and player id like as per your sample data
CREATE TABLE player_games
(`game_id` int, `player_id` int(11))
;
INSERT INTO player_games
(`game_id`, `player_id`)
VALUES
(1001, 1),
(1001, 2),
(1002, 1),
(1002, 2),
(1002, 3),
(1002, 4),
(1003, 3),
(1003, 1),
(1003, 2),
(1004, 4),
(1004, 2)
;
For Who played in the most games? Its dave not john as per your sample data set who played 4 games
select t.games_played,group_concat(t.name) players
from (
select p.name,
count(distinct pg.game_id) games_played
from player_games pg
join players p on p.id = pg.player_id
group by p.name
) t
group by games_played
order by games_played desc
limit 1
For above query there can be a chance that morethan one players have played most games like dave played 4 games and tim also played 4 games so both should be included
Demo
For What pair of players played the most games together? (John & Dave)
select t.games_played,group_concat(t.player_name) players
from (
select group_concat(distinct pg.game_id),
concat(least(p.name, p1.name), ' ', greatest(p.name, p1.name)) player_name,
count(distinct pg.game_id) games_played
from player_games pg
join player_games pg1 on pg.game_id = pg1.game_id
and pg.player_id <> pg1.player_id
join players p on p.id = pg.player_id
join players p1 on p1.id = pg1.player_id
group by player_name
) t
group by games_played
order by games_played desc
limit 1;
In above query i have self joined player_games table to get the combination of players against each game and then grouped data for each unique pair , Again followed same logic to handel that there can be a chance that morethan one pair of players have played most games
Demo

Most recent date with maximum score

I have the table definition below.
CREATE TABLE IF NOT EXISTS ranking (
user_id int(11) unsigned NOT NULL,
create_date date NOT NULL,
score double(8,2),
PRIMARY KEY (user_id, create_date)
)
insert into ranking (user_id, create_date, score) values
(1, '2017-03-01', 100),
(1, '2017-03-02', 90),
(1, '2017-03-03', 80),
(1, '2017-03-04', 100),
(1, '2017-03-05', 90),
(2, '2017-03-01', 90),
(2, '2017-03-02', 80),
(2, '2017-03-03', 100),
(2, '2017-03-5', 100),
(3, '2017-03-01', 80),
(3, '2017-03-02', 100),
(3, '2017-03-03', 90),
(3, '2017-03-6', 100);
select * from ranking;
user_id | create_date | score
1 | 2017-03-01 | 100
1 | 2017-03-02 | 90
1 | 2017-03-03 | 80
1 | 2017-03-04 | 100
1 | 2017-03-05 | 90
2 | 2017-03-01 | 90
2 | 2017-03-02 | 80
2 | 2017-03-03 | 100
2 | 2017-03-05 | 100
3 | 2017-03-01 | 80
3 | 2017-03-02 | 100
3 | 2017-03-03 | 90
3 | 2017-03-06 | 100
What I want is for each user_id, get the most recent create_date on which the score is maximum. For example, in the example above, for user_id = 1, when create_date = 2017-03-01 and create_date = 2017-03-04, the maximum score is 100, but I just want the most recent date with the maximum score, i.e., create_date = 2017-03-04 and score = 100. The query result is as follows:
user_id | create_date | score
1 | 2017-03-04 | 100
2 | 2017-03-05 | 100
3 | 2017-03-06 | 100
Below is my solution, which returns the expected result but I believe there exist better solutions.
SELECT a.* from
(
SELECT s1.user_id , s1.create_date, s1.score FROM ranking AS s1
INNER JOIN
(SELECT user_id , FORMAT(max(score), 0) as best_score FROM ranking GROUP BY user_id ) AS s2
ON s1.user_id = s2.user_id AND s1.score = s2.best_score
) a
NATURAL LEFT JOIN
(
SELECT s1.user_id , s1.create_date, s1.score FROM ranking AS s1
INNER JOIN
(
SELECT user_id , create_date, score FROM ranking
) s2
WHERE s1.user_id = s2.user_id AND s1.score = s2.score AND s1.create_date < s2.create_date
) b
WHERE b.user_id IS NULL;
Can someone provide better solutions? Thanks.
SELECT t1.user_id,
MAX(t1.create_date) AS max_date,
t2.max_score
FROM ranking t1
INNER JOIN
(
SELECT user_id, MAX(score) AS max_score
FROM ranking
GROUP BY user_id
) t2
ON t1.user_id = t2.user_id AND
t1.score = t2.max_score
GROUP BY t1.user_id
Output:
Demo here:
Rextester
Try this:
select user_id, max(create_date),max(score) from ranking GROUP BY user_id
result:
1 2017-03-04 100.00
2 2017-03-05 100.00
3 2017-03-06 100.00
or
select user_id, max(create_date),cast(max(score) as UNSIGNED) as maxscore from ranking GROUP BY user_id
result:
1 2017-03-04 100
2 2017-03-05 100
3 2017-03-06 100
Try this query -
SELECT r1.* FROM ranking r1
JOIN (SELECT user_id, MAX(score) max_score FROM ranking GROUP BY user_id) r2
ON r1.user_id = r2.user_id AND r1.score = r2.max_score
JOIN (SELECT user_id, score, MAX(create_date) max_create_date FROM ranking GROUP BY user_id, score) r3
ON r1.user_id = r3.user_id AND r1.score = r3.score AND r1.create_date = r3.max_create_date;
1 04-Mar-17 100
2 05-Mar-17 100
3 06-Mar-17 100

How to get biggest differences in column compared to last month?

I have two tables - Products and Prices
Every month table Prices is populated with new prices for each products. How can I get 5 products whose prices have the biggest incremental difference from last month prices?
table Products
id | name
1 | apples
2 | pears
3 | bananas
table Prices
id | price | product_id | created_at
1 | 10 | 1 | 2017-02-07 07:00:00
2 | 10 | 2 | 2017-02-07 07:00:00
3 | 15 | 3 | 2017-02-07 07:00:00
5 | 15 | 1 | 2017-03-07 07:00:00
6 | 20 | 2 | 2017-03-07 07:00:00
7 | 25 | 3 | 2017-03-07 07:00:00
The result would be to find out that
1. Bananas has prices by 15 higher (lastMonth: 15, now: 25)
2. Pears 2 has prices by 10 higher (lastMonth: 10, now: 20)
3. Apples has prices by 5 higher (lastMonth: 10, now: 15)
I was thinking something like this (uff I know this is terrible)
SELECT products.id, products.name, prices.beforePrice, prices.afterPrice, prices.difference
FROM products
INNER JOIN prices ON products.id = prices.product_id
WHERE
(
SELECT *biggest-difference*
FROM prices
WHERE *difference_between_last_2_months*
GROUP BY product_id
LIMIT 5
)
Create table/insert data
CREATE TABLE Products
(`id` INT, `name` VARCHAR(7))
;
INSERT INTO Products
(`id`, `name`)
VALUES
(1, 'apples'),
(2, 'pears'),
(3, 'bananas')
;
CREATE TABLE Prices
(`id` INT, `price` INT, `product_id` INT, `created_at` DATETIME)
;
INSERT INTO Prices
(`id`, `price`, `product_id`, `created_at`)
VALUES
(1, 10, 1, '2017-02-07 07:00:00'),
(2, 10, 2, '2017-02-07 07:00:00'),
(3, 15, 3, '2017-02-07 07:00:00'),
(5, 15, 1, '2017-03-07 07:00:00'),
(6, 20, 2, '2017-03-07 07:00:00'),
(7, 25, 3, '2017-03-07 07:00:00')
;
Query
SELECT
Products.id
, Products.name
, (current_month.price - last_month.price) AS difference
, (
CASE
WHEN last_month.price > current_month.price
THEN 'lower'
WHEN last_month.price < current_month.price
THEN 'higher'
END
) AS incremental
, last_month.price 'lastMonth'
, current_month.price 'now'
FROM (
SELECT
*
FROM
Prices
WHERE
MONTH(created_at) = MONTH((CURDATE() - INTERVAL 1 MONTH))
)
AS
last_month
INNER JOIN (
SELECT
*
FROM
Prices
WHERE
MONTH(created_at) = MONTH((CURDATE()))
)
AS
current_month
ON
last_month.product_id = current_month.product_id
INNER JOIN
Products
ON
last_month.product_id = Products.id
WHERE
last_month.price < current_month.price #incremental should be higher
ORDER BY
difference DESC
LIMIT 5
Result
id name difference incremental lastMonth now
------ ------- ---------- ----------- --------- --------
2 pears 10 higher 10 20
3 bananas 10 higher 15 25
1 apples 5 higher 10 15
You can use a proper joins based on fliterd select by month.
This should return the value you need ( you can add the literal string you need )
select p.name, m1.price as this_month, m2.price as prev_month, m2.price-m1.price as diff
from product
left join (
select price, product_id
from Prices
where month(created_at) = month(NOW())
and year(created_at) = year(NOW())
) m1 on m1.product_id = p.id
left join (
select price, product_id
from Prices
where month(created_at) = MONT(DATE_SUB(NOW(), INTERVAL 1 MONTH)
and year(created_at) = year(DATE_SUB(NOW(), INTERVAL 1 MONTH)
) m2 on m2.product_id = p.id
order by diff desc
limit 5

Query to get only single point record

I have following mysql tabels
tbl_users
user_id email db_add_date
1 steve#gmail.com 2014-04-22 19:32:35
2 mark#gmail.com 2014-05-02 10:12:01
3 allan#yahoo.com 2014-03-15 05:14:56
4 jim_ross#gmail.com 2014-02-12 14:10:08
tbl_points
id user_id points
1 1 10
2 3 1
3 2 15
4 1 16
5 4 46
6 3 24
i want to fetch those email & points that are single
Expected Result
email userid points
mark#gmail.com 2 15
jim_ross#gmail.com 4 46
please help...
UPDATE DESCRIPTION
I want to fetch those email records that having only 1 entry in points table
SQL Fiddle
MySQL 5.5.32 Schema Setup:
create table tbl_users
(
user_id int,
email varchar(20),
db_add_date datetime
);
insert into tbl_users values
(1, 'steve#gmail.com ', '2014-04-22 19:32:35'),
(2, 'mark#gmail.com ', '2014-05-02 10:12:01'),
(3, 'allan#yahoo.com ', '2014-03-15 05:14:56'),
(4, 'jim_ross#gmail.com', '2014-02-12 14:10:08');
create table tbl_points
(
id int,
user_id int,
points int
);
insert into tbl_points values
(1, 1, 10),
(2, 3, 1 ),
(3, 2, 15),
(4, 1, 16),
(5, 4, 46),
(6, 3, 24);
Query 1:
select u.email,
u.user_id,
p.points
from tbl_users as u
inner join (
select user_id,
sum(points) as points
from tbl_points
group by user_id
having count(*) = 1
) as p
on u.user_id = p.user_id
Results:
| EMAIL | USER_ID | POINTS |
|--------------------|---------|--------|
| mark#gmail.com | 2 | 15 |
| jim_ross#gmail.com | 4 | 46 |
your Expected Result...!!!
(Simple and sort query)
SELECT u.email,u.user_id as userid,p.points
FROM tbl_users u
LEFT JOIN tbl_points p
ON u.user_id = p.user_id
GROUP BY u.user_id
HAVING COUNT(u.user_id) = 1
Result:-
> email userid points
> mark#gmail.com 2 15
> jim_ross#gmail.com 4 46

SQL - get latest records from table where field is unique

I have a table of data as follows
id status conversation_id message_id date_created
1 1 1 72 2012-01-01 00:00:00
2 2 1 87 2012-03-03 00:00:00
3 2 2 95 2012-05-05 00:00:00
I want to get all the rows from the table in date_created DESC order, but only one row per conversation_id. So in the case of the example data above, I would want to get the rows with id 2 and 3.
Any advice is much appreciated.
SELECT t.id, t.status, t.conversation_id, t.message_id, t.date_created
FROM YourTable t
INNER JOIN (SELECT conversation_id, MAX(date_created) AS MaxDate
FROM YourTable
GROUP BY conversation_id) q
ON t.conversation_id = q.conversation_id
AND t.date_created = q.MaxDate
ORDER BY t.date_created DESC;
See SQL Fiddle
SELECT T.*
FROM T
WHERE NOT EXISTS (
SELECT *
FROM T AS _T
WHERE _T.conversation_id = T.conversation_id
AND (
_T.date_created > T.date_created
OR
_T.date_created = T.date_created AND _T.id > T.id)
)
ORDER BY T.date_created DESC
gets
ID STATUS CONVERSATION_ID MESSAGE_ID DATE_CREATED
3 2 2 95 May, 05 2012
2 2 1 87 March, 03 2012
Creation and inserts borrowed from #Incidently:
CREATE TABLE T
(id int, status int, conversation_id int, message_id int, date_created datetime);
insert into T (id, status, conversation_id, message_id, date_created)
values(1, 1, 1, 72, '2012-01-01 00:00:00');
insert into T (id, status, conversation_id, message_id, date_created)
values(2, 2, 1, 87, '2012-03-03 00:00:00');
insert into T (id, status, conversation_id, message_id, date_created)
values(3, 2 , 2 , 95 , '2012-05-05 00:00:00');
Supposing the id is the primary key then this solves the problem pointed by #Incidentally in #Joe's answer:
select T.*
from
T
inner join (
select conversation_id, max(id) as id
from T
group by conversation_id
) s on s.id = T.id
order by T.date_created desc, T.conversation_id
;
+------+--------+-----------------+------------+---------------------+
| id | status | conversation_id | message_id | date_created |
+------+--------+-----------------+------------+---------------------+
| 3 | 2 | 2 | 95 | 2012-05-05 00:00:00 |
| 2 | 2 | 1 | 87 | 2012-03-03 00:00:00 |
+------+--------+-----------------+------------+---------------------+