각 그룹화 된 SQL 결과 그룹에 대해 최대 값의 레코드를 가져옵니다.


229

각 그룹화 된 집합의 최대 값을 포함하는 행을 어떻게 얻습니까?

이 질문에 대해 지나치게 복잡한 변형을 보았지만 좋은 대답은 없습니다. 가장 간단한 예를 모아 보았습니다.

아래 표에 개인, 그룹 및 연령 열이있는 경우 각 그룹에서 가장 나이가 많은 사람을 어떻게 얻습니까? (그룹 내 동점은 첫 번째 알파벳 결과를 제공해야합니다)

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

원하는 결과 집합 :

Shawn | 1     | 42    
Laura | 2     | 39  

3
주의 : 수락 된 답변은 2012 년에 작성되었습니다. 그러나 주석에 제공된 것처럼 더 이상 여러 가지 이유로 작동하지 않습니다.
릭 제임스

답변:


132

mysql 에서이 작업을 수행하는 매우 간단한 방법이 있습니다.

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

mysql에서는 그룹화되지 않은 열을 집계 할 수 없으므로 mysql은 첫 번째 행을 반환하기 때문에 작동 합니다. 해결책은 각 그룹에 대해 원하는 행이 먼저 오도록 데이터를 먼저 정렬 한 다음 값을 원하는 열로 그룹화하는 것입니다.

복잡한 하위 쿼리는 피하려고합니다 max()등 와 최대 값이 같은 행이 여러 개있을 때 여러 행을 반환하는 문제를 피하십시오 (다른 답변과 마찬가지로)

참고 : 이것은 mysql 전용 솔루션입니다. 내가 아는 다른 모든 데이터베이스는 "집계 열에 그룹되지 않은 열이 나열되지 않습니다"라는 메시지와 함께 SQL 구문 오류가 발생합니다. 이 솔루션을 사용하기 때문에 문서화되지 않은 동작을이 것을, 더 많은주의가 주장 테스트를 포함 할 수 있습니다 남아 의 MySQL의 미래 버전이 동작을 변경해야합니다 작업.

버전 5.7 업데이트 :

버전 5.7에서, 이후 sql-mode설정이 포함 ONLY_FULL_GROUP_BY기본적으로, 그래서이 작업을 수행해야 만들 수 없습니다 (이 설정을 제거하는 편집 서버의 옵션 파일)이 옵션을 사용할 수 있습니다.


66
"mysql은 첫 번째 행만 반환합니다." -아마도 이것이 작동하는 방식이지만 보장되지는 않습니다. 문서는 말한다 : "서버들이 동일하지 않는, 그래서 선택한 값이 불확정 있으며, 각 그룹의 모든 값을 자유롭게 선택할 수 있습니다." . 서버는 SELECT절에 표시되고 집계 함수를 사용하여 계산되지 않는 각 열 또는 표현식에 대해 행을 선택하지 않고 값 (동일한 행에서 나오는 것은 아님)을 선택 합니다.
axiac

16
이 동작은 MySQL 5.7.5 에서 변경 되었으며 기본적으로 SELECT절의 열 이 기능적으로 GROUP BY열에 의존하지 않기 때문에이 쿼리를 거부합니다 . 이를 수락하도록 구성된 경우 (`ONLY_FULL_GROUP_BY`는 비활성화 됨) 이전 버전과 동일하게 작동합니다 (즉, 해당 열의 값이 결정되지 않음).
axiac

17
이 답변에 많은 찬사를 보냈다는 것이 놀랍습니다. 잘못되었고 나쁘다. 이 쿼리는 작동하지 않을 수 있습니다. 하위 쿼리의 데이터는 order by 절에도 불구하고 정렬되지 않은 집합입니다. MySQL 실제로 레코드를 지금 주문하고 그 순서를 유지할 수 있지만, 향후 버전에서 중단되면 아무런 규칙도 위반하지 않습니다. 그런 다음 GROUP BY하나의 레코드로 요약되지만 모든 필드는 임의로 레코드에서 선택됩니다. 그것은 MySQL이 현재 단순히 항상 첫 번째 행을 선택합니다 것을,하지만 그것은 단지뿐만 아니라에서 다른 행 또는 값을 선택할 수있는 다른 미래 버전의 행을.
Thorsten Kettner

9
우리는 여기에 동의하지 않습니다. 나는 현재 작동하는 문서화되지 않은 기능을 사용하지 않으며 이것을 희망적으로 다루는 일부 테스트에 의존합니다. 현재 구현이 문서에서 명확하지 않은 값을 얻을 수 있다고 명시하는 완전한 첫 번째 레코드를 얻지 만 여전히 사용한다는 것은 운이 좋다는 것을 알고 있습니다. 간단한 세션 또는 데이터베이스 설정으로 인해 언제든지 변경 될 수 있습니다. 나는 이것이 너무 위험하다고 생각합니다.
Thorsten Kettner

3
이 답변은 잘못된 것 같습니다. 당 문서 , 서버는 또한, 각 그룹의 값의 선택은 ORDER BY 절을 추가하여 영향을받을 수없는 각 그룹 ...에서 모든 값을 자유롭게 선택할 수있다. 결과 집합 정렬은 값을 선택한 후에 발생하며 ORDER BY는 서버가 선택한 각 그룹 내의 값에 영향을 미치지 않습니다.
Tgr

296

올바른 해결책은 다음과 같습니다.

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

작동 방식 :

열의 값이 같고 열의 값이 더 큰 o모든 행과 각 행을 일치 시킵니다. 열에 그룹의 최대 값이없는 행은의 행 하나 이상과 일치 합니다.bGroupAgeoAgeb

LEFT JOIN그것의 전체 행과 (자신의 그룹에 혼자있는 사람을 포함) 그룹에서 가장 오래된 사람과 일치한다 NULL에서의 b( '그룹에 더 큰 시대').
를 사용 INNER JOIN하면 이러한 행이 일치하지 않고 무시됩니다.

WHERENULL은에서 추출 된 필드에 s가있는 행만 유지합니다 b. 그들은 각 그룹에서 가장 오래된 사람들입니다.

추가 자료

이 솔루션과 다른 많은 것들이 책에 설명되어 있습니다. SQL Antipatterns : 데이터베이스 프로그래밍의 함정 피하기


43
o.Age = b.Age예를 들어 그룹 2의 Paul이 Laura와 같은 39에있는 경우 동일한 그룹에 대해 둘 이상의 행을 반환 할 수 있습니다 . 그러나 우리가 그러한 행동을 원하지 않는다면 우리는 다음과 같이 할 수 있습니다.ON o.Group = b.Group AND (o.Age < b.Age or (o.Age = b.Age and o.id < b.id))
Todor

8
놀랄 만한! 20M 레코드의 경우 "순진"알고리즘보다 50 배 빠릅니다 (max ()로 하위 쿼리에 참여)
user2706534

3
@Todor 주석과 완벽하게 작동합니다. 추가 쿼리 조건이 있으면 FROM 및 LEFT JOIN에 추가해야한다고 덧붙입니다. 뭔가 LIKE : FROM (사람 SELECT * FROM WHERE 나이 = 32!) 좌 가입 O (SELECT * 사람 어디에서 나이 = 32!) B를 - 당신이 32 사람들을 해고 할 경우
알랭 Zelink

1
@AlainZelink는 원래 @ axiac 답변에 필요하지 않은 하위 쿼리를 도입하지 않기 위해 이러한 "추가 쿼리 조건"이 최종 WHERE 조건 목록에 더 적합하지 않습니까?
tarilabs

5
이 솔루션은 효과가있었습니다. 그러나 동일한 ID를 공유하는 10,000 개 이상의 행으로 시도 할 때 느린 쿼리 로그에보고되기 시작했습니다. 인덱싱 된 열에 참여했습니다. 드문 경우이지만 언급 할 가치가 있다고 생각했습니다.
chaseisabelle

50

MAX(Group)및 을 가져 오는 하위 쿼리에 대해 조인 할 수 있습니다 Age. 이 방법은 대부분의 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;

마이클, 이것에 감사합니다-보헤미안의 의견에 따라 여러 행을 묶어 반환하는 문제에 대한 답변이 있습니까?
Yarin

1
@Yarin 예를 들어 where Group = 2, Age = 20와 같이 2 개의 행이있는 경우 하위 쿼리는 그 중 하나를 반환하지만 join ON절은 행 과 모두 일치 하므로 다른 열에 대해 다른 val을 사용하지만 동일한 그룹 / 연령으로 2 개의 행을 다시 가져옵니다. 하나가 아니라
Michael Berkowski

Bohemians MySQL 전용 경로로 이동하지 않으면 그룹당 결과를 제한하는 것이 불가능하다는 말입니까?
Yarin

@Yarin은 불가능하지 않습니다. 추가 열이있는 경우 더 많은 작업이 필요합니다. 그룹 / 연령 쌍과 같은 각 관련 최대 ID를 가져 오는 다른 중첩 된 하위 쿼리 일 수도 있고 ID를 기반으로 나머지 행을 가져 오기 위해 조인 할 수도 있습니다.
Michael Berkowski

이것은 허용되는 답변이어야합니다 (현재 승인 된 답변은 대부분의 다른 RDBMS에서는 실패하며 실제로는 여러 버전의 MySQL에서는 실패합니다).
Tim Biegeleisen

28

SQLite (그리고 아마도 MySQL)에 대한 나의 간단한 해결책 :

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

그러나 PostgreSQL 및 다른 플랫폼에서는 작동하지 않습니다.

PostgreSQL에서는 DISTINCT ON 절을 사용할 수 있습니다 .

SELECT DISTINCT ON ("group") * FROM "mytable" ORDER BY "group", "age" DESC;

@Bohemian 죄송합니다. 알 수 없습니다. 집계되지 않은 열이 포함되어 있으므로 MySQL 전용입니다.
Cec

2
@IgorKulagin-Postgres에서 작동하지 않습니다. 오류 메시지 : "mytable.id"열이 GROUP BY 절에 나타나거나 집계 함수에 사용되어야합니다
Yarin

13
MySQL 쿼리는 많은 경우 우연히 작동 할 수 있습니다. "SELECT *"는 속하는 MAX (나이)에 해당하지 않는 정보를 반환 할 수 있습니다. 이 답변은 잘못되었습니다. SQLite의 경우도 마찬가지입니다.
Albert Hendriks

2
그러나 이것은 그룹화 된 열과 최대 열을 선택 해야하는 경우에 적합합니다. 이는 결과 ( 'Bob', 1, 42) 인 위의 요구 사항에 맞지 않지만 예상 결과는 ( 'Shawn', 1, 42)입니다.
Ram Babu S

1
postgres에 적합
Karol Gasienica

4

순위 방법을 사용합니다.

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

sel-설명이 필요합니다- :=전에 본 적이 없습니다 -그게 뭐죠?
Yarin


나는 이것에 대해 파헤쳐 야 할 것이다. 나는 그 대답이 우리의 시나리오를 지나치게 복잡하게 생각한다고 생각하지만, 새로운 것을 가르쳐 줘서 고마워 ..
Yarin

3

MySQL에 row_number 함수가 있는지 확실하지 않습니다. 그렇다면 원하는 결과를 얻는 데 사용할 수 있습니다. SQL Server에서 다음과 유사한 작업을 수행 할 수 있습니다.

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
8.0 이후로 사용됩니다.
Ilja Everilä

2

axiac의 솔루션은 결국 저에게 가장 효과적이었습니다. 그러나 두 가지 열에서 파생 된 계산 된 "최대 값"이라는 추가 복잡성이있었습니다.

같은 예를 사용하겠습니다. 각 그룹에서 가장 나이 많은 사람을 원합니다. 똑같이 나이가 많은 사람들이 있다면 가장 큰 사람을 데려가십시오.

이 동작을 얻으려면 왼쪽 조인을 두 번 수행해야했습니다.

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;

도움이 되었기를 바랍니다! 그래도 더 좋은 방법이 있어야한다고 생각합니다 ...


2

내 솔루션은 하나의 열만 검색 해야하는 경우에만 작동하지만 성능 측면에서 가장 좋은 솔루션이었습니다 (단 하나의 쿼리 만 사용하십시오).

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

순서가 지정된 연결 목록을 작성하기 위해 GROUP_CONCAT를 사용하고 첫 번째 목록에만 하위 문자열을 지정합니다.


group_concat 내에서 동일한 키를 정렬하여 여러 열을 얻을 수 있지만 각 열에 대해 별도의 group_concat / index / substring을 작성해야 함을 확인할 수 있습니다.
Rasika

여기서 보너스는 group_concat 내에서 정렬에 여러 열을 추가 할 수 있으며 관계를 쉽게 해결하고 그룹 당 하나의 레코드 만 보장한다는 것입니다. 간단하고 효율적인 솔루션으로 완성되었습니다!
Rasika

2

사용하여 간단한 해결책이 있습니다. 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

1

CTE 사용-공통 테이블 표현식 :

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

Oracle에서 아래 쿼리는 원하는 결과를 줄 수 있습니다.

SELECT group,person,Age,
  ROWNUMBER() OVER (PARTITION BY group ORDER BY age desc ,person asc) as rankForEachGroup
  FROM tablename where rankForEachGroup=1

0
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`

0

시도해 볼 수도 있습니다

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

1
고마움, 이것은 넥타이가있을 때 나이에 대한 여러 레코드를 반환하지만
Yarin

또한 그룹 1에 39 세인 경우이 쿼리는 올바르지 않습니다.이 경우 그룹 1의 최대 연령이 더 높더라도 해당 사용자도 선택됩니다.
Joshua Richardson

0

예약어이므로 Group을 열 이름으로 사용하지 않습니다. 그러나 다음 SQL이 작동합니다.

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

감사합니다, 이것은 넥타이가있을 때 나이에 대한 여러 레코드를 반환하지만
Yarin

@Yarin 올바른 노인이 누구인지 어떻게 결정할까요? 여러 답변이 가장 올바른 답변 인 것 같습니다. 그렇지 않으면 제한과 질서를 사용하십시오
Duncan


0

테이블 이름을 사람으로 설정

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

mytable에서 ID (및 모든 coulmn)가 필요한 경우

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

이것이 mysql에서 그룹 당 N 최대 행을 얻는 방법입니다.

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
;

작동 방식 :

  • 테이블에 자기 조인
  • 그룹은 co.country = ci.country
  • 그룹당 N 개의 요소는 ) < 13 개의 요소에 대해 제어됩니다 .-) <3
  • 최대 또는 최소를 얻으려면 다음에 따라 다릅니다. co.id < ci.id
    • co.id <ci.id-최대
    • co.id> ci.id-분

전체 예는 다음과 같습니다.

MySQL은 그룹 당 최대 n 값을 선택합니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.