MYSQL - Group by limit

前端 未结 3 1820
太阳男子
太阳男子 2020-12-06 16:48

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

相关标签:
3条回答
  • 2020-12-06 17:23

    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
    
    0 讨论(0)
  • 2020-12-06 17:28
    SUBSTRING_INDEX(
        GROUP_CONCAT(expr1 ORDER BY expr2 SEPARATOR ";"),
        ";",
        2  /* the GROUP_LIMIT */
    )
    

    expr1 can be like CONCAT(...). Involve REPLACE to hide any ";".

    0 讨论(0)
  • 2020-12-06 17:35

    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;
    
    0 讨论(0)
提交回复
热议问题