web-dev-qa-db-fra.com

Obtenez le dernier ensemble distinct d'enregistrements

J'ai une table de base de données contenant les colonnes suivantes:

id   code   value   datetime   timestamp

Dans ce tableau, les seules valeurs uniques résident dans id, c'est-à-dire la clé primaire.

Je souhaite récupérer le dernier ensemble distinct d'enregistrements dans cette table en fonction de la valeur datetime. Par exemple, disons ci-dessous ma table

id   code   value   datetime               timestamp
1    1023   23.56   2011-04-05 14:54:52    1234223421
2    1024   23.56   2011-04-05 14:55:52    1234223423
3    1025   23.56   2011-04-05 14:56:52    1234223424
4    1023   23.56   2011-04-05 14:57:52    1234223425
5    1025   23.56   2011-04-05 14:58:52    1234223426
6    1025   23.56   2011-04-05 14:59:52    1234223427
7    1024   23.56   2011-04-05 15:00:12    1234223428
8    1026   23.56   2011-04-05 15:01:14    1234223429
9    1025   23.56   2011-04-05 15:02:22    1234223430

Je veux récupérer les enregistrements avec les ID 4, 7, 8 et 9, c'est-à-dire le dernier ensemble d'enregistrements avec des codes distincts (basés sur la valeur datetime). Ce que j'ai mis en évidence n'est qu'un exemple de ce que j'essaie de réaliser, car ce tableau va éventuellement contenir des millions d'enregistrements et des centaines de valeurs de code individuelles.

Quelle instruction SQL puis-je utiliser pour y parvenir? Je n'arrive pas à le faire avec une seule instruction SQL. Ma base de données est MySQL 5.

43
Obinwanne Hill

Cela devrait fonctionner pour vous.

 SELECT * 
 FROM [tableName] 
 WHERE id IN (SELECT MAX(id) FROM [tableName] GROUP BY code)

Si id est AUTO_INCREMENT, vous n'avez pas à vous soucier de la date/heure qui est beaucoup plus coûteuse à calculer, car la date/heure la plus récente aura également l'id le plus élevé.

pdate: Du point de vue des performances, assurez-vous que les colonnes id et code sont indexées lorsque vous traitez un grand nombre d'enregistrements. Si id est la clé primaire, elle est intégrée, mais vous devrez peut-être ajouter un index non groupé couvrant code et id.

71
smdrager

Essaye ça:

SELECT * 
  FROM <YOUR_TABLE>
 WHERE (code, datetime, timestamp) IN
 (
   SELECT code, MAX(datetime), MAX(timestamp)
     FROM <YOUR_TABLE>
    GROUP BY code
 )
8
Chandu

C'est un vieux message, mais tester la réponse de @smdrager avec de grandes tables était très lent. Ma solution à cela était d'utiliser "jointure interne" au lieu de "où".

SELECT * 
 FROM [tableName] as t1
 INNER JOIN (SELECT MAX(id) as id FROM [tableName] GROUP BY code) as t2
 ON t1.id = t2.id

Cela a fonctionné très rapidement.

3
educolo

Je vais essayer quelque chose comme ça:

select * from table
where id in (
    select id
    from table
    group by code
    having datetime = max(datetime)
)

(avertissement: ceci n'est pas testé)

Si la ligne avec le plus grand datetime a également le plus grand id, la solution proposée par smdrager est plus rapide.

1
krtek

Il semble que toutes les réponses existantes suggèrent de faire GROUP BY code sur toute la table. Quand elle est logiquement correcte, en réalité cette requête passera par toute la table (!) (Utilisez EXPLAIN pour vous en assurer). Dans mon cas, j'ai moins de 500 Ko de lignes dans la table et j'exécute ...GROUP BY codeprend 0,3 seconde, ce qui n'est absolument pas acceptable.

Cependant, je peux utiliser la connaissance de mes données ici (lire "afficher les derniers commentaires pour les messages"):

  • Je dois sélectionner uniquement les 20 meilleurs enregistrements
  • Le nombre d'enregistrements avec le même code sur les derniers enregistrements X est relativement faible
  • Quantité totale d'enregistrements >> quantité d'enregistrements disponibles code >> quantité d'enregistrements "supérieurs" que vous souhaitez obtenir

En expérimentant avec des nombres, j'ai découvert que je peux toujours trouver 20 code différents si je sélectionne seulement les 50 derniers enregistrements. Et dans ce cas, les travaux de requête suivants (en gardant à l'esprit le commentaire @smdrager sur la forte probabilité d'utiliser id au lieu de datetime)

SELECT id, code
FROM tablename
ORDER BY id DESC 
LIMIT 50

La sélection des 50 dernières entrées est très rapide, car elle n'a pas besoin de vérifier l'ensemble du tableau. Et le reste consiste à sélectionner le top 20 avec des code distincts parmi ces 50 entrées.

De toute évidence, les requêtes sur l'ensemble des 50 (100, 500) éléments sont nettement plus rapides que sur l'ensemble du tableau avec des centaines de milliers d'entrées.

"Post-traitement" SQL brut

SELECT MAX(id) as id, code FROM 
    (SELECT id, code
     FROM tablename
     ORDER BY id DESC 
     LIMIT 50) AS nested 
GROUP BY code
ORDER BY id DESC 
LIMIT 20

Cela vous donnera une liste de id très rapide et si vous souhaitez effectuer des JOIN supplémentaires, placez cette requête comme une autre requête imbriquée et effectuez toutes les jointures dessus.

"Post-traitement" côté backend

Et après cela, vous devez traiter les données dans votre langage de programmation pour inclure dans l'ensemble final uniquement les enregistrements avec un code distinct.

Une sorte de pseudo-code Python:

records = select_simple_top_records(50)
added_codes = []
top_records = []
for record in records:
    # If record for this code was already found before
    # Note: this is not optimal, better to use structure allowing O(1) search and insert
    if record['code'] in added_codes:
        continue
    # Save record
    top_records.append(record)
    added_codes.append(record['code'])
    # If we found all top-20 required, finish
    if len(top_records) >= 20:
        break
0
The Godfather