Uzyskiwanie rekordów z maksymalną wartością dla każdej grupy zgrupowanych wyników SQL

Jak uzyskać wiersze, które zawierają maksymalną wartość dla każdego zgrupowanego zestawu?

Widziałem kilka zbyt skomplikowanych wariacji na to pytanie, i żaden z dobrą odpowiedzią. Próbowałem ułożyć najprostszy możliwy przykład:

Biorąc pod uwagę tabelę taką jak ta poniżej, z kolumnami osoby, grupy i wieku, jak można uzyskać najstarszą osobę w każdej grupie? (Remis w grupie powinien dać pierwszy wynik alfabetyczny)

Person | Group | Age
---
Bob  | 1     | 32  
Jill | 1     | 34  
Shawn| 1     | 42  
Jake | 2     | 29  
Paul | 2     | 36  
Laura| 2     | 39  

Pożądany zbiór wyników:

Shawn | 1     | 42    
Laura | 2     | 39  
Author: axiac, 2012-08-24

17 answers

Jest super-prosty sposób, aby to zrobić w mysql:

select * 
from (select * from mytable order by `Group`, age desc, Person) x
group by `Group`

To działa, ponieważ w mysql możesz nie agregować kolumny bez grupowania według, w którym to przypadku mysql zwraca po prostu pierwszy wiersz. Rozwiązaniem jest najpierw uporządkowanie danych w taki sposób, aby dla każdej grupy najpierw był żądany wiersz, a następnie pogrupowanie według kolumn, dla których ma być podana wartość.

Unikasz skomplikowanych zapytań podrzędnych, które próbują znaleźć max() itd, a także problemów ze zwracaniem wielu wierszy, gdy istnieje więcej niż jedna o tej samej maksymalnej wartości (tak jak zrobiłyby to inne odpowiedzi)

Uwaga: jest to rozwiązaniemysql-only . Wszystkie inne bazy danych, które znam, wyświetlą błąd składni SQL z Komunikatem "nie zagregowane kolumny nie są wymienione w klauzuli group by" lub podobnym. Ponieważ to rozwiązanie używa undocumented zachowanie, bardziej ostrożny może chcieć dołączyć test, aby upewnić się, że pozostaje działa, jeśli przyszła wersja MySQL zmienić to zachowanie.

Wersja 5.7 update:

Od wersji 5.7,sql-mode Zestaw zawiera ONLY_FULL_GROUP_BY domyślnie, aby to działało, musisz nie mieć tę opcję(Edytuj plik opcji dla serwera, aby usunąć to ustawienie).

 117
Author: Bohemian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-30 20:49:49

Poprawne rozwiązanie to:

SELECT o.*
FROM `Persons` o                    # 'o' from 'oldest person in group'
  LEFT JOIN `Persons` b             # 'b' from 'bigger age'
      ON o.Group = b.Group AND o.Age < b.Age
WHERE b.Age is NULL                 # bigger age not found

Jak to działa:

Pasuje do każdego wiersza z o ze wszystkimi wierszami z b o tej samej wartości w kolumnie Group i większej wartości w kolumnie Age. Każdy wiersz z o nie mający maksymalnej wartości swojej grupy w kolumnie Age będzie pasował do jednego lub więcej wierszy z b.

The LEFT JOIN sprawia, że pasuje do najstarszej osoby w grupie (w tym osób, które są same w swojej grupie) z wierszem pełnym NULL S z b ("nie największy wiek w grupie").
użycie INNER JOIN sprawia, że te wiersze nie pasują i są ignorowane.

Klauzula WHERE przechowuje tylko wiersze posiadające NULLs w polach wyodrębnionych z b. Są najstarszymi osobami z każdej grupy.

Dalsze odczyty

To rozwiązanie i wiele innych zostało wyjaśnione w książce Antypatterny SQL: unikanie pułapek programowania baz danych

 218
Author: axiac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-22 13:56:25

Moje proste rozwiązanie dla SQLite (i prawdopodobnie MySQL):

SELECT *, MAX(age) FROM mytable GROUP BY `Group`;

Jednak nie działa w PostgreSQL i może na innych platformach.

W PostgreSQL możesz użyć DISTINCT na klauzuli:

SELECT DISTINCT ON ("group") * FROM "mytable" ORDER BY "group", "age" DESC;
 25
Author: Igor Kulagin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-02 11:55:38

Możesz dołączyć przeciwko zapytaniu podrzędnemu, które ciągnie MAX(Group) i Age. Ta metoda jest przenośna w większości RDBMS.

SELECT t1.*
FROM yourTable t1
INNER JOIN
(
    SELECT `Group`, MAX(Age) AS max_age
    FROM yourTable
    GROUP BY `Group`
) t2
    ON t1.`Group` = t2.`Group` AND t1.Age = t2.max_age;
 24
Author: Michael Berkowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-06 02:20:57

Przy użyciu metody rankingowej.

SELECT @rn :=  CASE WHEN @prev_grp <> groupa THEN 1 ELSE @rn+1 END AS rn,  
   @prev_grp :=groupa,
   person,age,groupa  
FROM   users,(SELECT @rn := 0) r        
HAVING rn=1
ORDER  BY groupa,age DESC,person
 3
Author: sel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-24 01:52:03

Rozwiązanie Axiaca zadziałało dla mnie najlepiej. Miałem jednak dodatkową złożoność: obliczoną "wartość maksymalną", pochodzącą z dwóch kolumn.

Użyjmy tego samego przykładu: chciałbym najstarszej osoby w każdej grupie. Jeśli są ludzie, którzy są równie stare, weź najwyższą osobę.

Musiałem wykonać lewe połączenie dwa razy, aby uzyskać takie zachowanie:

SELECT o1.* WHERE
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o1
LEFT JOIN
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o2
ON o1.Group = o2.Group AND o1.Height < o2.Height 
WHERE o2.Height is NULL;
Mam nadzieję, że to pomoże! Myślę, że powinien być lepszy sposób, aby to zrobić...
 2
Author: Arthur C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-14 13:30:45

Używanie Wyrażeń CTEs - Common Table:

WITH MyCTE(MaxPKID, SomeColumn1)
AS(
SELECT MAX(a.MyTablePKID) AS MaxPKID, a.SomeColumn1
FROM MyTable1 a
GROUP BY a.SomeColumn1
  )
SELECT b.MyTablePKID, b.SomeColumn1, b.SomeColumn2 MAX(b.NumEstado)
FROM MyTable1 b
INNER JOIN MyCTE c ON c.MaxPKID = b.MyTablePKID
GROUP BY b.MyTablePKID, b.SomeColumn1, b.SomeColumn2

--Note: MyTablePKID is the PrimaryKey of MyTable
 1
Author: Marvin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-19 16:22:08

Nie jestem pewien czy MySQL ma funkcję row_number. Jeśli tak, możesz go użyć, aby uzyskać pożądany rezultat. Na SQL Server możesz zrobić coś podobnego do:

CREATE TABLE p
(
 person NVARCHAR(10),
 gp INT,
 age INT
);
GO
INSERT  INTO p
VALUES  ('Bob', 1, 32);
INSERT  INTO p
VALUES  ('Jill', 1, 34);
INSERT  INTO p
VALUES  ('Shawn', 1, 42);
INSERT  INTO p
VALUES  ('Jake', 2, 29);
INSERT  INTO p
VALUES  ('Paul', 2, 36);
INSERT  INTO p
VALUES  ('Laura', 2, 39);
GO

SELECT  t.person, t.gp, t.age
FROM    (
         SELECT *,
                ROW_NUMBER() OVER (PARTITION BY gp ORDER BY age DESC) row
         FROM   p
        ) t
WHERE   t.row = 1;
 1
Author: user130268,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-10 21:56:46

Moje rozwiązanie działa tylko wtedy, gdy potrzebujesz odzyskać tylko jedną kolumnę, jednak dla moich potrzeb było najlepsze rozwiązanie pod względem wydajności (używaj tylko jednego zapytania!):

SELECT SUBSTRING_INDEX(GROUP_CONCAT(column_x ORDER BY column_y),',',1) AS xyz,
   column_z
FROM table_name
GROUP BY column_z;

Używa GROUP_CONCAT w celu utworzenia uporządkowanej listy concat, a następnie i substring tylko do pierwszej.

 1
Author: Antonio Giovanazzi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-28 09:48:04

Możesz również spróbować

SELECT * FROM mytable WHERE age IN (SELECT MAX(age) FROM mytable GROUP BY `Group`) ;
 0
Author: Ritwik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-27 13:51:02

Ta metoda ma tę zaletę, że pozwala na uszeregowanie według innej kolumny, a nie niszczenie innych danych. Jest to bardzo przydatne w sytuacji, gdy próbujesz wymienić zamówienia z kolumną dla przedmiotów, wymieniając najcięższe jako pierwsze.

Źródło: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat

SELECT person, group,
    GROUP_CONCAT(
        DISTINCT age
        ORDER BY age DESC SEPARATOR ', follow up: '
    )
FROM sql_table
GROUP BY group;
 0
Author: Ray Foss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-13 14:30:12

Niech nazwą tabeli będą ludzie

select O.*              -- > O for oldest table
from people O , people T
where O.grp = T.grp and 
O.Age = 
(select max(T.age) from people T where O.grp = T.grp
  group by T.grp)
group by O.grp; 
 0
Author: user3475425,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-10 11:31:26

Jeśli ID (i wszystkie coulmns) jest potrzebne z mytable

SELECT
    *
FROM
    mytable
WHERE
    id NOT IN (
        SELECT
            A.id
        FROM
            mytable AS A
        JOIN mytable AS B ON A. GROUP = B. GROUP
        AND A.age < B.age
    )
 0
Author: mayank kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-10 07:39:22

W ten sposób otrzymuję n max wierszy na grupę w mysql

SELECT co.id, co.person, co.country
FROM person co
WHERE (
SELECT COUNT(*)
FROM person ci
WHERE  co.country = ci.country AND co.id < ci.id
) < 1
;

Jak to działa:

  • self join to the table
  • grupy są wykonywane przez co.country = ci.country
  • N elementów na Grupę są kontrolowane przez ) < 1 więc dla 3 elementów -)
  • aby uzyskać max lub min zależy od: co.id < ci.id
    • co.id
    • co.id > ci.id -min

Pełny przykład tutaj:

Mysql select N max wartości na Grupę

 0
Author: Vanko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-28 07:11:26

Mam proste rozwiązanie używając WHERE IN

SELECT a.* FROM `mytable` AS a    
WHERE a.age IN( SELECT MAX(b.age) AS age FROM `mytable` AS b GROUP BY b.group )    
ORDER BY a.group ASC, a.person ASC
 0
Author: Khalid Musa Sagar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-11 20:29:27
with CTE as 
(select Person, 
[Group], Age, RN= Row_Number() 
over(partition by [Group] 
order by Age desc) 
from yourtable)`


`select Person, Age from CTE where RN = 1`
 -1
Author: Harshad C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-27 07:38:37

Nie użyłbym grupy jako nazwy kolumny, ponieważ jest to słowo zarezerwowane. Jednak po SQL będzie działać.

SELECT a.Person, a.Group, a.Age FROM [TABLE_NAME] a
INNER JOIN 
(
  SELECT `Group`, MAX(Age) AS oldest FROM [TABLE_NAME] 
  GROUP BY `Group`
) b ON a.Group = b.Group AND a.Age = b.oldest
 -1
Author: Bae Cheol Shin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-30 23:26:16