Voici l'exemple le plus simple possible, même si toute solution doit pouvoir s'adapter à tous les n résultats souhaités:
Avec un tableau comme celui ci-dessous, avec les colonnes personne, groupe et âge, comment voulez-vous obtenir les 2 personnes les plus âgées de chaque groupe? _ (Les liens au sein des groupes ne devraient pas donner plus de résultats, mais donner les 2 premiers ordre alphabétique)
+ -------- + ------- + ----- + | Personne | Groupe | Âge | + -------- + ------- + ----- + | Bob | 1 | 32 | | Jill | 1 | 34 | | Shawn | 1 | 42 | | Jake | 2 | 29 | | Paul | 2 | 36 | | Laura | 2 | 39 | + -------- + ------- + ----- +
Résultat souhaité:
+ -------- + ------- + ----- + | Shawn | 1 | 42 | | Jill | 1 | 34 | | Laura | 2 | 39 | | Paul | 2 | 36 | + -------- + ------- + ----- +
NOTE: Cette question est basée sur une précédente- Obtenir les enregistrements avec une valeur maximale pour chaque groupe de résultats SQL groupés - pour obtenir une seule ligne du haut de chaque groupe et qui a reçu un excellent résultat MySQL- réponse spécifique de @ Bohemian:
select *
from (select * from mytable order by `Group`, Age desc, Person) x
group by `Group`
J'adorerais pouvoir me baser dessus, mais je ne vois pas comment.
Voici un moyen de le faire, en utilisant UNION ALL
(voir SQL Fiddle avec Demo ). Cela fonctionne avec deux groupes, si vous avez plus de deux groupes, vous devrez alors spécifier le nombre group
et ajouter des requêtes pour chaque 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
)
Il existe différentes façons de procéder. Consultez cet article pour déterminer le meilleur itinéraire pour votre situation:
http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/
Modifier:
Cela peut également fonctionner pour vous, cela génère un numéro de ligne pour chaque enregistrement. En utilisant un exemple tiré du lien ci-dessus, seuls les enregistrements dont le numéro de ligne est inférieur ou égal à 2 sont renvoyés:
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;
Voir Démo
Dans d'autres bases de données, vous pouvez le faire en utilisant ROW_NUMBER
. MySQL ne supporte pas ROW_NUMBER
mais vous pouvez utiliser des variables pour l'émuler:
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
Voyez le travail en ligne: sqlfiddle
Edit Je viens de remarquer que bluefeet a posté une réponse très similaire: +1 pour lui. Cependant, cette réponse présente deux petits avantages:
Je vais donc le laisser ici au cas où cela pourrait aider quelqu'un.
Essaye ça:
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
Que diriez-vous d'utiliser l'auto-adhésion:
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;
donne moi:
a.person a.groupname a.age
---------- ----------- ----------
Shawn 1 42
Jill 1 34
Laura 2 39
Paul 2 36
La réponse de Bill Karwin à Sélectionne les 10 meilleurs disques de chaque catégorie
De plus, j'utilise SQLite, mais cela devrait fonctionner sous MySQL.
Autre chose: dans ce qui précède, j'ai remplacé la colonne group
par une colonne groupname
pour plus de commodité.
Modifier:
Pour donner suite au commentaire du PO concernant les résultats manquants, j'ai incrémenté la réponse de snuffin pour montrer tous les liens. Cela signifie que si les derniers sont des liens, plus de 2 lignes peuvent être retournées, comme indiqué ci-dessous:
.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;
donne moi:
person groupname age
---------- ---------- ----------
Shawn 1 42
Jill 1 34
Laura 2 39
Paul 2 36
Joe 2 36
Chuck 3 112
Regarde ça:
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;
Fiddle SQL: http://sqlfiddle.com/#!2/cdbb6/15
La solution Snuffin semble assez lente à exécuter lorsque vous avez beaucoup de lignes et que Mark Byers/Rick James et les solutions Bluefeet ne fonctionnent pas sur mon environnement (MySQL 5.6) car order by est appliqué après l'exécution de select, voici donc une variante solutions de Marc Byers/Rick James pour résoudre ce problème (avec une sélection extra imbriquée):
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;
J'ai essayé une requête similaire sur une table ayant 5 millions de lignes et elle renvoie le résultat en moins de 3 secondes
Si les autres réponses ne sont pas assez rapides, donnez ce code a essayez:
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;
Sortie:
+---------------------------+------+------------------+------------+
| 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 | ...
Je voulais partager cela parce que j'ai passé beaucoup de temps à chercher un moyen simple de l'implémenter dans un programme Java sur lequel je travaille. Cela ne donne pas tout à fait la sortie que vous recherchez mais sa fin. La fonction dans mysql appelée GROUP_CONCAT()
fonctionnait très bien pour spécifier le nombre de résultats à renvoyer dans chaque groupe. L'utilisation de LIMIT
ou de l'un des autres moyens sophistiqués d'essayer de le faire avec COUNT
n'a pas fonctionné pour moi. Donc, si vous êtes prêt à accepter une sortie modifiée, c'est une excellente solution. Disons que j'ai une table appelée 'student' avec les identifiants d'étudiant, leur sexe et leur gpa. Disons que je veux top 5 gpas pour chaque sexe. Ensuite, je peux écrire la requête comme ceci
SELECT sex, SUBSTRING_INDEX(GROUP_CONCAT(cast(gpa AS char ) ORDER BY gpa desc), ',',5)
AS subcategories FROM student GROUP BY sex;
Notez que le paramètre '5' indique le nombre d'entrées à concaténer dans chaque ligne.
Et la sortie ressemblerait à quelque chose comme
+--------+----------------+
| Male | 4,4,4,4,3.9 |
| Female | 4,4,3.9,3.9,3.8|
+--------+----------------+
Vous pouvez également modifier la variable ORDER BY
et les classer différemment. Donc, si j'avais l'âge de l'étudiant, je pourrais remplacer le "gpa desc" par "age desc" et cela fonctionnera! Vous pouvez également ajouter des variables à l'instruction group by pour obtenir plus de colonnes dans la sortie. Donc, c’est une des solutions que j’ai trouvées, qui est assez flexible et qui fonctionne bien si vous n’êtes pas mal à lister les résultats.
Dans SQL Server, row_numer()
est une fonction puissante qui permet d’obtenir facilement les résultats décrits ci-dessous.
select Person,[group],age
from
(
select * ,row_number() over(partition by [group] order by age desc) rn
from mytable
) t
where rn <= 2