Récupérer le dernier enregistrement de chaque groupe - MySQL

959

Il y a un tableau messagesqui contient les données comme indiqué ci-dessous:

Id   Name   Other_Columns
-------------------------
1    A       A_data_1
2    A       A_data_2
3    A       A_data_3
4    B       B_data_1
5    B       B_data_2
6    C       C_data_1

Si j'exécute une requête select * from messages group by name, j'obtiendrai le résultat sous la forme:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

Quelle requête retournera le résultat suivant?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

Autrement dit, le dernier enregistrement de chaque groupe doit être renvoyé.

À l'heure actuelle, c'est la requête que j'utilise:

SELECT
  *
FROM (SELECT
  *
FROM messages
ORDER BY id DESC) AS x
GROUP BY name

Mais cela semble très inefficace. Y a-t-il d'autres façons d'obtenir le même résultat?

Vijay Dev
la source
2
voir la réponse acceptée dans stackoverflow.com/questions/1379565/… pour une solution plus efficace
eyaler
7
Pourquoi ne pouvez-vous pas simplement ajouter DESC, c'est-à-dire sélectionner * dans le groupe de messages par nom DESC
Kim Prince
Duplication possible de Comment puis-je sélectionner des lignes avec MAX (valeur de colonne), DISTINCT par une autre colonne dans SQL?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
@KimPrince Il semble que la réponse que vous suggérez ne fait pas ce qui est attendu! Je viens d'essayer votre méthode et il a fallu PREMIÈRE rangée pour chaque groupe et commandé DESC. Il ne prend PAS la dernière ligne de chaque groupe
Ayrat

Réponses:

972

MySQL 8.0 prend désormais en charge les fonctions de fenêtrage, comme presque toutes les implémentations SQL populaires. Avec cette syntaxe standard, nous pouvons écrire des requêtes avec le plus grand nombre n par groupe:

WITH ranked_messages AS (
  SELECT m.*, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id DESC) AS rn
  FROM messages AS m
)
SELECT * FROM ranked_messages WHERE rn = 1;

Voici la réponse originale que j'ai écrite pour cette question en 2009:


J'écris la solution de cette façon:

SELECT m1.*
FROM messages m1 LEFT JOIN messages m2
 ON (m1.name = m2.name AND m1.id < m2.id)
WHERE m2.id IS NULL;

Concernant les performances, l'une ou l'autre solution peut être meilleure, selon la nature de vos données. Vous devez donc tester les deux requêtes et utiliser celle qui offre les meilleures performances compte tenu de votre base de données.

Par exemple, j'ai une copie du vidage de données StackOverflow August . Je vais l'utiliser pour l'analyse comparative. Il y a 1 114 357 lignes dans le Poststableau. Cela fonctionne sur MySQL 5.0.75 sur mon Macbook Pro 2.40GHz.

J'écrirai une requête pour trouver le message le plus récent pour un ID utilisateur donné (le mien).

En utilisant d'abord la technique montrée par @Eric avec le GROUP BYdans une sous-requête:

SELECT p1.postid
FROM Posts p1
INNER JOIN (SELECT pi.owneruserid, MAX(pi.postid) AS maxpostid
            FROM Posts pi GROUP BY pi.owneruserid) p2
  ON (p1.postid = p2.maxpostid)
WHERE p1.owneruserid = 20860;

1 row in set (1 min 17.89 sec)

Même l' EXPLAINanalyse prend plus de 16 secondes:

+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
| id | select_type | table      | type   | possible_keys              | key         | key_len | ref          | rows    | Extra       |
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL                       | NULL        | NULL    | NULL         |   76756 |             | 
|  1 | PRIMARY     | p1         | eq_ref | PRIMARY,PostId,OwnerUserId | PRIMARY     | 8       | p2.maxpostid |       1 | Using where | 
|  2 | DERIVED     | pi         | index  | NULL                       | OwnerUserId | 8       | NULL         | 1151268 | Using index | 
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
3 rows in set (16.09 sec)

Produisez maintenant le même résultat de requête en utilisant ma technique avec LEFT JOIN:

SELECT p1.postid
FROM Posts p1 LEFT JOIN posts p2
  ON (p1.owneruserid = p2.owneruserid AND p1.postid < p2.postid)
WHERE p2.postid IS NULL AND p1.owneruserid = 20860;

1 row in set (0.28 sec)

L' EXPLAINanalyse montre que les deux tables peuvent utiliser leurs index:

+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
| id | select_type | table | type | possible_keys              | key         | key_len | ref   | rows | Extra                                |
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
|  1 | SIMPLE      | p1    | ref  | OwnerUserId                | OwnerUserId | 8       | const | 1384 | Using index                          | 
|  1 | SIMPLE      | p2    | ref  | PRIMARY,PostId,OwnerUserId | OwnerUserId | 8       | const | 1384 | Using where; Using index; Not exists | 
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
2 rows in set (0.00 sec)

Voici le DDL pour ma Poststable:

CREATE TABLE `posts` (
  `PostId` bigint(20) unsigned NOT NULL auto_increment,
  `PostTypeId` bigint(20) unsigned NOT NULL,
  `AcceptedAnswerId` bigint(20) unsigned default NULL,
  `ParentId` bigint(20) unsigned default NULL,
  `CreationDate` datetime NOT NULL,
  `Score` int(11) NOT NULL default '0',
  `ViewCount` int(11) NOT NULL default '0',
  `Body` text NOT NULL,
  `OwnerUserId` bigint(20) unsigned NOT NULL,
  `OwnerDisplayName` varchar(40) default NULL,
  `LastEditorUserId` bigint(20) unsigned default NULL,
  `LastEditDate` datetime default NULL,
  `LastActivityDate` datetime default NULL,
  `Title` varchar(250) NOT NULL default '',
  `Tags` varchar(150) NOT NULL default '',
  `AnswerCount` int(11) NOT NULL default '0',
  `CommentCount` int(11) NOT NULL default '0',
  `FavoriteCount` int(11) NOT NULL default '0',
  `ClosedDate` datetime default NULL,
  PRIMARY KEY  (`PostId`),
  UNIQUE KEY `PostId` (`PostId`),
  KEY `PostTypeId` (`PostTypeId`),
  KEY `AcceptedAnswerId` (`AcceptedAnswerId`),
  KEY `OwnerUserId` (`OwnerUserId`),
  KEY `LastEditorUserId` (`LastEditorUserId`),
  KEY `ParentId` (`ParentId`),
  CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`PostTypeId`) REFERENCES `posttypes` (`PostTypeId`)
) ENGINE=InnoDB;
Bill Karwin
la source
8
Vraiment? Que se passe-t-il si vous avez une tonne d'entrées? Par exemple, si vous travaillez avec un contrôle de version interne, disons, et que vous avez une tonne de versions par fichier, ce résultat de jointure serait énorme. Avez-vous déjà évalué la méthode de sous-requête avec celle-ci? Je suis assez curieux de savoir qui gagnerait, mais pas assez pour ne pas vous demander d'abord.
Eric
2
A fait quelques tests. Sur une petite table (~ 300k enregistrements, ~ 190k groupes, donc pas de groupes massifs ou quoi que ce soit), les requêtes étaient liées (8 secondes chacune).
Eric
1
@BillKarwin: Voir meta.stackexchange.com/questions/123017 , en particulier les commentaires ci-dessous la réponse d'Adam Rackis. Faites-moi savoir si vous souhaitez récupérer votre réponse à la nouvelle question.
Robert Harvey
3
@Tim, non, <=n'aidera pas si vous avez une colonne non unique. Vous devez utiliser une colonne unique comme bris d'égalité.
Bill Karwin
2
Les performances se dégradent de façon exponentielle lorsque le nombre de lignes augmente ou lorsque les groupes deviennent plus grands. Par exemple, un groupe composé de 5 dates donnera 4 + 3 + 2 + 1 + 1 = 11 lignes via la jointure gauche sur laquelle une ligne est filtrée à la fin. La performance de la jonction avec des résultats groupés est presque linéaire. Vos tests semblent défectueux.
Salman A
148

UPD: 2017-03-31, la version 5.7.5 de MySQL a rendu le commutateur ONLY_FULL_GROUP_BY activé par défaut (par conséquent, les requêtes GROUP BY non déterministes ont été désactivées). De plus, ils ont mis à jour l'implémentation GROUP BY et la solution pourrait ne plus fonctionner comme prévu, même avec le commutateur désactivé. Il faut vérifier.

La solution de Bill Karwin ci-dessus fonctionne bien lorsque le nombre d'éléments au sein des groupes est plutôt faible, mais les performances de la requête deviennent mauvaises lorsque les groupes sont assez grands, car la solution ne nécessite n*n/2 + n/2que des IS NULLcomparaisons.

J'ai fait mes tests sur une table InnoDB de 18684446lignes avec des 1182groupes. Le tableau contient les résultats des tests pour les tests fonctionnels et a la (test_id, request_id)clé primaire comme. Ainsi, test_idest un groupe et je cherchais le dernier request_idpour chacun test_id.

La solution de Bill fonctionne déjà depuis plusieurs heures sur mon dell e4310 et je ne sais pas quand elle va se terminer même si elle fonctionne sur un indice de couverture (donc using indexdans EXPLAIN).

J'ai quelques autres solutions basées sur les mêmes idées:

  • si l'indice sous-jacent est l'indice BTREE (ce qui est généralement le cas), la (group_id, item_value)paire la plus grande est la dernière valeur à l'intérieur de chacun group_id, c'est la première pour chacun group_idsi nous parcourons l'index dans l'ordre décroissant;
  • si nous lisons les valeurs couvertes par un index, les valeurs sont lues dans l'ordre de l'index;
  • chaque index contient implicitement des colonnes de clé primaire ajoutées à cela (c'est-à-dire que la clé primaire est dans l'index de couverture). Dans les solutions ci-dessous, j'opère directement sur la clé primaire, dans votre cas, il vous suffira d'ajouter des colonnes de clé primaire dans le résultat.
  • dans de nombreux cas, il est beaucoup moins cher de collecter les ID de ligne requis dans l'ordre requis dans une sous-requête et de joindre le résultat de la sous-requête sur l'ID. Étant donné que pour chaque ligne du résultat de la sous-requête, MySQL aura besoin d'une extraction unique basée sur la clé primaire, la sous-requête sera placée en premier dans la jointure et les lignes seront sorties dans l'ordre des identifiants de la sous-requête (si nous omettons ORDER BY explicite pour la jointure)

3 façons dont MySQL utilise les index est un excellent article pour comprendre certains détails.

Solution 1

Celui-ci est incroyablement rapide, il faut environ 0,8 secondes sur mes 18 millions de lignes:

SELECT test_id, MAX(request_id) AS request_id
FROM testresults
GROUP BY test_id DESC;

Si vous souhaitez modifier l'ordre en ASC, placez-le dans une sous-requête, renvoyez uniquement les identifiants et utilisez-le comme sous-requête pour joindre le reste des colonnes:

SELECT test_id, request_id
FROM (
    SELECT test_id, MAX(request_id) AS request_id
    FROM testresults
    GROUP BY test_id DESC) as ids
ORDER BY test_id;

Celui-ci prend environ 1,2 secondes sur mes données.

Solution 2

Voici une autre solution qui prend environ 19 secondes pour ma table:

SELECT test_id, request_id
FROM testresults, (SELECT @group:=NULL) as init
WHERE IF(IFNULL(@group, -1)=@group:=test_id, 0, 1)
ORDER BY test_id DESC, request_id DESC

Il renvoie également les tests dans l'ordre décroissant. Il est beaucoup plus lent car il effectue un balayage d'index complet, mais il est là pour vous donner une idée de la sortie de N max de lignes pour chaque groupe.

L'inconvénient de la requête est que son résultat ne peut pas être mis en cache par le cache de requête.

newtover
la source
Veuillez créer un lien vers un vidage de vos tables afin que les utilisateurs puissent le tester sur leurs plateformes.
Pacerier
3
La solution 1 ne peut pas fonctionner, vous ne pouvez pas sélectionner request_id sans l'avoir dans la clause group by,
giò
2
@ giò, c'est la réponse a 5 ans. Jusqu'à ce que MySQL 5.7.5 ONLY_FULL_GROUP_BY soit désactivé par défaut et que cette solution soit sortie de la boîte dev.mysql.com/doc/relnotes/mysql/5.7/en/… . Maintenant, je ne sais pas si la solution fonctionne toujours lorsque vous désactivez le mode, car l'implémentation de GROUP BY a été modifiée.
newtover
Si vous vouliez ASC dans la première solution, cela fonctionnerait-il si vous passiez MAX à MIN?
Jin
@JinIzzraeel, vous avez MIN par défaut en haut de chaque groupe (c'est l'ordre de l'index de couverture): SELECT test_id, request_id FROM testresults GROUP BY test_id;retournerait le request_id minimum pour chaque test_id.
newtover
102

Utilisez votre sous - requête pour renvoyer le groupe correct, car vous êtes à mi-chemin.

Essaye ça:

select
    a.*
from
    messages a
    inner join 
        (select name, max(id) as maxid from messages group by name) as b on
        a.id = b.maxid

Si ce n'est pas le cas, idvous voulez le maximum de:

select
    a.*
from
    messages a
    inner join 
        (select name, max(other_col) as other_col 
         from messages group by name) as b on
        a.name = b.name
        and a.other_col = b.other_col

De cette façon, vous évitez les sous-requêtes corrélées et / ou l'ordre dans vos sous-requêtes, qui ont tendance à être très lentes / inefficaces.

Eric
la source
1
Notez une mise en garde pour la solution avec other_col: si cette colonne n'est pas unique, vous pouvez récupérer plusieurs enregistrements avec le même name, s'ils sont liés max(other_col). J'ai trouvé cet article qui décrit une solution à mes besoins, où j'ai besoin exactement d'un enregistrement par name.
Eric Simonton
Dans certaines situations, vous ne pouvez utiliser que cette solution mais celle acceptée.
tom10271
D'après mon expérience, c'est le regroupement de la fichue table de messages qui a tendance à être lent / inefficace! En d'autres termes, notez que la sous-requête nécessite une analyse complète de la table et effectue un regroupement à ce sujet pour démarrer ... à moins que votre optimiseur ne fasse quelque chose que le mien ne fait pas. Cette solution dépend donc fortement de la conservation de la table entière en mémoire.
Timo
Ceux qui bénéficieraient de INDEX(name, id)etINDEX(name, other_col)
Rick James
55

Je suis arrivé à une solution différente, qui consiste à obtenir les ID du dernier message dans chaque groupe, puis à sélectionner dans le tableau des messages en utilisant le résultat de la première requête comme argument pour une WHERE x INconstruction:

SELECT id, name, other_columns
FROM messages
WHERE id IN (
    SELECT MAX(id)
    FROM messages
    GROUP BY name
);

Je ne sais pas comment cela fonctionne par rapport à certaines des autres solutions, mais cela a fonctionné de manière spectaculaire pour ma table avec plus de 3 millions de lignes. (Exécution de 4 secondes avec plus de 1200 résultats)

Cela devrait fonctionner à la fois sur MySQL et SQL Server.

JYelton
la source
Assurez-vous simplement d'avoir un index sur (nom, id).
Samuel Åslund
1
Beaucoup mieux que le soi se joint
anwerj
J'ai appris quelque chose de vous qui est un bon travail et cette requête est plus rapide
Humphrey
33

Solution par sous-requête violon Lien

select * from messages where id in
(select max(id) from messages group by Name)

Solution En joignant condition fiddle link

select m1.* from messages m1 
left outer join messages m2 
on ( m1.id<m2.id and m1.name=m2.name )
where m2.id is null

La raison de ce message est de donner un lien violon uniquement. Le même SQL est déjà fourni dans d'autres réponses.

Vipin
la source
1
@AlexanderSuraphel mysql5.5 n'est pas disponible dans fiddle maintenant, le lien fiddle a été créé en utilisant cela. Maintenant, un violon prend en charge mysql5.6, j'ai changé la base de données en mysql 5.6 et je suis capable de construire un schéma et d'exécuter le sql.
Vipin
8

Une approche avec une vitesse considérable est la suivante.

SELECT * 
FROM messages a
WHERE Id = (SELECT MAX(Id) FROM messages WHERE a.Name = Name)

Résultat

Id  Name    Other_Columns
3   A   A_data_3
5   B   B_data_2
6   C   C_data_1
Song Zhengyi
la source
Cela suppose idque vous l'ordonniez comme vous en avez besoin. Dans le cas général, une autre colonne est nécessaire.
Rick James
6

Voici deux suggestions. Tout d'abord, si mysql prend en charge ROW_NUMBER (), c'est très simple:

WITH Ranked AS (
  SELECT Id, Name, OtherColumns,
    ROW_NUMBER() OVER (
      PARTITION BY Name
      ORDER BY Id DESC
    ) AS rk
  FROM messages
)
  SELECT Id, Name, OtherColumns
  FROM messages
  WHERE rk = 1;

Je suppose que par "dernier" vous voulez dire le dernier dans l'ordre d'identification. Sinon, modifiez la clause ORDER BY de la fenêtre ROW_NUMBER () en conséquence. Si ROW_NUMBER () n'est pas disponible, voici une autre solution:

Deuxièmement, si ce n'est pas le cas, c'est souvent une bonne façon de procéder:

SELECT
  Id, Name, OtherColumns
FROM messages
WHERE NOT EXISTS (
  SELECT * FROM messages as M2
  WHERE M2.Name = messages.Name
  AND M2.Id > messages.Id
)

En d'autres termes, sélectionnez les messages où il n'y a pas de message d'identification ultérieure portant le même nom.

Steve Kass
la source
8
MySQL ne prend pas en charge ROW_NUMBER () ou CTE.
Bill Karwin
1
MySQL 8.0 (et MariaDB 10.2) prend désormais en charge ROW_NUMBER()et les CTE.
Rick James
6

Je n'ai pas encore testé avec une grande base de données mais je pense que cela pourrait être plus rapide que de rejoindre des tables:

SELECT *, Max(Id) FROM messages GROUP BY Name
Shai
la source
14
Cela renvoie des données arbitraires. En d'autres termes, les colonnes renvoyées peuvent ne pas provenir de l'enregistrement avec MAX (Id).
nuire le
Utile pour sélectionner l'id max dans un ensemble d'enregistrements avec la condition WHERE: "SELECT Max (Id) FROM Prod WHERE Pn = '" + Pn + "'" Il renvoie l'id max d'un ensemble d'enregistrements avec le même Pn.In c # utilisez reader.GetString (0) pour obtenir le résultat
Nicola
5

Voici une autre façon d'obtenir le dernier enregistrement associé en utilisant GROUP_CONCATavec ordre par et SUBSTRING_INDEXde choisir l'un des enregistrements dans la liste

SELECT 
  `Id`,
  `Name`,
  SUBSTRING_INDEX(
    GROUP_CONCAT(
      `Other_Columns` 
      ORDER BY `Id` DESC 
      SEPARATOR '||'
    ),
    '||',
    1
  ) Other_Columns 
FROM
  messages 
GROUP BY `Name` 

La requête ci-dessus regroupera tous les éléments Other_Columnsdu même Namegroupe et l'utilisation ORDER BY id DESCjoindra tous les éléments Other_Columnsd'un groupe spécifique dans l'ordre décroissant avec le séparateur fourni dans mon cas que j'ai utilisé ||, en utilisant SUBSTRING_INDEXcette liste, vous sélectionnerez le premier

Démo de violon

M Khalid Junaid
la source
Sachez que cela group_concat_max_lenlimite le nombre de lignes que vous pouvez gérer.
Rick James
5

De toute évidence, il existe de nombreuses façons d'obtenir les mêmes résultats, votre question semble être quelle est la manière efficace d'obtenir les derniers résultats dans chaque groupe dans MySQL. Si vous travaillez avec d'énormes quantités de données et en supposant que vous utilisez InnoDB avec même les dernières versions de MySQL (telles que 5.7.21 et 8.0.4-rc), il pourrait ne pas y avoir de moyen efficace de le faire.

Nous devons parfois le faire avec des tables contenant encore plus de 60 millions de lignes.

Pour ces exemples, j'utiliserai des données avec seulement environ 1,5 million de lignes où les requêtes devraient trouver des résultats pour tous les groupes dans les données. Dans nos cas réels, nous aurions souvent besoin de renvoyer les données d'environ 2 000 groupes (ce qui, en théorie, ne nécessiterait pas d'examiner une grande partie des données).

J'utiliserai les tableaux suivants:

CREATE TABLE temperature(
  id INT UNSIGNED NOT NULL AUTO_INCREMENT, 
  groupID INT UNSIGNED NOT NULL, 
  recordedTimestamp TIMESTAMP NOT NULL, 
  recordedValue INT NOT NULL,
  INDEX groupIndex(groupID, recordedTimestamp), 
  PRIMARY KEY (id)
);

CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id)); 

Le tableau des températures est peuplé d'environ 1,5 million d'enregistrements aléatoires et de 100 groupes différents. Le groupe sélectionné est peuplé de ces 100 groupes (dans nos cas, cela serait normalement inférieur à 20% pour tous les groupes).

Comme ces données sont aléatoires, cela signifie que plusieurs lignes peuvent avoir les mêmes horodatages enregistrés. Ce que nous voulons, c'est obtenir une liste de tous les groupes sélectionnés par ordre d'ID de groupe avec le dernier horodatage enregistré pour chaque groupe, et si le même groupe a plus d'une ligne correspondante comme celle-là, le dernier ID correspondant de ces lignes.

Si hypothétiquement MySQL avait une fonction last () qui renvoyait des valeurs de la dernière ligne d'une clause ORDER BY spéciale, alors nous pourrions simplement faire:

SELECT 
  last(t1.id) AS id, 
  t1.groupID, 
  last(t1.recordedTimestamp) AS recordedTimestamp, 
  last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;

qui aurait seulement besoin d'examiner quelques 100 lignes dans ce cas car il n'utilise aucune des fonctions GROUP BY normales. Cela s'exécuterait en 0 secondes et serait donc très efficace. Notez que normalement dans MySQL, nous verrions une clause ORDER BY suivant la clause GROUP BY mais cette clause ORDER BY est utilisée pour déterminer l'ORDRE de la fonction last (), si elle était après le GROUP BY, elle ordonnerait les GROUPES. Si aucune clause GROUP BY n'est présente, les dernières valeurs seront les mêmes dans toutes les lignes renvoyées.

Cependant, MySQL ne l'a pas, alors examinons différentes idées de ce qu'il a et prouvons qu'aucune de celles-ci n'est efficace.

Exemple 1

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT t2.id
  FROM temperature t2 
  WHERE t2.groupID = g.id
  ORDER BY t2.recordedTimestamp DESC, t2.id DESC
  LIMIT 1
);

Cela a examiné 3 009 254 rangées et a pris ~ 0,859 seconde sur 5.7.21 et légèrement plus long sur 8.0.4-rc

Exemple 2

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
INNER JOIN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
) t5 ON t5.id = t1.id;

Cela a examiné 1505331 rangées et a pris environ 1,25 seconde le 5.7.21 et légèrement plus long le 8.0.4-rc

Exemple 3

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
WHERE t1.id IN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
)
ORDER BY t1.groupID;

Cela a examiné 3 009 685 rangées et a pris environ 1,95 seconde le 5.7.21 et légèrement plus long le 8.0.4-rc

Exemple 4

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT max(t2.id)
  FROM temperature t2 
  WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
      SELECT max(t3.recordedTimestamp)
      FROM temperature t3 
      WHERE t3.groupID = g.id
    )
);

Cela a examiné 6 137 810 rangées et a pris ~ 2,2 secondes le 5.7.21 et légèrement plus long le 8.0.4-rc

Exemple 5

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
  SELECT 
    t2.id, 
    t2.groupID, 
    t2.recordedTimestamp, 
    t2.recordedValue, 
    row_number() OVER (
      PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
    ) AS rowNumber
  FROM selected_group g 
  INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;

Cela a examiné 6 017 808 rangées et a pris ~ 4,2 secondes sur 8.0.4-rc

Exemple 6

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM (
  SELECT 
    last_value(t2.id) OVER w AS id, 
    t2.groupID, 
    last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp, 
    last_value(t2.recordedValue) OVER w AS recordedValue
  FROM selected_group g
  INNER JOIN temperature t2 ON t2.groupID = g.id
  WINDOW w AS (
    PARTITION BY t2.groupID 
    ORDER BY t2.recordedTimestamp, t2.id 
    RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  )
) t1
GROUP BY t1.groupID;

Cela a examiné 6 017 908 rangées et a pris ~ 17,5 secondes sur 8.0.4-rc

Exemple 7

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2 
  ON t2.groupID = g.id 
  AND (
    t2.recordedTimestamp > t1.recordedTimestamp 
    OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
  )
WHERE t2.id IS NULL
ORDER BY t1.groupID;

Celui-ci prenait une éternité donc j'ai dû le tuer.

Yoseph
la source
C'est un problème différent. Et la solution est une énorme requête UNION ALL.
Paul Spiegel
@PaulSpiegel Je suppose que vous plaisantez sur l'immense UNION ALL. Outre le fait que l'on aurait besoin de connaître tous les groupes sélectionnés à l'avance et qu'avec 2000 groupes sélectionnés, ce serait une requête incroyablement énorme, cela serait encore pire que l'exemple le plus rapide ci-dessus, donc non, ce ne serait pas un Solution.
Yoseph
Je suis absolument sérieux. J'ai testé cela dans le passé avec quelques centaines de groupes. Lorsque vous devez gérer des liens dans de grands groupes, UNION ALL est le seul moyen dans MySQL pour forcer un plan d'exécution optimal. SELECT DISTINCT(groupID)est rapide et vous donnera toutes les données dont vous avez besoin pour construire une telle requête. Vous devriez être bien avec la taille de la requête tant qu'elle ne dépasse pas max_allowed_packet, ce qui par défaut est de 4 Mo dans MySQL 5.7.
Paul Spiegel
5

nous verrons comment vous pouvez utiliser MySQL pour obtenir le dernier enregistrement d'un regroupement d'enregistrements. Par exemple, si vous disposez de cet ensemble de résultats de publications.

id category_id post_title

1 1 Title 1

2 1 Title 2

3 1 Title 3

4 2 Title 4

5 2 Title 5

6 3 Title 6

Je veux pouvoir obtenir le dernier message dans chaque catégorie qui sont le titre 3, le titre 5 et le titre 6. Pour obtenir les messages par catégorie, vous utiliserez le clavier MySQL Group By.

select * from posts group by category_id

Mais les résultats que nous obtenons de cette requête sont.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

Le groupe par retournera toujours le premier enregistrement du groupe sur l'ensemble de résultats.

SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );

Cela retournera les messages avec les identifiants les plus élevés dans chaque groupe.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Référence Cliquez ici

Yagnesh bhalala
la source
4
SELECT 
  column1,
  column2 
FROM
  table_name 
WHERE id IN 
  (SELECT 
    MAX(id) 
  FROM
    table_name 
  GROUP BY column1) 
ORDER BY column1 ;
jeet singh parmar
la source
Pourriez-vous développer un peu votre réponse? Pourquoi votre requête est-elle préférable à la requête originale de Vijays?
janfoeh
4

Voici ma solution:

SELECT 
  DISTINCT NAME,
  MAX(MESSAGES) OVER(PARTITION BY NAME) MESSAGES 
FROM MESSAGE;
Abhishek Yadav
la source
Cela ne renvoie pas le dernier message par nom. Et ce n'est qu'une version trop compliquée de SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel
De plus, cette formulation est extrêmement inefficace.
Rick James
3

Essaye ça:

SELECT jos_categories.title AS name,
       joined .catid,
       joined .title,
       joined .introtext
FROM   jos_categories
       INNER JOIN (SELECT *
                   FROM   (SELECT `title`,
                                  catid,
                                  `created`,
                                  introtext
                           FROM   `jos_content`
                           WHERE  `sectionid` = 6
                           ORDER  BY `id` DESC) AS yes
                   GROUP  BY `yes`.`catid` DESC
                   ORDER  BY `yes`.`created` DESC) AS joined
         ON( joined.catid = jos_categories.id )  
Pro Web Design
la source
3

Salut @Vijay Dev si vos messages de table contiennent l' ID qui est la clé primaire d'incrémentation automatique, alors pour récupérer la dernière base d'enregistrement sur la clé primaire, votre requête doit se lire comme ci-dessous:

SELECT m1.* FROM messages m1 INNER JOIN (SELECT max(Id) as lastmsgId FROM messages GROUP BY Name) m2 ON m1.Id=m2.lastmsgId
bikashphp
la source
Celui-ci le plus rapide que j'ai trouvé
CORSAIR
3

Vous pouvez également voir ici.

http://sqlfiddle.com/#!9/ef42b/9

PREMIÈRE SOLUTION

SELECT d1.ID,Name,City FROM Demo_User d1
INNER JOIN
(SELECT MAX(ID) AS ID FROM Demo_User GROUP By NAME) AS P ON (d1.ID=P.ID);

DEUXIÈME SOLUTION

SELECT * FROM (SELECT * FROM Demo_User ORDER BY ID DESC) AS T GROUP BY NAME ;
Shrikant Gupta
la source
3
SELECT * FROM table_name WHERE primary_key IN (SELECT MAX(primary_key) FROM table_name GROUP BY column_name )
ShriP
la source
3

**

Bonjour, cette requête pourrait aider:

**

SELECT 
  *
FROM 
  message 

WHERE 
  `Id` IN (
    SELECT 
      MAX(`Id`) 
    FROM 
      message 
    GROUP BY 
      `Name`
  ) 
ORDER BY 
   `Id` DESC
Abhishek Sengupta
la source
2

Existe-t-il un moyen d'utiliser cette méthode pour supprimer les doublons dans une table? Le jeu de résultats est essentiellement une collection d'enregistrements uniques, donc si nous pouvions supprimer tous les enregistrements qui ne se trouvent pas dans le jeu de résultats, nous n'aurions effectivement pas de doublons? J'ai essayé mais mySQL a donné une erreur 1093.

DELETE FROM messages WHERE id NOT IN
 (SELECT m1.id  
 FROM messages m1 LEFT JOIN messages m2  
 ON (m1.name = m2.name AND m1.id < m2.id)  
 WHERE m2.id IS NULL)

Existe-t-il un moyen de sauvegarder la sortie dans une variable temporaire puis de la supprimer de NOT IN (variable temporaire)? @Bill merci pour une solution très utile.

EDIT: Je pense avoir trouvé la solution:

DROP TABLE IF EXISTS UniqueIDs; 
CREATE Temporary table UniqueIDs (id Int(11)); 

INSERT INTO UniqueIDs 
    (SELECT T1.ID FROM Table T1 LEFT JOIN Table T2 ON 
    (T1.Field1 = T2.Field1 AND T1.Field2 = T2.Field2 #Comparison Fields  
    AND T1.ID < T2.ID) 
    WHERE T2.ID IS NULL); 

DELETE FROM Table WHERE id NOT IN (SELECT ID FROM UniqueIDs);
Simon
la source
2

La requête ci-dessous fonctionnera correctement selon votre question.

SELECT M1.* 
FROM MESSAGES M1,
(
 SELECT SUBSTR(Others_data,1,2),MAX(Others_data) AS Max_Others_data
 FROM MESSAGES
 GROUP BY 1
) M2
WHERE M1.Others_data = M2.Max_Others_data
ORDER BY Others_data;
Teja
la source
2

Si vous voulez la dernière ligne pour chacun Name, vous pouvez attribuer un numéro de ligne à chaque groupe de lignes par l' Nameordre et par Idordre décroissant.

REQUETE

SELECT t1.Id, 
       t1.Name, 
       t1.Other_Columns
FROM 
(
     SELECT Id, 
            Name, 
            Other_Columns,
    (
        CASE Name WHEN @curA 
        THEN @curRow := @curRow + 1 
        ELSE @curRow := 1 AND @curA := Name END 
    ) + 1 AS rn 
    FROM messages t, 
    (SELECT @curRow := 0, @curA := '') r 
    ORDER BY Name,Id DESC 
)t1
WHERE t1.rn = 1
ORDER BY t1.Id;

SQL Fiddle

Ullas
la source
2

Que dis-tu de ça:

SELECT DISTINCT ON (name) *
FROM messages
ORDER BY name, id DESC;

J'ai eu un problème similaire (sur postgresql difficile) et sur une table d'enregistrements 1M. Cette solution prend 1,7s contre 44s produites par celui avec LEFT JOIN. Dans mon cas, j'ai dû filtrer le corrigeant de votre champ de nom par rapport aux valeurs NULL, résultant en de meilleures performances de 0,2 seconde

Azathoth
la source
1

Si les performances sont vraiment votre préoccupation, vous pouvez introduire une nouvelle colonne sur la table appelée IsLastInGroupde type BIT.

Réglez-le sur true sur les dernières colonnes et conservez-le à chaque insertion / mise à jour / suppression de ligne. Les écritures seront plus lentes, mais vous bénéficierez des lectures. Cela dépend de votre cas d'utilisation et je le recommande uniquement si vous êtes concentré sur la lecture.

Votre requête ressemblera donc à:

SELECT * FROM Messages WHERE IsLastInGroup = 1
michal.jakubeczy
la source
Certaines tables dans Moodle ont une colonne d'indicateur comme celle-ci.
Lawrence
0
select * from messages group by name desc
huuang
la source
0

Vous pouvez grouper en comptant et obtenir également le dernier élément du groupe comme:

SELECT 
    user,
    COUNT(user) AS count,
    MAX(id) as last
FROM request 
GROUP BY user
Amir Fo
la source
0

J'espère que la requête Oracle ci-dessous peut vous aider:

WITH Temp_table AS
(
    Select id, name, othercolumns, ROW_NUMBER() over (PARTITION BY name ORDER BY ID 
    desc)as rank from messages
)
Select id, name,othercolumns from Temp_table where rank=1
Kiruba
la source
0

Une autre approche:

Trouvez la propriété avec le m2_price max avec chaque programme (n propriétés dans 1 programme):

select * from properties p
join (
    select max(m2_price) as max_price 
    from properties 
    group by program_id
) p2 on (p.program_id = p2.program_id)
having p.m2_price = max_price
Ka.
la source