J'ai ce tableau pour les documents (version simplifiée ici):
+------+-------+--------------------------------------+
| id | rev | content |
+------+-------+--------------------------------------+
| 1 | 1 | ... |
| 2 | 1 | ... |
| 1 | 2 | ... |
| 1 | 3 | ... |
+------+-------+--------------------------------------+
Comment sélectionner une ligne par identifiant et uniquement le plus grand nombre de tours?
Avec les données ci-dessus, le résultat doit contenir deux lignes: [1, 3, ...]
et [2, 1, ..]
. J'utilise MySQL.
Actuellement, j'utilise des contrôles dans la boucle while
pour détecter et écraser les anciennes révs du jeu de résultats. Mais est-ce la seule méthode pour obtenir le résultat? N'y a-t-il pas une solution SQL?
Mettre à jour
Comme le suggèrent les réponses, il y a est une solution SQL et ici, une démonstration de sqlfiddle .
Mise à jour 2
J'ai remarqué qu'après avoir ajouté le sqlfiddle ci-dessus, le taux de vote favorable de la question a dépassé le taux de vote positif des réponses. Cela n'a pas été l'intention! Le violon est basé sur les réponses, en particulier sur la réponse acceptée.
Tout ce dont vous avez besoin est une clause GROUP BY
avec la fonction d'agrégation MAX
:
SELECT id, MAX(rev)
FROM YourTable
GROUP BY id
Je viens de remarquer que vous avez également besoin de la colonne content
.
C'est une question très courante en SQL: trouver toutes les données de la ligne avec une valeur maximale dans une colonne par identificateur de groupe. J'ai beaucoup entendu parler de ça au cours de ma carrière. En fait, c’était l’une des questions auxquelles j’ai répondu lors de l’entretien technique de mon emploi actuel.
En fait, il est si courant que la communauté StackOverflow ait créé une seule étiquette pour traiter des questions comme celle-ci: greatest-n-per-group .
En gros, vous avez deux approches pour résoudre ce problème:
group-identifier, max-value-in-group
Dans cette approche, vous trouvez d’abord le group-identifier, max-value-in-group
(déjà résolu ci-dessus) dans une sous-requête. Ensuite, vous joignez votre table à la sous-requête avec une égalité à la fois sur group-identifier
et max-value-in-group
:
SELECT a.id, a.rev, a.contents
FROM YourTable a
INNER JOIN (
SELECT id, MAX(rev) rev
FROM YourTable
GROUP BY id
) b ON a.id = b.id AND a.rev = b.rev
Dans cette approche, vous avez quitté rejoindre la table avec lui-même. L'égalité, bien sûr, va dans le group-identifier
. Ensuite, 2 mouvements intelligents:
NULL
dans la partie droite (c'est un LEFT JOIN
, vous vous souvenez?). Ensuite, nous filtrons le résultat joint en affichant uniquement les lignes où le côté droit est NULL
.Donc vous vous retrouvez avec:
SELECT a.*
FROM YourTable a
LEFT OUTER JOIN YourTable b
ON a.id = b.id AND a.rev < b.rev
WHERE b.id IS NULL;
Les deux approches donnent exactement le même résultat.
Si vous avez deux lignes avec max-value-in-group
pour group-identifier
, les deux lignes seront dans le résultat dans les deux approches.
Les deux approches sont compatibles SQL ANSI et fonctionneront donc avec votre SGBDR préféré, quelle que soit sa "saveur".
Les deux approches favorisent également les performances, mais votre kilométrage peut varier (SGBDR, structure de base de données, index, etc.). Donc, lorsque vous choisissez une approche plutôt que l'autre, benchmark. Et assurez-vous de choisir celui qui vous convient le mieux.
Ma préférence est d'utiliser le moins de code possible ...
Vous pouvez le faire en utilisant IN
Essayez ceci:
SELECT *
FROM t1 WHERE (id,rev) IN
( SELECT id, MAX(rev)
FROM t1
GROUP BY id
)
à mon avis, c'est moins compliqué ... plus facile à lire et à entretenir.
Une autre solution consiste à utiliser une sous-requête corrélée:
select yt.id, yt.rev, yt.contents
from YourTable yt
where rev =
(select max(rev) from YourTable st where yt.id=st.id)
Avoir un index sur (id, rev) rend la sous-requête presque comme une simple recherche ...
Voici des comparaisons avec les solutions de la réponse de @ AdrianCarneiro (sous-requête, leftjoin), basées sur des mesures MySQL avec une table InnoDB d'environ 1 million d'enregistrements, la taille du groupe étant: 1-3.
Alors que pour les balayages de table complets, les sous-requêtes/leftjoin/corrélations se rapportent les 6/8/9, lorsqu'il s'agit de recherches directes ou de batch (id in (1,2,3)
), la sous-requête est beaucoup plus lente que les autres (en raison de la réexécution de la sous-requête). Cependant, je ne pouvais pas faire la différence entre les solutions de gauche et corrélées en termes de rapidité.
Une dernière note, comme leftjoin crée n * (n + 1)/2 jointures dans des groupes, ses performances peuvent être fortement affectées par la taille des groupes ...
Je suis abasourdi par le fait qu'aucune réponse ne soit proposée dans la solution de la fonction de fenêtre SQL:
SELECT a.id, a.rev, a.contents
FROM (SELECT id, rev, contents,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
FROM YourTable) a
WHERE a.rank = 1
Ajoutées dans la norme SQL ANSI/ISO Standard SQL: 2003 et ultérieurement étendue à la norme ANSI/ISO Standard SQL: 2008, les fonctions de fenêtre (ou de fenêtrage) sont désormais disponibles chez tous les principaux fournisseurs. Il existe plus de types de fonctions de classement disponibles pour traiter un problème d'égalité: RANK, DENSE_RANK, PERSENT_RANK
.
Je ne peux pas garantir les performances, mais voici une astuce inspirée par les limitations de Microsoft Excel. Il a quelques bonnes caractéristiques
BON PRODUIT
APPROCHE
C'est un peu moche et nécessite que vous sachiez quelque chose sur la plage de valeurs valides de la colonnerev. Supposons que nous savons que la colonnerevest un nombre compris entre 0,00 et 999, décimales comprises, mais qu’il n’y aura jamais que deux chiffres à droite du séparateur décimal (par exemple, 34.17 serait une valeur valide ).
En résumé, vous créez une colonne synthétique unique en concaténant/encapsidant le champ de comparaison principal avec les données souhaitées. De cette manière, vous pouvez forcer la fonction d'agrégation MAX () de SQL à renvoyer toutes les données (car elles ont été regroupées dans une seule colonne). Ensuite, vous devez décompresser les données.
Voici à quoi ça ressemble avec l'exemple ci-dessus, écrit en SQL
SELECT id,
CAST(SUBSTRING(max(packed_col) FROM 2 FOR 6) AS float) as max_rev,
SUBSTRING(max(packed_col) FROM 11) AS content_for_max_rev
FROM (SELECT id,
CAST(1000 + rev + .001 as CHAR) || '---' || CAST(content AS char) AS packed_col
FROM yourtable
)
GROUP BY id
Le compactage commence par forcer la colonnerevà être un nombre de longueurs de caractères connues quelle que soit la valeur derevafin que, par exemple,
Si vous le faites bien, la comparaison de chaînes de deux nombres devrait donner le même "max" que la comparaison numérique des deux nombres et il est facile de reconvertir le nombre en utilisant la fonction de sous-chaîne (disponible sous une forme ou une autre partout).
Je pense que c'est la solution la plus simple:
SELECT *
FROM
(SELECT *
FROM Employee
ORDER BY Salary DESC)
AS employeesub
GROUP BY employeesub.Salary;
Si vous n'avez besoin que d'une seule ligne, c'est encore plus simple:
SELECT *
FROM Employee
ORDER BY Employee.Salary DESC
LIMIT 1
Je pense également qu'il est le plus facile de décomposer, de comprendre et de modifier d'autres objectifs:
Comprendre cette approche, résoudre l'un de ces problèmes similaires devient trivial: recruter l'employé avec le salaire le plus bas (changer DESC en ASC), obtenir le top 10 des employés gagnant (changer LIMIT 1 en LIMIT 10), trier à l'aide d'un autre champ (modifier Employee.Salary to ORDER BY Employee.Commission), etc.
Quelque chose comme ça?
SELECT yourtable.id, rev, content
FROM yourtable
INNER JOIN (
SELECT id, max(rev) as maxrev FROM yourtable
WHERE yourtable
GROUP BY id
) AS child ON (yourtable.id = child.id) AND (yourtable.rev = maxrev)
Comme il s’agit de la question la plus populaire concernant ce problème, je vais publier une autre réponse ici aussi:
Il semble y avoir un moyen plus simple de faire cela (mais uniquement dans MySQL):
select *
from (select * from mytable order by id, rev desc ) x
group by id
Merci de citer la réponse de l'utilisateur Bohemian dans cette question pour avoir fourni une réponse aussi concise et élégante à ce problème.
EDIT: bien que cette solution fonctionne pour de nombreuses personnes, elle risque de ne pas être stable à long terme, car MySQL ne garantit pas que l'instruction GROUP BY renvoie des valeurs significatives pour les colonnes ne figurant pas dans la liste GROUP BY. Alors utilisez cette solution à vos risques et périls
J'aime utiliser une solution basée sur NOT EXIST
pour résoudre ce problème:
SELECT id, rev
FROM YourTable t
WHERE NOT EXISTS (
SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev
)
Une troisième solution que je vois rarement mentionnée est spécifique à MySQL et ressemble à ceci:
SELECT id, MAX(rev) AS rev
, 0+SUBSTRING_INDEX(GROUP_CONCAT(numeric_content ORDER BY rev DESC), ',', 1) AS numeric_content
FROM t1
GROUP BY id
Oui, c'est affreux (conversion en chaîne, etc.), mais d'après mon expérience, c'est généralement plus rapide que les autres solutions. Peut-être que ce n'est que pour mes cas d'utilisation, mais je l'ai utilisé sur des tables avec des millions d'enregistrements et de nombreux identifiants uniques. Peut-être est-ce dû au fait que MySQL optimise assez mal les autres solutions (du moins dans les 5.0 jours où j'ai proposé cette solution).
Une chose importante est que GROUP_CONCAT a une longueur maximale pour la chaîne qu'il peut construire. Vous voudrez probablement augmenter cette limite en définissant la variable group_concat_max_len
. Et gardez à l’esprit que la mise à l’échelle sera limitée si vous avez un grand nombre de lignes.
Quoi qu'il en soit, ce qui précède ne fonctionne pas directement si votre champ de contenu est déjà du texte. Dans ce cas, vous voudrez probablement utiliser un séparateur différent, comme\0 peut-être. Vous rencontrerez également la limite group_concat_max_len
plus rapidement.
Je pense que tu veux ça?
select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)
SQL Fiddle: À vérifier ici
Si vous avez plusieurs champs dans l'instruction select et que vous souhaitez obtenir la dernière valeur pour tous ces champs via un code optimisé:
select * from
(select * from table_name
order by id,rev desc) temp
group by id
NOT mySQL, mais pour les autres personnes trouvant cette question et utilisant SQL, un autre moyen de résoudre le problème le plus grand nombre par groupe consiste à utiliser Cross Apply
dans MS SQL.
WITH DocIds AS (SELECT DISTINCT id FROM docs)
SELECT d2.id, d2.rev, d2.content
FROM DocIds d1
CROSS APPLY (
SELECT Top 1 * FROM docs d
WHERE d.id = d1.id
ORDER BY rev DESC
) d2
Une autre façon de faire le travail consiste à utiliser la fonction analytique MAX()
dans la clause OVER PARTITION
SELECT t.*
FROM
(
SELECT id
,rev
,contents
,MAX(rev) OVER (PARTITION BY id) as max_rev
FROM YourTable
) t
WHERE t.rev = t.max_rev
L’autre solution ROW_NUMBER()
OVER PARTITION déjà décrite dans ce message est
SELECT t.*
FROM
(
SELECT id
,rev
,contents
,ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
FROM YourTable
) t
WHERE t.rank = 1
Ce 2 SELECT fonctionne bien sur Oracle 10g.
MAX () solution fonctionne certainement PLUS RAPIDEMENT que ROW_NUMBER()
solution parce que MAX()
complexité est O(n)
tandis que ROW_NUMBER()
complexité est au minimum O(n.log(n))
où n
représente le nombre d'enregistrements dans la table!
Je voudrais utiliser ceci:
select t.*
from test as t
join
(select max(rev) as rev
from test
group by id) as o
on o.rev = t.rev
La sous-requête SELECT n'est peut-être pas trop efficace, mais la clause JOIN semble utilisable. Je ne suis pas un expert en optimisation de requêtes, mais j'ai déjà essayé avec MySQL, PostgreSQL, FireBird et cela fonctionne très bien.
Vous pouvez utiliser ce schéma dans plusieurs jointures et avec la clause WHERE. Voici mon exemple de travail (résoudre le même problème que le vôtre avec le tableau "firmy"):
select *
from platnosci as p
join firmy as f
on p.id_rel_firmy = f.id_rel
join (select max(id_obj) as id_obj
from firmy
group by id_rel) as o
on o.id_obj = f.id_obj and p.od > '2014-03-01'
Il est posé sur des tables ayant des dizaines d’adolescents, et il faut moins de 0,01 seconde sur une machine vraiment pas trop forte.
Je ne voudrais pas utiliser la clause IN (comme il est mentionné quelque part ci-dessus). IN est donné à utiliser avec des listes courtes de constantes, et non comme un filtre de requête construit sur une sous-requête. C’est parce que la sous-requête dans IN est exécutée pour chaque enregistrement analysé, ce qui peut rendre la requête très longue.
Que dis-tu de ça:
SELECT all_fields.*
FROM (SELECT id, MAX(rev) FROM yourtable GROUP BY id) AS max_recs
LEFT OUTER JOIN yourtable AS all_fields
ON max_recs.id = all_fields.id
SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
Aucune de ces réponses n'a fonctionné pour moi.
C'est ce qui a fonctionné pour moi.
with score as (select max(score_up) from history)
select history.* from score, history where history.score_up = score.max
Trié le champ rev en ordre inverse, puis groupé par id, ce qui a donné la première ligne de chaque groupe, celle avec la valeur rev la plus élevée.
SELECT * FROM (SELECT * FROM table1 ORDER BY id, rev DESC) X GROUP BY X.id;
Testé dans http://sqlfiddle.com/ avec les données suivantes
CREATE TABLE table1
(`id` int, `rev` int, `content` varchar(11));
INSERT INTO table1
(`id`, `rev`, `content`)
VALUES
(1, 1, 'One-One'),
(1, 2, 'One-Two'),
(2, 1, 'Two-One'),
(2, 2, 'Two-Two'),
(3, 2, 'Three-Two'),
(3, 1, 'Three-One'),
(3, 3, 'Three-Three')
;
Cela a donné le résultat suivant dans MySql 5.5 et 5.6
id rev content
1 2 One-Two
2 2 Two-Two
3 3 Three-Two
Voici une bonne façon de le faire
Utilisez le code suivant:
with temp as (
select count(field1) as summ , field1
from table_name
group by field1 )
select * from temp where summ = (select max(summ) from temp)
J'aime faire cela en classant les enregistrements par colonne. Dans ce cas, rangez les valeurs rev
groupées par id
. Les personnes dont la variable rev
est élevée auront un classement inférieur. Donc, la plus haute rev
aura un rang de 1.
select id, rev, content
from
(select
@rowNum := if(@prevValue = id, @rowNum+1, 1) as row_num,
id, rev, content,
@prevValue := id
from
(select id, rev, content from YOURTABLE order by id asc, rev desc) TEMP,
(select @rowNum := 1 from DUAL) X,
(select @prevValue := -1 from DUAL) Y) TEMP
where row_num = 1;
Pas sûr que l'introduction de variables ralentisse le tout. Mais au moins, je ne demande pas YOURTABLE
deux fois.
Voici une autre solution pour récupérer les enregistrements uniquement avec un champ ayant la valeur maximale pour ce champ. Cela fonctionne pour SQL400, la plate-forme sur laquelle je travaille. Dans cet exemple, les enregistrements avec la valeur maximale dans la zone FIELD5 seront récupérés par l'instruction SQL suivante.
SELECT A.KEYFIELD1, A.KEYFIELD2, A.FIELD3, A.FIELD4, A.FIELD5
FROM MYFILE A
WHERE RRN(A) IN
(SELECT RRN(B)
FROM MYFILE B
WHERE B.KEYFIELD1 = A.KEYFIELD1 AND B.KEYFIELD2 = A.KEYFIELD2
ORDER BY B.FIELD5 DESC
FETCH FIRST ROW ONLY)
voici une autre solution espérons que cela aidera quelqu'un
Select a.id , a.rev, a.content from Table1 a
inner join
(SELECT id, max(rev) rev FROM Table1 GROUP BY id) x on x.id =a.id and x.rev =a.rev
Cette solution n'effectue qu'une seule sélection dans YourTable, elle est donc plus rapide. Cela ne fonctionne que pour MySQL et SQLite (pour SQLite, supprimez DESC) selon le test effectué sur sqlfiddle.com. Peut-être qu’il pourrait être modifié pour travailler sur d’autres langues que je ne connais pas bien.
SELECT *
FROM ( SELECT *
FROM ( SELECT 1 as id, 1 as rev, 'content1' as content
UNION
SELECT 2, 1, 'content2'
UNION
SELECT 1, 2, 'content3'
UNION
SELECT 1, 3, 'content4'
) as YourTable
ORDER BY id, rev DESC
) as YourTable
GROUP BY id
Ce n'est pas du SQL pur. Cela utilisera l'ORM SQLAlchemy.
Je suis venu ici pour demander de l'aide à SQLAlchemy. Je vais donc dupliquer la réponse d'Adrian Carneiro avec la version python/SQLAlchemy, plus précisément la jointure externe.
Cette requête répond à la question de:
"Pouvez-vous me retourner les enregistrements de ce groupe d'enregistrements (basés sur le même identifiant) qui ont le numéro de version le plus élevé".
Cela me permet de dupliquer l’enregistrement, de le mettre à jour, d’augmenter son numéro de version et d’obtenir la copie de l’ancienne version de manière à pouvoir montrer les changements au fil du temps.
MyTableAlias = aliased(MyTable)
newest_records = appdb.session.query(MyTable).select_from(join(
MyTable,
MyTableAlias,
onclause=and_(
MyTable.id == MyTableAlias.id,
MyTable.version_int < MyTableAlias.version_int
),
isouter=True
)
).filter(
MyTableAlias.id == None,
).all()
Testé sur une base de données PostgreSQL.
J'ai utilisé le ci-dessous pour résoudre un problème de mon cru. J'ai d'abord créé une table temporaire et inséré la valeur de rév. Max par identifiant unique.
CREATE TABLE #temp1
(
id varchar(20)
, rev int
)
INSERT INTO #temp1
SELECT a.id, MAX(a.rev) as rev
FROM
(
SELECT id, content, SUM(rev) as rev
FROM YourTable
GROUP BY id, content
) as a
GROUP BY a.id
ORDER BY a.id
J'ai ensuite joint ces valeurs maximales (# temp1) à toutes les combinaisons possibles id/contenu. En faisant cela, je filtre naturellement les combinaisons id/contenu non maximales, et je ne dispose plus que des valeurs maximales de rév.
SELECT a.id, a.rev, content
FROM #temp1 as a
LEFT JOIN
(
SELECT id, content, SUM(rev) as rev
FROM YourTable
GROUP BY id, content
) as b on a.id = b.id and a.rev = b.rev
GROUP BY a.id, a.rev, b.content
ORDER BY a.id
Vous pouvez effectuer la sélection sans jointure en combinant les rev
et id
en une valeur maxRevId
pour MAX()
, puis en le fractionnant aux valeurs d'origine:
SELECT maxRevId & ((1 << 32) - 1) as id, maxRevId >> 32 AS rev
FROM (SELECT MAX(((rev << 32) | id)) AS maxRevId
FROM YourTable
GROUP BY id) x;
Ceci est particulièrement rapide lorsqu'il existe une jointure complexe au lieu d'une seule table. Avec les approches traditionnelles, la jointure complexe se ferait deux fois.
La combinaison ci-dessus est simple avec les fonctions de bits lorsque rev
et id
sont INT UNSIGNED
(32 bits) et que la valeur combinée correspond à BIGINT UNSIGNED
(64 bits). Lorsque les variables id
& rev
sont supérieures à 32 bits ou composées de plusieurs colonnes, vous devez combiner la valeur, par exemple. une valeur binaire avec un remplissage approprié pour MAX()
.