Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL Query to select bottom 2 from each category

In Mysql, I want to select the bottom 2 items from each category

Category Value
1        1.3
1        4.8
1        3.7
1        1.6
2        9.5
2        9.9
2        9.2
2        10.3
3        4
3        8
3        16

Giving me:

Category Value
1        1.3
1        1.6
2        9.5
2        9.2
3        4
3        8

Before I migrated from sqlite3 I had to first select a lowest from each category, then excluding anything that joined to that, I had to again select the lowest from each category. Then anything equal to that new lowest or less in a category won. This would also pick more than 2 in case of a tie, which was annoying... It also had a really long runtime.

My ultimate goal is to count the number of times an individual is in one of the lowest 2 of a category (there is also a name field) and this is the one part I don't know how to do. Thanks

like image 501
Dan Avatar asked Dec 17 '09 18:12

Dan


2 Answers

SELECT c1.category, c1.value
FROM catvals c1
LEFT OUTER JOIN catvals c2
  ON (c1.category = c2.category AND c1.value > c2.value)
GROUP BY c1.category, c1.value
HAVING COUNT(*) < 2;

Tested on MySQL 5.1.41 with your test data. Output:

+----------+-------+
| category | value |
+----------+-------+
|        1 |  1.30 |
|        1 |  1.60 |
|        2 |  9.20 |
|        2 |  9.50 |
|        3 |  4.00 |
|        3 |  8.00 |
+----------+-------+

(The extra decimal places are because I declared the value column as NUMERIC(9,2).)

Like other solutions, this produces more than 2 rows per category if there are ties. There are ways to construct the join condition to resolve that, but we'd need to use a primary key or unique key in your table, and we'd also have to know how you intend ties to be resolved.

like image 165
Bill Karwin Avatar answered Sep 18 '22 10:09

Bill Karwin


You could try this:

SELECT * FROM (
  SELECT c.*,
        (SELECT COUNT(*)
         FROM user_category c2
         WHERE c2.category = c.category
         AND c2.value < c.value) cnt
  FROM user_category c ) uc
WHERE cnt < 2

It should give you the desired results, but check if performance is ok.

like image 24
Peter Lang Avatar answered Sep 22 '22 10:09

Peter Lang