Получить первые N записей для каждой группы сгруппированного результаты
ниже приведен самый простой возможный пример, хотя любое решение должно быть в состоянии масштабироваться до того, сколько N лучших результатов необходимо:
учитывая таблицу, подобную приведенной ниже, с колонками person, group и age, как бы вы получить 2 старейших людей в каждой группе? (связи внутри групп не должны давать больше результатов, но дают первые 2 в алфавитном порядке)
+--------+-------+-----+
| Person | Group | Age |
+--------+-------+-----+
| Bob | 1 | 32 |
| Jill | 1 | 34 |
| Shawn | 1 | 42 |
| Jake | 2 | 29 |
| Paul | 2 | 36 |
| Laura | 2 | 39 |
+--------+-------+-----+
желаемого результата:
+--------+-------+-----+
| Shawn | 1 | 42 |
| Jill | 1 | 34 |
| Laura | 2 | 39 |
| Paul | 2 | 36 |
+--------+-------+-----+
Примечание: этот вопрос основан на предыдущем-получить записи с максимальным значением для каждой группы сгруппированных результатов SQL - для получения одной верхней строки из каждой группы, и которая получила отличный MySQL-конкретный ответ от @Bohemian:
select *
from (select * from mytable order by `Group`, Age desc, Person) x
group by `Group`
хотел бы иметь возможность построить это, хотя я не вижу, как.
10 ответов:
вот один из способов сделать это, используя
UNION ALL(см. SQL Скрипка с демо). Это работает с двумя группами, если у вас есть более двух групп, то вам нужно будет указатьgroupколичество и добавить запросы для каждогоgroup:( select * from mytable where `group` = 1 order by age desc LIMIT 2 ) UNION ALL ( select * from mytable where `group` = 2 order by age desc LIMIT 2 )есть множество способов сделать это, смотрите эту статью, чтобы определить лучший маршрут для вашего ситуация:
http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/
Edit:
Это может работать и для вас тоже, он генерирует номер строки для каждой записи. Используя пример из ссылки выше, это вернет только те записи с номером строки меньше или равным 2:
select person, `group`, age from ( select person, `group`, age, (@num:=if(@group = `group`, @num +1, if(@group := `group`, 1, 1))) row_number from test t CROSS JOIN (select @num:=0, @group:=null) c order by `Group`, Age desc, person ) as x where x.row_number <= 2;посмотреть демо
в других базах данных вы можете сделать это с помощью
ROW_NUMBER. MySQL не поддерживаетROW_NUMBERно вы можете использовать переменные, чтобы подражать это:SELECT person, groupname, age FROM ( SELECT person, groupname, age, @rn := IF(@prev = groupname, @rn + 1, 1) AS rn, @prev := groupname FROM mytable JOIN (SELECT @prev := NULL, @rn := 0) AS vars ORDER BY groupname, age DESC, person ) AS T1 WHERE rn <= 2смотрите, как он работает в интернете:sqlfiddle
Edit я только что заметил, что bluefeet опубликовал очень похожий ответ: +1 к нему. Однако этот ответ имеет два небольших преимущества:
- это один запрос. Переменные инициализируются внутри заявление.
- он обрабатывает связей, как описано в вопросе (в алфавитном порядке по имени).
поэтому я оставлю его здесь, если он может кому-то помочь.
попробуйте это:
SELECT a.person, a.group, a.age FROM person AS a WHERE (SELECT COUNT(*) FROM person AS b WHERE b.group = a.group AND b.age >= a.age) <= 2 ORDER BY a.group ASC, a.age DESC
как насчет использования самостоятельной работы:
CREATE TABLE mytable (person, groupname, age); INSERT INTO mytable VALUES('Bob',1,32); INSERT INTO mytable VALUES('Jill',1,34); INSERT INTO mytable VALUES('Shawn',1,42); INSERT INTO mytable VALUES('Jake',2,29); INSERT INTO mytable VALUES('Paul',2,36); INSERT INTO mytable VALUES('Laura',2,39); SELECT a.* FROM mytable AS a LEFT JOIN mytable AS a2 ON a.groupname = a2.groupname AND a.age <= a2.age GROUP BY a.person HAVING COUNT(*) <= 2 ORDER BY a.groupname, a.age DESC;дает мне:
a.person a.groupname a.age ---------- ----------- ---------- Shawn 1 42 Jill 1 34 Laura 2 39 Paul 2 36меня сильно вдохновил ответ Билла Карвина на выберите 10 лучших записей для каждой категории
кроме того, я использую SQLite, но это должно работать на MySQL.
другое дело: в приведенном выше, Я заменил с
groupnameколонка для удобства.Edit:
продолжение операции комментарий по поводу недостающих результатов галстук, я увеличивается на ответ snuffin, чтобы показать все связи. Это означает, что если последние из них являются связями, может быть возвращено более 2 строк, как показано ниже:
.headers on .mode column CREATE TABLE foo (person, groupname, age); INSERT INTO foo VALUES('Paul',2,36); INSERT INTO foo VALUES('Laura',2,39); INSERT INTO foo VALUES('Joe',2,36); INSERT INTO foo VALUES('Bob',1,32); INSERT INTO foo VALUES('Jill',1,34); INSERT INTO foo VALUES('Shawn',1,42); INSERT INTO foo VALUES('Jake',2,29); INSERT INTO foo VALUES('James',2,15); INSERT INTO foo VALUES('Fred',1,12); INSERT INTO foo VALUES('Chuck',3,112); SELECT a.person, a.groupname, a.age FROM foo AS a WHERE a.age >= (SELECT MIN(b.age) FROM foo AS b WHERE (SELECT COUNT(*) FROM foo AS c WHERE c.groupname = b.groupname AND c.age >= b.age) <= 2 GROUP BY b.groupname) ORDER BY a.groupname ASC, a.age DESC;дает мне:
person groupname age ---------- ---------- ---------- Shawn 1 42 Jill 1 34 Laura 2 39 Paul 2 36 Joe 2 36 Chuck 3 112
зацени вот это:
SELECT p.Person, p.`Group`, p.Age FROM people p INNER JOIN ( SELECT MAX(Age) AS Age, `Group` FROM people GROUP BY `Group` UNION SELECT MAX(p3.Age) AS Age, p3.`Group` FROM people p3 INNER JOIN (SELECT MAX(Age) AS Age, `Group` FROM people GROUP BY `Group`) p4 ON p3.Age < p4.Age AND p3.`Group` = p4.`Group` GROUP BY `Group` ) p2 ON p.Age = p2.Age AND p.`Group` = p2.`Group` ORDER BY `Group`, Age DESC, Person;Скрипка SQL:http://sqlfiddle.com/#! 2 / cdbb6/15
Snuffin решение кажется довольно медленным, чтобы выполнить, когда у вас есть много строк и Марк Байерс/Рик Джеймс и Bluefeet решений не работает в моем энвайронмент (в MySQL 5.6), потому что порядок применяется после исполнения выбрать, так это вариант Марк Байерс/Рик Джеймс решения, чтобы исправить эту проблему (за дополнительную черепитчато выбор):
select person, groupname, age from ( select person, groupname, age, (@rn:=if(@prev = groupname, @rn +1, 1)) as rownumb, @prev:= groupname from ( select person, groupname, age from persons order by groupname , age desc, person ) as sortedlist JOIN (select @prev:=NULL, @rn :=0) as vars ) as groupedlist where rownumb<=2 order by groupname , age desc, person;Я пробовал подобный запрос в таблице, имеющей 5 миллионов строк, и он возвращает результат менее чем за 3 секунды
Если другие ответы не достаточно быстро дать код попробуй:
SELECT province, n, city, population FROM ( SELECT @prev := '', @n := 0 ) init JOIN ( SELECT @n := if(province != @prev, 1, @n + 1) AS n, @prev := province, province, city, population FROM Canada ORDER BY province ASC, population DESC ) x WHERE n <= 3 ORDER BY province, n;выход:
+---------------------------+------+------------------+------------+ | province | n | city | population | +---------------------------+------+------------------+------------+ | Alberta | 1 | Calgary | 968475 | | Alberta | 2 | Edmonton | 822319 | | Alberta | 3 | Red Deer | 73595 | | British Columbia | 1 | Vancouver | 1837970 | | British Columbia | 2 | Victoria | 289625 | | British Columbia | 3 | Abbotsford | 151685 | | Manitoba | 1 | ...
Я хотел поделиться этим, потому что я потратил много времени на поиск простого способа реализовать это в программе java, над которой я работаю. Это не совсем дает выход, который вы ищете, но его близко. Функция в mysql называется
GROUP_CONCAT()работал очень хорошо для указания, сколько результатов возвращать в каждой группе. ИспользуяLIMITили любой другой причудливый способ попытаться сделать это сCOUNTне работает для меня. Поэтому, если вы готовы принять измененный вывод, это отличное решение. Допустим, у меня есть таблица под названием "студент" с идентификаторами студентов, их полом и gpa. Допустим, я хочу возглавить 5 gpas для каждого пола. Тогда я могу написать запрос вот такSELECT sex, SUBSTRING_INDEX(GROUP_CONCAT(cast(gpa AS char ) ORDER BY gpa desc), ',',5) AS subcategories FROM student GROUP BY sex;обратите внимание, что параметр ' 5 ' сообщает ему, сколько записей для объединения в каждую строку
и выход будет выглядеть примерно так
+--------+----------------+ | Male | 4,4,4,4,3.9 | | Female | 4,4,3.9,3.9,3.8| +--------+----------------+вы также можете изменить
ORDER BYпеременная и упорядочить их по-другому. Поэтому, если бы у меня был возраст студента, я мог бы заменить "GPA desc" с "возрастом desc" и это будет работать! Вы также можете добавить переменные в инструкцию group by, чтобы получить больше столбцов в выходных данных. Так что это просто способ я нашел, что это довольно гибкий и работает хорошо, если у вас все в порядке с результатами.
в SQL Server
row_numer()- это мощная функция, которая может легко получить результат, как показано нижеselect Person,[group],age from ( select * ,row_number() over(partition by [group] order by age desc) rn from mytable ) t where rn <= 2
есть очень хороший ответ на эту проблему на MySQL-как получить верхние N строк на каждую группу
на основе решения в ссылочной ссылке, ваш запрос будет выглядеть так:
SELECT Person, Group, Age FROM (SELECT Person, Group, Age, @group_rank := IF(@group = Group, @group_rank + 1, 1) AS group_rank, @current_group := Group FROM `your_table` ORDER BY Group, Age DESC ) ranked WHERE group_rank <= `n` ORDER BY Group, Age DESC;здесь
nэтоtop nиyour_table- имя вашей таблицы.Я думаю, что объяснение в ссылке очень понятно. Для быстрого ознакомления я скопирую и вставлю его здесь:
в настоящее время MySQL не поддерживает ROW_NUMBER () функция, которая может назначить порядковый номер внутри группы, но в качестве временного решения мы можем использовать MySQL переменные сессий.
эти переменные не требуют объявления и могут быть использованы в запросе делать расчеты и хранить промежуточные результаты.
@current_country := country этот код выполняется для каждой строки и сохраняет значение столбца страна в переменной @current_country.
@country_rank := IF (@current_country = страна, @country_rank + 1, 1) В этом коде, если @current_country то же самое мы увеличиваем ранг, в противном случае установите его в 1. Для первой строки @current_country равно NULL, поэтому ранг тоже равен 1.
для правильного ранжирования, мы должны иметь порядок по стране, население DESC
Comments