web-dev-qa-db-fra.com

Comment puis-je sélectionner des lignes avec MAX (valeur de colonne), DISTINCT par une autre colonne en SQL?

Ma table est:  

id  home  datetime     player   resource
---|-----|------------|--------|---------
1  | 10  | 04/03/2009 | john   | 399 
2  | 11  | 04/03/2009 | juliet | 244
5  | 12  | 04/03/2009 | borat  | 555
3  | 10  | 03/03/2009 | john   | 300
4  | 11  | 03/03/2009 | juliet | 200
6  | 12  | 03/03/2009 | borat  | 500
7  | 13  | 24/12/2008 | borat  | 600
8  | 13  | 01/01/2009 | borat  | 700

Je dois sélectionner chaque home distincte dont la valeur maximale est datetime

Le résultat serait:  

id  home  datetime     player   resource 
---|-----|------------|--------|---------
1  | 10  | 04/03/2009 | john   | 399
2  | 11  | 04/03/2009 | juliet | 244
5  | 12  | 04/03/2009 | borat  | 555
8  | 13  | 01/01/2009 | borat  | 700

J'ai essayé:

-- 1 ..by the MySQL manual: 

SELECT DISTINCT
  home,
  id,
  datetime AS dt,
  player,
  resource
FROM topten t1
WHERE datetime = (SELECT
  MAX(t2.datetime)
FROM topten t2
GROUP BY home)
GROUP BY datetime
ORDER BY datetime DESC

Ça ne marche pas Le jeu de résultats a 130 lignes bien que la base de données en contienne 187. Le résultat inclut des doublons de home.

-- 2 ..join

SELECT
  s1.id,
  s1.home,
  s1.datetime,
  s1.player,
  s1.resource
FROM topten s1
JOIN (SELECT
  id,
  MAX(datetime) AS dt
FROM topten
GROUP BY id) AS s2
  ON s1.id = s2.id
ORDER BY datetime 

Nan. Donne tous les enregistrements.

-- 3 ..something exotic: 

Avec des résultats variés.

678
Kaptah

Tu es si proche! Tout ce que vous avez à faire est de sélectionner à la fois le domicile et sa date et heure maximales, puis de rejoindre la table topten sur les DEUX champs:

SELECT tt.*
FROM topten tt
INNER JOIN
    (SELECT home, MAX(datetime) AS MaxDateTime
    FROM topten
    GROUP BY home) groupedtt 
ON tt.home = groupedtt.home 
AND tt.datetime = groupedtt.MaxDateTime
835
Michael La Voie

Voici la version T-SQL:

-- Test data
DECLARE @TestTable TABLE (id INT, home INT, date DATETIME, 
  player VARCHAR(20), resource INT)
INSERT INTO @TestTable
SELECT 1, 10, '2009-03-04', 'john', 399 UNION
SELECT 2, 11, '2009-03-04', 'juliet', 244 UNION
SELECT 5, 12, '2009-03-04', 'borat', 555 UNION
SELECT 3, 10, '2009-03-03', 'john', 300 UNION
SELECT 4, 11, '2009-03-03', 'juliet', 200 UNION
SELECT 6, 12, '2009-03-03', 'borat', 500 UNION
SELECT 7, 13, '2008-12-24', 'borat', 600 UNION
SELECT 8, 13, '2009-01-01', 'borat', 700

-- Answer
SELECT id, home, date, player, resource 
FROM (SELECT id, home, date, player, resource, 
    RANK() OVER (PARTITION BY home ORDER BY date DESC) N
    FROM @TestTable
)M WHERE N = 1

-- and if you really want only home with max date
SELECT T.id, T.home, T.date, T.player, T.resource 
    FROM @TestTable T
INNER JOIN 
(   SELECT TI.id, TI.home, TI.date, 
        RANK() OVER (PARTITION BY TI.home ORDER BY TI.date) N
    FROM @TestTable TI
    WHERE TI.date IN (SELECT MAX(TM.date) FROM @TestTable TM)
)TJ ON TJ.N = 1 AND T.id = TJ.id

MODIFIER
Malheureusement, il n’existe pas de fonction RANK () OVER dans MySQL.
Mais il peut être émulé, voir Fonctions de émulation d’analytique (Classement AKA) avec MySQL .
Donc, ceci est la version MySQL:

SELECT id, home, date, player, resource 
FROM TestTable AS t1 
WHERE 
    (SELECT COUNT(*) 
            FROM TestTable AS t2 
            WHERE t2.home = t1.home AND t2.date > t1.date
    ) = 0
69
Maksym Gontar

La solution MySQL la plus rapide, sans requêtes internes et sans GROUP BY:

SELECT m.*                    -- get the row that contains the max value
FROM topten m                 -- "m" from "max"
    LEFT JOIN topten b        -- "b" from "bigger"
        ON m.home = b.home    -- match "max" row with "bigger" row by `home`
        AND m.datetime < b.datetime           -- want "bigger" than "max"
WHERE b.datetime IS NULL      -- keep only if there is no bigger than max

Explication:

Joignez la table avec lui-même en utilisant la colonne home. L'utilisation de LEFT JOIN garantit que toutes les lignes de la table m apparaissent dans le jeu de résultats. Ceux qui n'ont pas de correspondance dans la table b auront NULLs pour les colonnes de b.

L'autre condition de la variable JOIN demande de ne faire correspondre que les lignes de b dont la valeur de la colonne datetime est supérieure à celle de la ligne de m.

En utilisant les données postées dans la question, le LEFT JOIN produira les paires suivantes:

+------------------------------------------+--------------------------------+
|              the row from `m`            |    the matching row from `b`   |
|------------------------------------------|--------------------------------|
| id  home  datetime     player   resource | id    home   datetime      ... |
|----|-----|------------|--------|---------|------|------|------------|-----|
| 1  | 10  | 04/03/2009 | john   | 399     | NULL | NULL | NULL       | ... | *
| 2  | 11  | 04/03/2009 | juliet | 244     | NULL | NULL | NULL       | ... | *
| 5  | 12  | 04/03/2009 | borat  | 555     | NULL | NULL | NULL       | ... | *
| 3  | 10  | 03/03/2009 | john   | 300     | 1    | 10   | 04/03/2009 | ... |
| 4  | 11  | 03/03/2009 | juliet | 200     | 2    | 11   | 04/03/2009 | ... |
| 6  | 12  | 03/03/2009 | borat  | 500     | 5    | 12   | 04/03/2009 | ... |
| 7  | 13  | 24/12/2008 | borat  | 600     | 8    | 13   | 01/01/2009 | ... |
| 8  | 13  | 01/01/2009 | borat  | 700     | NULL | NULL | NULL       | ... | *
+------------------------------------------+--------------------------------+

Enfin, la clause WHERE conserve uniquement les paires qui ont NULLs dans les colonnes de b (elles sont marquées de * dans le tableau ci-dessus); Cela signifie que, en raison de la deuxième condition de la clause JOIN, la ligne sélectionnée dans m a la plus grande valeur de la colonne datetime.

Lisez les Les anti-modèles SQL: éviter les pièges de la programmation de base de données book pour d'autres astuces SQL.

60
axiac

Cela fonctionnera même si vous avez deux ou plusieurs lignes pour chaque home avec des _ égaux DATETIME:

SELECT id, home, datetime, player, resource
FROM   (
       SELECT (
              SELECT  id
              FROM    topten ti
              WHERE   ti.home = t1.home
              ORDER BY
                      ti.datetime DESC
              LIMIT 1
              ) lid
       FROM   (
              SELECT  DISTINCT home
              FROM    topten
              ) t1
       ) ro, topten t2
WHERE  t2.id = ro.lid
26
Quassnoi

Je pense que cela vous donnera le résultat souhaité:

SELECT   home, MAX(datetime)
FROM     my_table
GROUP BY home

MAISsi vous avez également besoin d'autres colonnes, créez une jointure avec la table d'origine (cochez Michael La Voie answer)

Meilleures salutations.

21

Puisque les gens semblent continuer à courir sur ce fil (la date du commentaire est comprise dans un an et demi), ce n’est pas si simple:

SELECT * FROM (SELECT * FROM topten ORDER BY datetime DESC) tmp GROUP BY home

Aucune fonction d'agrégation nécessaire ...

À votre santé.

16
MJB

Vous pouvez également essayer celui-ci et pour les grandes tables, les performances seront meilleures. Cela fonctionne quand il n'y a pas plus de deux enregistrements pour chaque maison et que leurs dates sont différentes. La meilleure requête générale pour MySQL est celle de Michael La Voie ci-dessus. 

SELECT t1.id, t1.home, t1.date, t1.player, t1.resource
FROM   t_scores_1 t1 
INNER JOIN t_scores_1 t2
   ON t1.home = t2.home
WHERE t1.date > t2.date

Ou dans le cas de Postgres ou de ces dbs qui fournissent des fonctions analytiques, essayez

SELECT t.* FROM 
(SELECT t1.id, t1.home, t1.date, t1.player, t1.resource
  , row_number() over (partition by t1.home order by t1.date desc) rw
 FROM   topten t1 
 INNER JOIN topten t2
   ON t1.home = t2.home
 WHERE t1.date > t2.date 
) t
WHERE t.rw = 1
10
Shiva

Cela fonctionne sur Oracle:

with table_max as(
  select id
       , home
       , datetime
       , player
       , resource
       , max(home) over (partition by home) maxhome
    from table  
)
select id
     , home
     , datetime
     , player
     , resource
  from table_max
 where home = maxhome
8
FerranB
SELECT  tt.*
FROM    TestTable tt 
INNER JOIN 
        (
        SELECT  coord, MAX(datetime) AS MaxDateTime 
        FROM    rapsa 
        GROUP BY
                krd 
        ) groupedtt
ON      tt.coord = groupedtt.coord
        AND tt.datetime = groupedtt.MaxDateTime
7
Kaptah

Essayez ceci pour SQL Server:

WITH cte AS (
   SELECT home, MAX(year) AS year FROM Table1 GROUP BY home
)
SELECT * FROM Table1 a INNER JOIN cte ON a.home = cte.home AND a.year = cte.year
7
SysDragon

Une autre façon de gt la dernière rangée par groupe en utilisant une sous-requête qui calcule fondamentalement un rang pour chaque rangée par groupe, puis filtre vos rangées les plus récentes comme avec rank = 1

select a.*
from topten a
where (
  select count(*)
  from topten b
  where a.home = b.home
  and a.`datetime` < b.`datetime`
) +1 = 1

D&EACUTE;MO

Voici la démo visuelle pour le numéro de rang pour chaque ligne pour une meilleure compréhension. 

En lisant quelques commentaires que se passe-t-il s'il y a deux lignes qui ont les mêmes valeurs de champs 'home' et 'datetime'?

La requête ci-dessus échouera et renverra plus de 1 lignes pour la situation ci-dessus. Pour couvrir cette situation, il faudra recourir à un autre critère/paramètre/colonne pour décider de la ligne à prendre qui correspond à la situation ci-dessus. En consultant un exemple de fichier, je suppose qu’il existe une colonne de clé primaire id qui devrait être définie sur incrémentation automatique. Nous pouvons donc utiliser cette colonne pour sélectionner la ligne la plus récente en ajustant la même requête à l'aide de l'instruction CASE comme

select a.*
from topten a
where (
  select count(*)
  from topten b
  where a.home = b.home
  and  case 
       when a.`datetime` = b.`datetime`
       then a.id < b.id
       else a.`datetime` < b.`datetime`
       end
) + 1 = 1

D&EACUTE;MO

La requête ci-dessus choisira la ligne avec le plus grand identifiant parmi les mêmes valeurs datetime 

démo visuelle pour le numéro de rang pour chaque ligne

4
M Khalid Junaid
SELECT c1, c2, c3, c4, c5 FROM table1 WHERE c3 = (select max(c3) from table)

SELECT * FROM table1 WHERE c3 = (select max(c3) from table1)
4
Jr.

Voici la version de MySQL qui n’imprime qu’une entrée dans laquelle se trouvent des doublons MAX (date/heure) dans un groupe.

Vous pouvez tester ici http://www.sqlfiddle.com/#!2/0a4ae/1

Échantillon de données

mysql> SELECT * from topten;
+------+------+---------------------+--------+----------+
| id   | home | datetime            | player | resource |
+------+------+---------------------+--------+----------+
|    1 |   10 | 2009-04-03 00:00:00 | john   |      399 |
|    2 |   11 | 2009-04-03 00:00:00 | juliet |      244 |
|    3 |   10 | 2009-03-03 00:00:00 | john   |      300 |
|    4 |   11 | 2009-03-03 00:00:00 | juliet |      200 |
|    5 |   12 | 2009-04-03 00:00:00 | borat  |      555 |
|    6 |   12 | 2009-03-03 00:00:00 | borat  |      500 |
|    7 |   13 | 2008-12-24 00:00:00 | borat  |      600 |
|    8 |   13 | 2009-01-01 00:00:00 | borat  |      700 |
|    9 |   10 | 2009-04-03 00:00:00 | borat  |      700 |
|   10 |   11 | 2009-04-03 00:00:00 | borat  |      700 |
|   12 |   12 | 2009-04-03 00:00:00 | borat  |      700 |
+------+------+---------------------+--------+----------+

Version MySQL avec variable utilisateur

SELECT *
FROM (
    SELECT ord.*,
        IF (@prev_home = ord.home, 0, 1) AS is_first_appear,
        @prev_home := ord.home
    FROM (
        SELECT t1.id, t1.home, t1.player, t1.resource
        FROM topten t1
        INNER JOIN (
            SELECT home, MAX(datetime) AS mx_dt
            FROM topten
            GROUP BY home
          ) x ON t1.home = x.home AND t1.datetime = x.mx_dt
        ORDER BY home
    ) ord, (SELECT @prev_home := 0, @seq := 0) init
) y
WHERE is_first_appear = 1;
+------+------+--------+----------+-----------------+------------------------+
| id   | home | player | resource | is_first_appear | @prev_home := ord.home |
+------+------+--------+----------+-----------------+------------------------+
|    9 |   10 | borat  |      700 |               1 |                     10 |
|   10 |   11 | borat  |      700 |               1 |                     11 |
|   12 |   12 | borat  |      700 |               1 |                     12 |
|    8 |   13 | borat  |      700 |               1 |                     13 |
+------+------+--------+----------+-----------------+------------------------+
4 rows in set (0.00 sec)

Réponses acceptées

SELECT tt.*
FROM topten tt
INNER JOIN
    (
    SELECT home, MAX(datetime) AS MaxDateTime
    FROM topten
    GROUP BY home
) groupedtt ON tt.home = groupedtt.home AND tt.datetime = groupedtt.MaxDateTime
+------+------+---------------------+--------+----------+
| id   | home | datetime            | player | resource |
+------+------+---------------------+--------+----------+
|    1 |   10 | 2009-04-03 00:00:00 | john   |      399 |
|    2 |   11 | 2009-04-03 00:00:00 | juliet |      244 |
|    5 |   12 | 2009-04-03 00:00:00 | borat  |      555 |
|    8 |   13 | 2009-01-01 00:00:00 | borat  |      700 |
|    9 |   10 | 2009-04-03 00:00:00 | borat  |      700 |
|   10 |   11 | 2009-04-03 00:00:00 | borat  |      700 |
|   12 |   12 | 2009-04-03 00:00:00 | borat  |      700 |
+------+------+---------------------+--------+----------+
7 rows in set (0.00 sec)
3
Jason Heo

Pourquoi ne pas utiliser les éléments suivants:

1
Roland

Essaye ça

select * from mytable a join
(select home, max(datetime) datetime
from mytable
group by home) b
 on a.home = b.home and a.datetime = b.datetime

Cordialement K

1
Khb

c'est la requête dont vous avez besoin:

 SELECT b.id, a.home,b.[datetime],b.player,a.resource FROM
 (SELECT home,MAX(resource) AS resource FROM tbl_1 GROUP BY home) AS a

 LEFT JOIN

 (SELECT id,home,[datetime],player,resource FROM tbl_1) AS b
 ON  a.resource = b.resource WHERE a.home =b.home;
0
Vijunav Vastivch

@Michae La réponse acceptée fonctionnera très bien dans la plupart des cas, mais échouera pour un des cas indiqués ci-dessous.

Dans le cas où il y aurait deux lignes ayant HomeID et Datetime identiques, la requête retournera les deux lignes, pas HomeID distinct comme requis, pour cela ajouter Distinct dans la requête comme ci-dessous.

SELECT DISTINCT tt.home  , tt.MaxDateTime
FROM topten tt
INNER JOIN
    (SELECT home, MAX(datetime) AS MaxDateTime
    FROM topten
    GROUP BY home) groupedtt 
ON tt.home = groupedtt.home 
AND tt.datetime = groupedtt.MaxDateTime
0
Manoj Kargeti