Ниже приведен простейший пример, хотя любое решение должно иметь возможность масштабирования до любого количества n лучших результатов:
Учитывая таблицу, подобную приведенной ниже, со столбцами «человек», «группа» и «возраст», как бы вы могли получить 2 самых старых человека в каждой группе? (Связи внутри групп не должны приводить к большему количеству результатов, но дают первые 2 в алфавитном порядке)
+ -------- + ------- + ----- + | Человек | Группа | Возраст | + -------- + ------- + ----- + | Боб | 1 | 32 | | Джилл | 1 | 34 | | Шон | 1 | 42 | | Джейк | 2 | 29 | | Пол | 2 | 36 | | Лаура | 2 | 39 | + -------- + ------- + ----- +
Желаемый результат:
+ -------- + ------- + ----- + | Шон | 1 | 42 | | Джилл | 1 | 34 | | Лаура | 2 | 39 | | Пол | 2 | 36 | + -------- + ------- + ----- +
ПРИМЕЧАНИЕ. Этот вопрос основан на предыдущем - Получить записи с максимальным значением для каждой группы сгруппированных результатов SQL - для получения одной верхней строки из каждой группы, на который был получен отличный ответ для MySQL от @Bohemian:
select *
from (select * from mytable order by `Group`, Age desc, Person) x
group by `Group`
Хотел бы иметь возможность строить на этом, хотя я не понимаю, как.
Ответы:
Вот один из способов сделать это, используя
UNION ALL
(См. SQL Fiddle с демонстрацией ). Это работает с двумя группами, если у вас больше двух групп, вам нужно будет указать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/
Редактировать:
Это может сработать и для вас, он генерирует номер строки для каждой записи. Используя пример из приведенной выше ссылки, будут возвращены только те записи, номер строки которых меньше или равен 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;
См. Демонстрацию
источник
SELECT
(и, фактически, иногда оценивает их не по порядку). Ключ к решению - поместить все назначения переменных в одно выражение; вот пример: stackoverflow.com/questions/38535020/… .В других базах данных это можно сделать с помощью
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 ему. Однако у этого ответа есть два небольших преимущества:
Так что я оставлю его здесь на случай, если он кому-то поможет.
источник
JOIN (SELECT @prev := NULL, @rn := 0) AS vars
. Я понимаю, что нужно объявить пустые переменные, но для MySql это кажется лишним.Попробуй это:
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
ДЕМО
источник
a.person
.Как насчет использования самосоединения:
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.
Другое дело: в приведенном выше я заменил
group
столбец наgroupname
столбец для удобства.Редактировать :
Следуя за комментарием ОП относительно недостающих результатов ничьей, я увеличил ответ Снаффина, чтобы показать все связи. Это означает, что если последние являются связями, может быть возвращено более 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
источник
ERROR 1242 (21000): Subquery returns more than 1 row
, предположительно, из-за файлаGROUP BY
. Когда я выполняю толькоSELECT MIN
подзапрос, он генерирует три строки:34, 39, 112
и оказывается, что второе значение должно быть 36, а не 39.Решение Snuffin кажется довольно медленным для выполнения, когда у вас много строк, а решения Mark Byers / Rick James и Bluefeet не работают в моей среде (MySQL 5.6), потому что порядок по применяется после выполнения select, поэтому вот вариант решений Marc Byers / Rick James для устранения этой проблемы (с дополнительным фрагментированным выбором):
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 секунды.
источник
LIMIT 9999999
в любую производную таблицу с расширениемORDER BY
. Это может предотвратитьORDER BY
игнорирование.Проверь это:
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
источник
max(internal_version - 1)
- Так что меньше стресса :)Если другие ответы недостаточно быстры, попробуйте этот код :
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 лучших баллов для каждого пола. Тогда я могу написать такой запрос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» на «age desc», и это сработает! Вы также можете добавить переменные в группу по оператору, чтобы получить больше столбцов в выводе. Я обнаружил, что это довольно гибкий способ, который хорошо работает, если вас устраивает просто перечисление результатов.источник
В 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
это имя вашей таблицы.Я думаю, что объяснение в ссылке действительно ясное. Для быстрого ознакомления я скопирую и вставлю его сюда:
источник
SELECT p1.Person, p1.`GROUP`, p1.Age FROM person AS p1 WHERE ( SELECT COUNT( DISTINCT ( p2.age ) ) FROM person AS p2 WHERE p2.`GROUP` = p1.`GROUP` AND p2.Age >= p1.Age ) < 2 ORDER BY p1.`GROUP` ASC, p1.age DESC
ссылка leetcode
источник