2009-05-29 9 views
45

Je le tableau suivant:Sélectionnez les 3 derniers enregistrements où les valeurs d'une colonne sont distinctes

id  time  text  otheridentifier 
    ------------------------------------------- 
    1  6   apple  4 
    2  7   orange 4 
    3  8   banana 3 
    4  9   pear  3 
    5  10  grape  2 

Ce que je veux faire est de sélectionner les 3 derniers enregistrements (par le temps desc), dont otheridentifier s sont distincts. Donc, dans ce cas, le résultat serait id « s: 5, 4 et 2.

id = 3 serait sautée parce qu'il ya un dossier plus récent avec le même domaine otheridentifier.

Voici ce que j'essayé de faire:

SELECT * FROM `table` GROUP BY (`otheridentifier`) ORDER BY `time` DESC LIMIT 3 

Cependant, je finis par obtenir des lignes de id = 5, et au lieu de 5, 4, 2 comme prévu. Est-ce que quelqu'un peut me dire pourquoi cette requête ne retournerait pas ce que je m'attendais? J'ai essayé de changer le ORDER BY en ASC mais cela réorganise simplement les lignes retournées à 1, 3, 5.

Répondre

34

Cela ne retourne pas ce que vous attendez parce que le regroupement se produit avant la commande, comme reflété par la position des clauses dans le Instruction SQL Vous allez malheureusement devoir obtenir plus de fantaisie pour obtenir les rangées que vous voulez. Essayez ceci:

SELECT * 
FROM `table` 
WHERE `id` = (
    SELECT `id` 
    FROM `table` as `alt` 
    WHERE `alt`.`otheridentifier` = `table`.`otheridentifier` 
    ORDER BY `time` DESC 
    LIMIT 1 
) 
ORDER BY `time` DESC 
LIMIT 3 
+1

je me suis rappelé le temps que je passais des heures à fixer comme ça et sql tourne sur MySQL 4.0 ne prend pas en charge les requêtes imbriquées; p – Unreality

+1

@Unreality: Fortu La plupart des solutions impliquant des sous-requêtes peuvent être exprimées en tant que jointures si nécessaire. :) – Rytmis

+1

Oui, mais ceux avec ORDER/LIMIT sont _not_ facilement exprimés par JOIN ... –

2
SELECT * FROM table t1 
WHERE t1.time = 
    (SELECT MAX(time) FROM table t2 
    WHERE t2.otheridentifier = t1.otheridentifier) 
+0

Comment cela sélectionnerait-il la dernière ligne par otheridentifier? – Andomar

+0

@Andomar: Je ne devrais pas essayer de répondre aux questions quand je ne suis pas complètement réveillé. Changé un peu les noms des colonnes - voyez si cela a plus de sens maintenant. :) – Rytmis

18

Vous pouvez joindre la table sur elle-même pour filtrer la dernière entrée par otheridentifier, puis prendre les 3 premières lignes de cette:

SELECT last.* 
FROM `table` last 
LEFT JOIN `table` prev 
    ON prev.`otheridentifier` = last.`otheridentifier` 
    AND prev.`time` < last.`time` 
WHERE prev.`id` is null 
ORDER BY last.`time` DESC 
LIMIT 3 
+0

Andomar, pouvez-vous expliquer comment MySQL utilise la table 'prev' dans votre requête? J'ai essayé d'utiliser ceci sur une requête locale et j'obtiens une erreur en disant que 'database.prev' n'existe pas. –

+0

Fiddle avec les données OP et votre requête. Je ne sais pas pourquoi cette réponse a été upvoted 6 fois - il ne fonctionne pas ici: http://sqlfiddle.com/#!2/ace0b/1 –

+1

@acoder: Vous avez raison, il y avait une petite erreur dans la requête: le le nom de la table après 'left join' était manquant. J'ai mis à jour la réponse. – Andomar

2

Andomar's answer est probablement mieux en qu'il n'utilise pas de sous-requête.

Une autre approche:

select * 
from `table` t1 
where t1.`time` in (
        select max(s2.`time`) 
        from  `table` t2 
        group by t2.otheridentifier 
        ) 
+0

Je pense que je vois un problème ici si les valeurs de temps ne sont pas uniques - cela pourrait renvoyer des lignes qu'il ne devrait pas. Supposons qu'il y ait une valeur de temps qui soit le maximum pour un autre identificateur, mais soit, disons, la deuxième plus grande pour un autre autre identificateur. Cette requête ne retournerait-elle pas les deux autres identifiants? Je suis peut-être tout à fait éteint, je suis toujours un peu fatigué. :) – Rytmis

+0

@Rytmis: Ouais, et ainsi de ma requête, et le vôtre :) hehe – Andomar

+0

@Andomar: Hmm, êtes-vous sûr de ma requête si? Parce que je viens de le tester en ajoutant une ligne (6, 7, 'fraise', 3) - la valeur du temps 7 est la plus grande du groupe qui a un autre identificateur 4, mais la deuxième plus grande dans celle qui a un autre identificateur 3. Mon La requête ne renvoie toujours que les lignes souhaitées par l'OP. Mon test est-il erroné? :) – Rytmis

1

Qu'en est-

SELECT *, max(time) FROM `table` group by otheridentifier 
+0

Modifier - cela fonctionne sur les données de l'OP. J'ai eu quelques jointures supplémentaires dans ma requête. Cela fonctionne bien avec les données de l'OP. http://sqlfiddle.com/#!2/ace0b/2 –

+1

Il ne semble pas fonctionner afaict. Dans votre sqlfiddle, il devrait montrer orange et poire - leur valeur de temps est plus élevée. – mahemoff

+0

@mahemoff Cela ne fonctionne pas correctement comme vous l'avez dit. – GusDeCooL

4

J'ai eu une exigence similaire, mais j'avais des critères de sélection plus avancés. En utilisant quelques-unes des autres réponses que je ne pouvais pas obtenir exactement ce que je avais besoin, mais je trouve que vous pouvez encore faire GROUP BY après et ORDER BY comme ceci:

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t 
GROUP BY t.otheridentifier 
+0

fonctionne finement pour moi !! – wiLLiamcastrO

2

Vous pouvez utiliser cette requête pour obtenir une réponse correcte:

SELECT * FROM 
     (SELECT * FROM `table` order by time DESC) 
      t group by otheridentifier 
0

Ceci est également:

SELECT * FROM 
OrigTable T INNER JOIN 
( 
SELECT otheridentifier,max(time) AS duration 
FROM T 
GROUP BY otheridentifier) S 
ON S.duration = T.time AND S.otheridentifier = T.otheridentifier.