Group and rank top N rows by category - mysql

I have a table with the columns category and votes. I've tried multiple solutions before with very little success; usually what would happen is that instead of returning the top 3 items in each category, it returns all of the items available.
SELECT `id`, `user_id`, `full_name`, `category`, `year`, `month`, `thumbnail_photo`, `photo_title`, `votes`
FROM
(
SELECT `id`, `user_id`, `full_name`, `category`, `year`, `month`, `thumbnail_photo`, `photo_title`, `votes`,
#category_rank := IF(#current_category = category, #category_rank + 1, 1) AS category_rank,
#current_category := category
FROM `photo_contest`
ORDER BY
`category`,
`votes` DESC
) ranked
WHERE
category_rank <= 3
AND `year` = '2017'
AND `month` = 'April'
AND `votes` > 0
This particular solution was adapted from SQLines. What I ultimately want to do is to turn a table like this:
Name | Category | Votes
--------- | -------- | -----
Name Foo | CatFoo | 0
Name Bar | CatFoo | 1
Name Baz | CatFoo | 10
Name Quux | CatFoo | 200
Name ooF | CatBar | 50
Name raB | CatBar | 300
Name zaB | CatBar | 10
Name xuuQ | CatBar | 200
...to:
Name | Category | Votes
--------- | -------- | -----
Name Quux | CatFoo | 200
Name Baz | CatFoo | 10
Name Bar | CatFoo | 1
Name raB | CatBar | 300
Name xuuQ | CatBar | 200
Name ooF | CatBar | 50
...with the other WHERE statements included. Year, month, and minimum votes.

Your subquery tries to calculate ranking over the entire table. If you only want to rank for the selected year-month with votes > 0, you should copy those conditions into the subquery as its own WHERE conditions.
UPDATE:
Looks like it's the missing ORDER BY in the outer-query that causes the said problem. I've created the following DDL/SQL at sqlfiddle.
CREATE TABLE IF NOT EXISTS `votes` (
`id` INT NOT NULL,
`category` VARCHAR(10) NULL,
`year` VARCHAR(4) NULL,
`month` VARCHAR(2) NULL,
`votes` INT
)
ENGINE = InnoDB;
INSERT INTO `votes` VALUES
(10, 'cat1', '2016', '05', 300),
(10, 'cat1', '2016', '06', 200),
(10, 'cat2', '2016', '05', 500),
(11, 'cat1', '2016', '05', 200),
(11, 'cat2', '2016', '05', 0),
(11, 'cat2', '2016', '06', 100),
(12, 'cat1', '2016', '05', 400),
(12, 'cat2', '2016', '05', 150),
(13, 'cat1', '2016', '05', 350),
(13, 'cat2', '2016', '05', 100),
(13, 'cat2', '2016', '06', 150),
(14, 'cat1', '2016', '05', 0),
(14, 'cat2', '2016', '05', 450);
SELECT `id`, `category`, `year`, `month`, `votes`
FROM (
SELECT `id`, `category`, `year`, `month`, `votes`,
#category_rank := IF(#current_category = category, #category_rank + 1, 1) AS category_rank,
#current_category := category
FROM `votes`
WHERE
`year` = '2016'
AND `month` = '05'
AND `votes` > 0
ORDER BY
`category`,
`votes` DESC
) ranked
WHERE
category_rank <= 3
ORDER BY
`category`,
`votes` DESC;

I'm not an expert with MySQL, so I propose you a different (standard SQL) approach: you can join the table with itself on Category and on Votes being less or equal to the votes of the current row.
select t1.Name, t1.Category, t1.Votes, count(distinct t2.Name) as rank
from photo_contest t1
join photo_contest t2
on t1.Category = t2.Category and
t1.Votes <= t2.Votes
/*where whatever you want*/
group by t1.Name, t1.Category, t1.Votes
having count(distinct t2.Name) <= 3
order by t1.Category, rank
I tested it here and it seems to do what you asked for

It sounds like your PHPMyAdmin needs an upgrade or a replacement. Meanwhile you might want to try #Stefano Zanini's non-MySQL specific SQL:
SELECT
t1.`id`, t1.`category`, t1.`year`, t1.`month`,
t1.`votes`, count(distinct t2.`id`) as rank
FROM photo_contest t1
INNER JOIN photo_contest t2 ON
t1.`category` = t2.`category` AND
t1.`votes` <= t2.`votes`
WHERE t1.`votes` > 0
GROUP BY t1.`id`, t1.`category`, t1.`votes`
HAVING count(distinct t2.`id`) <= 3
ORDER BY t1.`category`, rank;
It's available on sqlfiddle. If you think this solution suits you better please credit #Stefano Zanini's answer instead of this one.

Related

How to get difference or delta of counts entries of each days with window functions?

I have a table with few fields like id, country, ip, created_at. Then I am trying to get the deltas between total entry of one day and total entry of the next day.
CREATE TABLE session (
id int NOT NULL AUTO_INCREMENT,
country varchar(50) NOT NULL,
ip varchar(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
INSERT INTO `session` (`id`, `country`, `ip`, `created_at`) VALUES
('1', 'IN', '10.100.102.11', '2021-04-05 20:26:02'),
('2', 'IN', '10.100.102.11', '2021-04-05 19:26:02'),
('3', 'US', '10.120.102.11', '2021-04-17 10:26:02'),
('4', 'US', '10.100.112.11', '2021-04-16 12:26:02'),
('5', 'AU', '10.100.102.122', '2021-04-12 19:36:02'),
('6', 'AU', '10.100.102.122', '2021-04-12 18:20:02'),
('7', 'AU', '10.100.102.122', '2021-04-12 23:26:02'),
('8', 'US', '10.100.102.2', '2021-04-16 21:33:01'),
('9', 'AU', '10.100.102.122', '2021-04-18 20:46:02'),
('10', 'AU', '10.100.102.111', '2021-04-04 13:19:12'),
('11', 'US', '10.100.112.11', '2021-04-16 12:26:02'),
('12', 'IN', '10.100.102.11', '2021-04-05 15:26:02'),
('13', 'IN', '10.100.102.11', '2021-04-05 19:26:02');
Now I have written this query to get the delta
SELECT T1.date1 as date, IFNULL(T1.cnt1-T2.cnt2, T1.cnt1) as delta from (
select TA.dateA as date1, MAX(TA.countA) as cnt1 from (
select DATE(created_at) AS dateA, COUNT(*) AS countA
FROM session
GROUP BY DATE(created_at)
UNION
select DISTINCT DATE(DATE(created_at)+1) AS dateA, 0 AS countA
FROM session
) as TA
group by TA.dateA
) as T1
LEFT OUTER JOIN (
select DATE(DATE(created_at)+1) AS date2,
COUNT(*) AS cnt2
FROM session
GROUP BY DATE(created_at)
) as T2
ON T1.date1=T2.date2
ORDER BY date;
http://sqlfiddle.com/#!9/4f5fd26/60
Then I am getting the results as
date delta
2021-04-04 1
2021-04-05 3
2021-04-06 -4
2021-04-12 3
2021-04-13 -3
2021-04-16 3
2021-04-17 -2
2021-04-18 0
2021-04-19 -1
Now, is there any place of improvements/optimizes on it with/or window functions? (I am zero with SQL, still playing around).
Try a shorter version
with grp as (
SELECT t.dateA, SUM(t.cnt) AS countA
FROM session,
LATERAL (
select DATE(created_at) AS dateA, 1 as cnt
union all
select DATE(DATE(created_at)+1), 0 as cnt
) t
GROUP BY dateA
)
select t1.dateA as date, IFNULL(t1.countA-t2.countA, t1.countA) as delta
from grp t1
left join grp t2 on DATE(t2.dateA + 1) = t1.dateA
order by t1.dateA
db<>fiddle

MySQL median subquery in select

This is what I have at the moment:
http://sqlfiddle.com/#!9/30a97c/10
Schema:
CREATE TABLE IF NOT EXISTS `test` (
`t_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`t_city` varchar(64) NOT NULL,
`t_category` enum('cat1','cat2') NOT NULL,
`t_type` enum('type1','type2') NOT NULL,
`t_num` int(8) NOT NULL,
PRIMARY KEY (`t_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
INSERT INTO `test` (`t_id`, `t_city`, `t_category`, `t_type`, `t_num`) VALUES
(1, 'New York', 'cat1', 'type1', 1056),
(2, 'New York', 'cat1', 'type1', 3756),
(3, 'London', 'cat1', 'type2', 3456),
(4, 'New York', 'cat1', 'type2', 5756),
(5, 'London', 'cat1', 'type2', 3777),
(6, 'New York', 'cat2', 'type1', 3756),
(7, 'New York', 'cat2', 'type1', 3756),
(8, 'London', 'cat2', 'type1', 3777),
(9, 'New York', 'cat2', 'type2', 4556),
(10, 'Berlin', 'cat1', 'type2', 1556),
(11, 'Berlin', 'cat2', 'type2', 9756),
(12, 'London', 'cat1', 'type2', 1756);
Query:
SELECT "cat1" as st_category, "type2" as st_type, t_city as st_name,
(
SELECT (ROUND(AVG(dd.t_num) / 10) * 10) as median
FROM (
SELECT d.t_num, #rownum:=#rownum+1 as `row_number`, #total_rows:=#rownum
FROM test as d, (SELECT #rownum:=0) as r
WHERE d.t_num is NOT NULL
AND `t_category` = "cat1" AND `t_type` = "type2" AND `t_city` = "XXXXX"
ORDER BY d.t_num
) as dd
WHERE dd.row_number IN ( FLOOR((#total_rows+1)/2), FLOOR((#total_rows+2)/2) )
) as st_median,
COUNT(t_num) as st_count
FROM `test` WHERE `t_category` = "cat1" AND `t_type` = "type2"
AND t_city in ("London", "New York")
GROUP BY t_city
ORDER BY st_name ASC
I don't know what to write in place of XXX (in the subquery) so that the median values of "t_num" in the output lines are not NULL. No median function in my MySQL.
OUTPUT NOW:
+-------------+---------+----------+-----------+----------+
| st_category | st_type | st_name | st_median | st_count |
+-------------+---------+----------+-----------+----------+
| cat1 | type2 | London | (null) | 3 |
| cat1 | type2 | New York | (null) | 1 |
+-------------+---------+----------+-----------+----------+
REQUIRED OUTPUT:
+-------------+---------+----------+-----------+----------+
| st_category | st_type | st_name | st_median | st_count |
+-------------+---------+----------+-----------+----------+
| cat1 | type2 | London | 3460 | 3 |
| cat1 | type2 | New York | 5760 | 1 |
+-------------+---------+----------+-----------+----------+
Thank you all for your help! This is the right solution:
SET SESSION GROUP_CONCAT_MAX_LEN = 1000000;
SELECT `t_category` AS st_category,
`t_type` AS st_type,
`t_city` AS st_name,
CASE ( COUNT(*) % 2 )
WHEN 1 THEN
ROUND(SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(`t_num`
ORDER BY `t_num` SEPARATOR ','), ',', ( COUNT(*) + 1 ) / 2), ',', -1)
/ 10) * 10
ELSE
ROUND((SUBSTRING_INDEX( SUBSTRING_INDEX( GROUP_CONCAT(`t_num`
ORDER BY `t_num` SEPARATOR ','), ',', COUNT(*) / 2), ',', -1)
+ SUBSTRING_INDEX( SUBSTRING_INDEX( GROUP_CONCAT(`t_num`
ORDER BY `t_num` SEPARATOR ','), ',', (COUNT(*) + 1) / 2), ',', -1) ) / 2
/ 10) * 10
END st_median,
COUNT(`t_num`) AS st_count
FROM `test`
WHERE `t_category` = "cat1"
AND `t_type` = "type2"
AND `t_city` IN ( "London", "New York" )
GROUP BY `t_city`
ORDER BY st_name ASC;
SQL Fiddle: http://sqlfiddle.com/#!9/30a97c/13
The GROUP_CONCAT_MAX_LEN setting is only required if you are working a lot of data.
Maximum value is 18,446,744,073,709,551,615 on 64-bit platforms.
On 32-bit platforms: 4,294,967,295.
https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_group_concat_max_len

Top sales making product for each year

I have two tables with following schema.
CREATE TABLE Product (
id INT AUTO_INCREMENT,
name VARCHAR(64) UNIQUE,
unit_price VARCHAR(6),
PRIMARY KEY (id)
);
CREATE TABLE Sale (
id INT AUTO_INCREMENT,
product_id INT, created_at DATETIME,
units INT, PRIMARY KEY (id),
FOREIGN KEY (product_id) REFERENCES Product(id)
);
INSERT INTO Product(name, unit_price)
VALUES
('Methadone', 30),
('Codeine', 20),
('Morphine', 40),
('Fentanyl', 10);
INSERT INTO Sale(product_id, created_at, units)
VALUES
(1, '2004-01-06 08:27:25', 8),
(4, '2004-03-05 05:21:15', 24),
(2, '2005-01-06 08:26:55', 3),
(4, '2005-03-31 01:55:25', 6),
(1, '2005-11-09 11:33:20', 2),
(4, '2006-02-15 10:46:30', 4),
(3, '2006-04-14 07:40:50', 1),
(4, '2008-01-06 08:27:25', 5),
(4, '2008-01-10 21:31:45', 3),
(1, '2008-01-28 16:07:30', 9),
(3, '2008-03-05 05:21:00', 4),
(2, '2008-03-08 18:25:50', 5),
(4, '2008-05-02 02:15:20', 1),
(4, '2008-05-05 15:19:40', 3),
(4, '2008-06-28 23:08:55', 2),
(4, '2008-07-02 12:14:00', 2),
(3, '2008-08-29 09:07:50', 1),
(2, '2008-10-22 16:56:20', 5),
(3, '2008-10-26 06:01:25', 2),
(4, '2008-12-19 13:49:55', 4),
(2, '2008-12-23 02:55:30', 4),
(1, '2009-02-15 10:43:45', 4),
(2, '2009-04-14 07:37:50', 6),
(3, '2009-06-11 04:31:25', 3),
(4, '2009-08-08 01:25:30', 12),
(1, '2010-11-09 11:33:20', 8),
(1, '2011-01-14 10:22:35', 3),
(3, '2011-03-13 07:16:10', 3),
(4, '2011-05-10 04:10:15', 4),
(4, '2011-07-07 01:04:35', 4),
(4, '2011-09-02 21:58:10', 3),
(2, '2011-10-30 18:51:45', 3),
(1, '2011-12-27 15:45:20', 1),
(4, '2012-02-23 12:43:25', 2),
(4, '2012-04-21 09:37:30', 3),
(4, '2012-06-18 06:31:20', 1),
(2, '2012-08-15 03:25:40', 4),
(2, '2013-01-14 10:23:20', 4),
(4, '2013-02-01 05:01:35', 1),
(4, '2013-03-13 07:16:55', 1),
(4, '2013-03-31 01:55:10', 2),
(4, '2013-05-10 04:11:15', 2),
(3, '2013-05-27 22:48:45', 3),
(1, '2013-07-07 01:05:20', 3),
(2, '2013-07-24 19:42:20', 4),
(4, '2013-09-02 21:59:40', 4),
(3, '2013-09-20 16:36:10', 1),
(4, '2013-10-30 18:54:00', 2),
(4, '2013-11-17 13:29:45', 1),
(4, '2013-12-27 15:48:20', 3);
I want to find top sales making product for each year sorted by year in descending order along-with the total sale made for the top selling product.
I have written the following query.
SELECT EXTRACT(YEAR FROM S.created_at) as year,
GROUP_CONCAT(DISTINCT P1.name
ORDER BY P1.name
SEPARATOR ',')as top,
MAX(S.units*P1.unit_price) as sale
FROM Sale S
INNER JOIN Product P1 ON S.product_id=P1.id
GROUP BY year
ORDER BY year desc
Expected Output:
2013 Codeine,Fentanyl,Morphine 160
2012 Codeine 80
2011 Methadone,Morphine 120
2010 Methadone 240
2009 Codeine,Fentanyl,Methadone,Morphine 120
2008 Codeine,Morphine 280
2006 Fentanyl,Morphine 40
2005 Codeine,Fentanyl,Methadone 60
2004 Fentanyl,Methadone 240
Original Output:
2013 Codeine,Fentanyl,Methadone,Morphine 120
2012 Codeine,Fentanyl 80
2011 Codeine,Fentanyl,Methadone,Morphine 120
2010 Methadone 240
2009 Codeine,Fentanyl,Methadone,Morphine 120
2008 Codeine,Fentanyl,Methadone,Morphine 270
2006 Fentanyl,Morphine 40
2005 Codeine,Fentanyl,Methadone 60
2004 Fentanyl,Methadone 240
The query was supposed to return only the top selling products, bu it's returning all the products. Could anyone provide any help?
Here is a solution for MySQL < 8.0. First, aggregate sales by year and product and filter on the top selling products with an aggregate, correlated subquery; then, aggregate by year:
select
year_at,
group_concat(name order by name) products,
total_sales
from (
select
year(s.created_at) year_at,
p.name,
sum(s.units * p.unit_price) total_sales
from Product p
inner join Sale s on s.product_id = p.id
group by year_at, p.id, p.name
having total_sales = (
select sum(s1.units * p1.unit_price) total_sales1
from Product p1
inner join Sale s1 on s1.product_id = p1.id
where year(s1.created_at) = year_at
group by s1.product_id
order by total_sales1 desc
limit 1
)
) t
group by year_at, total_sales
order by year_at desc
Demo on DB Fiddlde:
year_at | products | total_sales
------: | :---------------------------------- | ----------:
2013 | Codeine,Fentanyl,Morphine | 160
2012 | Codeine | 80
2011 | Methadone,Morphine | 120
2010 | Methadone | 240
2009 | Codeine,Fentanyl,Methadone,Morphine | 120
2008 | Codeine,Morphine | 280
2006 | Fentanyl,Morphine | 40
2005 | Codeine,Fentanyl,Methadone | 60
2004 | Fentanyl,Methadone | 240
You seem to be after this...
SELECT GROUP_CONCAT(a.name) names,a.total, a.year
FROM
( SELECT p.name
, SUM(p.unit_price*s.units) total
, YEAR(created_at) year
FROM product p
JOIN sale s
ON s.product_id = p.id
GROUP
BY year
, name
) a
JOIN
( SELECT MAX(total) total
, year
FROM
( SELECT p.name
, SUM(p.unit_price*s.units) total
, YEAR(created_at) year
FROM product p
JOIN sale s
ON s.product_id = p.id
GROUP
BY year
, name
) n
GROUP
BY year) b
ON b.year = a.year AND b.total = a.total
GROUP BY a.total, a.year
ORDER BY year DESC;
+-------------------------------------+-------+------+
| names | total | year |
+-------------------------------------+-------+------+
| Fentanyl,Codeine,Morphine | 160 | 2013 |
| Codeine | 80 | 2012 |
| Morphine,Methadone | 120 | 2011 |
| Methadone | 240 | 2010 |
| Morphine,Methadone,Fentanyl,Codeine | 120 | 2009 |
| Morphine,Codeine | 280 | 2008 |
| Morphine,Fentanyl | 40 | 2006 |
| Codeine,Methadone,Fentanyl | 60 | 2005 |
| Methadone,Fentanyl | 240 | 2004 |
+-------------------------------------+-------+------+
Here's a variant of my Scalar-Aggregate Comparison query technique that will achieve what you want in a vastly better-performing query than self-joining the aggregate results. As you can see below, this only ever performs a single join, then 3 cascaded layers of aggregation to achieve the final output.
SELECT
yr,
SUBSTRING(MAX(CONCAT(LPAD(total_sales, 16, '0'), products)), 17) AS best_seller,
MAX(total_sales) AS sales_amount
FROM (
SELECT
yr,
total_sales,
GROUP_CONCAT(name order by name) AS products
FROM (
SELECT
YEAR(s.created_at) AS yr,
p.id,
p.name,
SUM(s.units * p.unit_price) AS total_sales
FROM Product AS p
INNER JOIN Sale AS s on s.product_id = p.id
GROUP BY YEAR(s.created_at), p.id
) AS pys
GROUP BY yr, total_sales
) AS py
GROUP BY yr
ORDER BY yr DESC;
Here's a DB Fiddle demo: https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=e14ed7f396738ce064f1998bdb4310ac
try this:
Select name, year(created_at), sum(unit_price*units )
from Product p inner join sale s on p.id = s.product_id
group by name, year(created_at)
order by 2, 3 desc
If you want the top selling product for each year, use rank() and an aggregation query:
select ps.*
from (select p.name, year(s.created_at) as year, sum(p.unit_price*s.units) as sales
rank() over (partition by year(s.created_at) order by sum(p.unit_price*s.units) desc as seqnum
from Product p inner join
sale s
on p.id = s.product_id
group by name, year
) ps
where seqnum <= 1; -- You can change "1" for more rows

Mysql get total of matched items from two tables

I have two tables as given below...
INSERT INTO `tbl_project_skills` (`id`, `projectId`, `skillId`) VALUES
(17, 2, 44),
(27, 2, 43),
(31, 2, 78),
(33, 142, 79),
(35, 123, 88);
INSERT INTO `tbl_user_skills` (`id`, `userId`, `skillId`) VALUES
(2, 1, 44),
(3, 1, 78),
(5, 23, 45),
(6, 1, 36),
(7, 23, 88);
I want to find that how many skills are matched of user with each project like below
-----------------------------------------------------
userId | projectId | number_of_matched_skills
-----------------------------------------------------
1 | 2 | 2
1 | 142 | 0
1 | 123 | 0
23 | 2 | 0
23 | 142 | 0
23 | 123 | 1
------------------------------------------------------
I've tried below solution for the same...
SELECT ps.projectId, us.userId, ps.skillId AS pskillid, us.skillId AS uskillid, #points_veri:= (CASE WHEN '44' IN(ps.skillId) THEN 1 ELSE 0 END) + (CASE WHEN '43' IN(ps.skillId) THEN 1 ELSE 0 END) + (CASE WHEN '78' IN(ps.skillId) THEN 1 ELSE 0 END) AS number_of_matched_skills FROM tbl_user_skills AS us, tbl_project_skills AS ps
But I can't get it right. Hope you guys can give a hint.
I think you may have another table for store user and project, or this query will need get userId from tbl_user_skills and projectId from tbl_project_skills first, then do cross join, then join tbl_user_skills and tbl_project_skills to do aggregation:
select
main.userId,
main.projectId,
count(ps.projectId) as number_of_matched_skills
from (
select u.userId, p.projectId
from (
select distinct projectId from tbl_project_skills
) p
cross join (
select distinct userId from tbl_user_skills
) u
) main
left join tbl_user_skills us on us.userId = main.userId
left join tbl_project_skills ps on ps.skillId = us.skillId and main.projectId = ps.projectId
group by main.userId, main.projectId
Here is a demo in rextester.com.
Another option without having another table. Make an OUTER JOIN and then a GROUP BY on skillId.
SELECT userid,
projectid,
SUM(CASE WHEN p.skillId>1 AND u.skillId>1 THEN 1 ELSE 0 END) AS skills
FROM tbl_user_skills u
FULL OUTER JOIN tbl_project_skills p
ON u.skillId = p.skillId
GROUP BY userid, projectid
ORDER BY skills DESC

How do I get the sum of each persons best ten scores of the season?

I have a database of results for a season of 25 games. However only each persons best ten scores count.
Can anybody tell me how to sum just the top ten scores of each person AND show the minimum score that was used in that sum (their tenth best).
The database has PlayerName, TournamentID, Points
eg.
- TounamentID PlayerName Points
- 1 Jo 100
- 1 Tel 50
- 1 Kevin 75
- 2 Jo 100
- 2 Tel 50
- 2 Kevin 75
- 3 Jo 100
- 3 Tel 50
- 3 Kevin 75
- 4 Jo 100
- 4 Tel 50
- 4 Kevin 75
- 5 Jo 100
- 5 Tel 50
- 5 Kevin 75 etc
Many thanks in advance
EDIT 1
At the moment I have this kind of working though it doesn't handle duplicate scores very well and can actual end up adding up the top 11 if there's a duplicate;
SELECT X.PlayerName, Sum(X.Points) AS SumOfPoints, Min(X.Points) AS Target
FROM SoP11PreBats AS X
WHERE (((10)>(SELECT count(*)
FROM SoP11PreBats
WHERE PlayerName = X.PlayerName
AND Points > X.Points )))
GROUP BY X.PlayerName
ORDER BY Sum(X.Points) DESC;
Something like this would work for one player at a time:
SELECT SUM(n), MIN(n) FROM
(SELECT points AS n
FROM table
WHERE PlayerName = ?
ORDER BY n DESC
LIMIT 10
)
I'm not sure how to expand it to produce a table for every player.
SELECT test.playername, sum(top10.score), MIN(top10.score)
FROM test
LEFT JOIN (SELECT playername, score FROM test ORDER BY score DESC LIMIT 10) top10
ON top10.playername = test.playername
GROUP BY test.playername
Edit: Turns out the above approach using a subselect and join is not going to do the trick. Because you limit the results in the subselect it is not going to lead to a set of max 10 records PER playername.
The next approach would be to do something like
SELECT pk, name, score from test where
pk IN (SELECT pk FROM test t2 WHERE t2.name = name ORDER BY score DESC LIMIT 10)
This could create the proper set of records to join with. However LIMIT is not supported inside an IN clause (yet). So this won't compile.
EIDT2: The final approach I can think of goes like this:
select distinct test.name
, (SELECT sum(t2.score) FROM (select t3.score FROM test t3 WHERE t3.name = test.name ORDER BY score desc LIMIT 10) t2)
, (SELECT min(t2.score) FROM (select t3.score FROM test t3 WHERE t3.name = test.name ORDER BY score desc LIMIT 10) t2)
FROM test
This one also won't compile because the most inner test.name in the where can't be resolved properly.
I don't think that what you want to do can currently be done in 1 single query on mysql, but I'm curious if someone can proof me wrong.
Ok I got it working but it's about the most nasty sql hack I could think of and I'm not sure you should even consider to put it in production like this. It also only runs on mysql:
select PlayerName, sum(Points), min(Points) from
(select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 1,1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 2,1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 3,1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 4,1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 5,1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 6,1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 7,1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 8,1) as Points FROM test
UNION ALL
select distinct SoP11PreBats.PlayerName, (SELECT t2.Points FROM test t2 WHERE t2.PlayerName = SoP11PreBats.PlayerName ORDER BY Points desc LIMIT 9,1) as Points FROM test
) top10
group by name
I have a feeling this will not work:
SELECT *
FROM
( SELECT pd.PlayerName
, ( SELECT SUM(t10.Points)
FROM
( SELECT t10.Points
FROM SoP11PreBats AS t10
WHERE t10.PlayerName = pd.PlayerName
ORDER BY t10.Points DESC
LIMIT 10
) AS x
) AS Sum10
, ( SELECT t10.Points
FROM SoP11PreBats AS t10
WHERE t10.PlayerName = pd.PlayerName
ORDER BY t10.Points DESC
LIMIT 1 OFFSET 9
) AS TenthBest
FROM
( SELECT DISTINCT PlayerName
FROM SoP11PreBats
) AS pd
) AS y
ORDER BY Sum10 DESC
But this will:
SELECT pb.PlayerName AS PlayerName
, COALESCE(SUM(p.Points),0) + pb.TenthBest*(10-COUNT(p.Points))
AS SumOfPoints
, pb.TenthBest AS Target
FROM
( SELECT pd.PlayerName
, ( SELECT t10.Points
FROM SoP11PreBats AS t10
WHERE t10.PlayerName = pd.PlayerName
ORDER BY t10.Points DESC
LIMIT 1 OFFSET 9
) AS TenthBest
FROM
( SELECT DISTINCT PlayerName
FROM SoP11PreBats
) AS pd
) AS pb
LEFT JOIN SoP11PreBats AS p
ON p.PlayerName = pb.PlayerName
AND p.Points > pb.TenthBest
GROUP BY pb.PlayerName
ORDER BY SumOfPoints DESC
This works (see test output below):
set #count:=0, #player:='';
SELECT
PlayerName,
SUM(Points) as sum_top_10,
MIN(Points) as min_top_10
FROM (SELECT PlayerName, Points
FROM (SELECT
Points,
#count := if (#player != PlayerName, 0, #count + 1) as count,
#player := PlayerName as PlayerName
FROM (SELECT PlayerName, Points FROM SoP11PreBATS order by 1, 2 desc) x) y
where count < 10) z
group by 1;
Here's the test, using OP's data, plus extra rows for 'Jo' to make more than 10 rows:
create table SoP11PreBATS (TounamentID int, PlayerName text, Points int);
delete from SoP11PreBATS;
insert into SoP11PreBATS values
(1, 'Jo', 100), (1, 'Tel', 50), (1, 'Kevin', 75), (2, 'Jo', 100), (2, 'Tel', 50),
(2, 'Kevin', 75), (3, 'Jo', 100), (3, 'Tel', 50), (3, 'Kevin', 75), (4, 'Jo', 100),
(4, 'Tel', 50), (4, 'Kevin', 75), (5, 'Jo', 100), (5, 'Tel', 50), (5, 'Kevin', 75),
(5, 'Jo', 50), (6, 'Jo', 75), (7, 'Jo', 100), (8, 'Jo', 50), (9, 'Jo', 75),
(10, 'Jo', 50), (11, 'Jo', 75), (12, 'Jo', 100);
select * from SoP11PreBATS where playername = 'Jo' order by points desc;
+-------------+------------+--------+
| TounamentID | PlayerName | Points |
+-------------+------------+--------+
| 1 | Jo | 100 |
| 2 | Jo | 100 |
| 3 | Jo | 100 |
| 4 | Jo | 100 |
| 5 | Jo | 100 |
| 7 | Jo | 100 |
| 12 | Jo | 100 |
| 6 | Jo | 75 |
| 9 | Jo | 75 |
| 11 | Jo | 75 |
| 5 | Jo | 50 |
| 8 | Jo | 50 |
| 10 | Jo | 50 |
+-------------+------------+--------+
-- Inspection shows Jo should have 925 as sum and 75 as min
-- Ran query above and got:
+------------+------------+------------+
| PlayerName | sum_top_10 | min_top_10 |
+------------+------------+------------+
| Jo | 925 | 75 |
| Kevin | 375 | 75 |
| Tel | 250 | 50 |
+------------+------------+------------+
-- Test output correct
I think this works, and it only uses one derived table:
SELECT #row := 0, #pp := NULL, #min := 0;
SELECT Player,
SUM(Points) AS Points,
MIN(Points) AS MinPoints
FROM (
SELECT Player,
Points,
#row := IF(
IFNULL(#pp, '') <> Player AND NOT (#pp := Player),
1,
#row + 1
) AS Row
FROM SoP11PreBats
ORDER BY Player, Points DESC
) tmp
WHERE tmp.Row <= 10
GROUP BY Player;
Test data:
CREATE TABLE `SoP11PreBats` (
`TournamentID` int(11) NOT NULL,
`Player` varchar(255) NOT NULL,
`Points` int(11) NOT NULL
);
INSERT INTO SoP11PreBats (TournamentID, Player, Points) VALUES
(15, 'Jo', 10),
(14, 'Jo', 20),
(13, 'Jo', 30),
(12, 'Jo', 40),
(11, 'Jo', 50),
(10, 'Jo', 60),
( 9, 'Jo', 70),
( 8, 'Jo', 80),
( 7, 'Jo', 90),
( 6, 'Jo', 100),
( 5, 'Jo', 110),
( 4, 'Jo', 120),
( 3, 'Jo', 130),
( 2, 'Jo', 140),
( 1, 'Jo', 150),
( 1, 'Tel', 15),
( 2, 'Tel', 25),
( 3, 'Tel', 35),
( 4, 'Tel', 45),
( 5, 'Tel', 55),
( 6, 'Tel', 65),
( 7, 'Tel', 75),
( 8, 'Tel', 85),
( 9, 'Tel', 95),
(10, 'Tel', 105),
(11, 'Tel', 115),
(12, 'Tel', 125),
(13, 'Tel', 135),
(14, 'Tel', 145),
(15, 'Tel', 155),
( 1, 'Kevin', 10),
( 2, 'Kevin', 20),
( 3, 'Kevin', 30),
( 4, 'Kevin', 40),
( 5, 'Kevin', 50),
( 6, 'Kevin', 60),
( 7, 'Kevin', 70),
( 8, 'Kevin', 80),
( 9, 'Kevin', 90),
(10, 'Kevin', 100),
(11, 'Kevin', 110),
(12, 'Kevin', 120),
(13, 'Kevin', 130),
(14, 'Kevin', 140),
(15, 'Kevin', 150);
Result:
+--------+--------+-----------+
| Player | Points | MinPoints |
+--------+--------+-----------+
| Jo | 1050 | 60 |
| Kevin | 1050 | 60 |
| Tel | 1100 | 65 |
+--------+--------+-----------+