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
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).
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 NULL
s 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
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;
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;
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
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ć...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
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;
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.
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`) ;
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;
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;
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
)
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:
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
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`
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
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