Is there a simple way to LIMIT the GROUP BY results to the top 2. The following query returns all the results. Using \'LIMIT 2\' reduces the overall list to the top 2 entr
I don't think that there is a simple way in MySQL. One way to do this is by generating a row number for each row partitioned in groups by rating_name, and then only select the rows with row_number 2 or less. In most databases you could do this using something like:
SELECT * FROM (
SELECT
rating_name,
etc...,
ROW_NUMBER() OVER (PARTITION BY rating_name ORDER BY good) AS rn
FROM your_table
) T1
WHERE rn <= 2
Unfortunately, MySQL doesn't support the ROW_NUMBER
syntax. You can however simulate ROW_NUMBER
using variables:
SELECT
rating_name, id_markets, good, neutral, bad
FROM (
SELECT
*,
@rn := CASE WHEN @prev_rating_name = rating_name THEN @rn + 1 ELSE 1 END AS rn,
@prev_rating_name := rating_name
FROM (
SELECT
rating_name,
id_markets,
SUM(COALESCE(rating_good, 0)) AS good,
SUM(COALESCE(rating_neutral, 0)) AS neutral,
SUM(COALESCE(rating_bad, 0)) AS bad
FROM zzratings
WHERE rating_year = YEAR(CURDATE()) AND rating_week = WEEK(CURDATE(), 1)
GROUP BY rating_name, id_markets
) AS T1, (SELECT @prev_rating_name := '', @rn := 0) AS vars
ORDER BY rating_name, good DESC
) AS T2
WHERE rn <= 2
ORDER BY rating_name, good DESC
Result when run on your test data:
france 1 2 0 0 france 2 1 0 0 ireland 1 4 2 0 ireland 21 3 1 0 poland 1 3 1 0 poland 2 1 0 0
SUBSTRING_INDEX(
GROUP_CONCAT(expr1 ORDER BY expr2 SEPARATOR ";"),
";",
2 /* the GROUP_LIMIT */
)
expr1 can be like CONCAT(...). Involve REPLACE to hide any ";".
This is still possible via a single query, but it's a bit long, and there are some caveats, which I'll explain after the query. Though, they're not flaws in the query so much as some ambiguity in what "top two" means.
Here's the query:
SELECT ratings.* FROM
(SELECT rating_name,
id_markets,
sum(rating_good) 'good',
sum(rating_neutral)'neutral',
sum(rating_bad) 'bad'
FROM zzratings
WHERE rating_year=year(curdate()) AND rating_week = week(curdate(),1)
GROUP BY rating_name,id_markets) AS ratings
LEFT JOIN
(SELECT rating_name,
id_markets,
sum(rating_good) 'good',
sum(rating_neutral)'neutral',
sum(rating_bad) 'bad'
FROM zzratings
WHERE rating_year=year(curdate()) AND rating_week= week(curdate(),1)
GROUP BY rating_name,id_markets) AS ratings2
ON ratings2.good <= ratings.good AND
ratings2.id_markets <> ratings.id_markets AND
ratings2.rating_name = ratings.rating_name
LEFT JOIN
(SELECT rating_name,
id_markets,
sum(rating_good) 'good',
sum(rating_neutral)'neutral',
sum(rating_bad) 'bad'
FROM zzratings
WHERE rating_year=year(curdate()) AND rating_week= week(curdate(),1)
GROUP BY rating_name,id_markets) AS ratings3
ON ratings3.good >= ratings2.good AND
ratings3.id_markets <> ratings.id_markets AND
ratings3.id_markets <> ratings2.id_markets AND
ratings3.rating_name = ratings.rating_name
WHERE (ratings2.good IS NULL OR ratings3.good IS NULL) AND
ratings.good IS NOT NULL
ORDER BY ratings.rating_name, ratings.good DESC
The caveat is that if there is more than one id_market with the same "good" count for the same rating_name, then you will get more than two records. For example, if there are three ireland id_markets with a "good" count of 3, the highest, then how can you display the top two? You can't. So the query will show all three.
Also, if there were one count of "3", the highest, and two counts of "2", you couldn't show the top two, since you have a tie for second place, so the query shows all three.
The query will be simpler if you create a temporary table with the aggregate result set first, then work from that.
CREATE TEMPORARY TABLE temp_table
SELECT rating_name,
id_markets,
sum(rating_good) 'good',
sum(rating_neutral)'neutral',
sum(rating_bad) 'bad'
FROM zzratings
WHERE rating_year=year(curdate()) AND rating_week= week(curdate(),1;
SELECT ratings.*
FROM temp_table ratings
LEFT JOIN temp_table ratings2
ON ratings2.good <= ratings.good AND
ratings2.id_markets <> ratings.id_markets AND
ratings2.rating_name = ratings.rating_name
LEFT JOIN temp_table ratings3
ON ratings3.good >= ratings2.good AND
ratings3.id_markets <> ratings.id_markets AND
ratings3.id_markets <> ratings2.id_markets AND
ratings3.rating_name = ratings.rating_name
WHERE (ratings2.good IS NULL OR ratings3.good IS NULL) AND
ratings.good IS NOT NULL
ORDER BY ratings.rating_name, ratings.good DESC;