각 그룹화 된 결과 그룹에 대한 상위 n 개 레코드 가져 오기


140

다음은 가장 간단한 예이지만 모든 솔루션을 확장 할 수 있어야하지만 많은 n 개의 상위 결과가 필요합니다.

아래 표에 개인, 그룹 및 연령 열 이있는 경우 각 그룹에서 가장 나이가 많은 두 사람을 어떻게 구할 수 있습니까? (그룹 내 동점은 더 많은 결과를 산출하지 않지만 알파벳 순서로 처음 2 개를 제공해야 함)

+ -------- + ------- + ----- +
| 사람 | 그룹 | 나이 |
+ -------- + ------- + ----- +
| 밥 | 1 | 32 |
| 질 | 1 | 34 |
| 숀 | 1 | 42 |
| 제이크 | 2 | 29 |
| 폴 | 2 | 36 |
| 로라 | 2 | 39 |
+ -------- + ------- + ----- +

원하는 결과 집합 :

+ -------- + ------- + ----- +
| 숀 | 1 | 42 |
| 질 | 1 | 34 |
| 로라 | 2 | 39 |
| 폴 | 2 | 36 |
+ -------- + ------- + ----- +

참고 : 이 질문은 각 그룹화 된 SQL 결과 그룹에 대해 최대 값을 가진 이전의 하나의 레코드 가져 오기-각 그룹에서 단일 최상위 행을 가져오고 @Bohemian에서 MySQL 관련 답변을 받았습니다.

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

나는 방법을 모르겠지만 이것을 구축 할 수 있기를 바랍니다.



2
이 예를 확인하십시오. 그것은 당신이 묻는 것에 거의 가깝습니다 : stackoverflow.com/questions/1537606/…
Savas Vedova

GROUP BY 내에서 LIMIT를 사용하여 그룹당 N 개의 결과를 얻습니까? stackoverflow.com/questions/2129693/…
Edye Chan

답변:


88

다음을 사용하여이를 수행하는 한 가지 방법이 있습니다 UNION ALL( Demo with SQL Fiddle 참조 ). 그룹이 두 개 이상인 경우 두 그룹으로 작업 할 수 있습니다. 그룹 group수 를 지정하고 각 그룹에 대해 쿼리를 추가해야합니다 group.

(
  select *
  from mytable 
  where `group` = 1
  order by age desc
  LIMIT 2
)
UNION ALL
(
  select *
  from mytable 
  where `group` = 2
  order by age desc
  LIMIT 2
)

이를 수행하는 다양한 방법이 있습니다. 상황에 가장 적합한 경로를 결정하려면이 기사를 참조하십시오.

http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/

편집하다:

이것은 당신에게도 효과가있을 수 있으며 각 레코드마다 행 번호를 생성합니다. 위의 링크에서 예제를 사용하면 행 번호가 2 이하인 레코드 만 반환합니다.

select person, `group`, age
from 
(
   select person, `group`, age,
      (@num:=if(@group = `group`, @num +1, if(@group := `group`, 1, 1))) row_number 
  from test t
  CROSS JOIN (select @num:=0, @group:=null) c
  order by `Group`, Age desc, person
) as x 
where x.row_number <= 2;

데모 보기


52
만약 그가 1,000 명 이상의 그룹을 가지고 있다면, 이것이 조금 무섭지 않을까요?
Charles Forest

1
@CharlesForest 그렇습니다. 그렇기 때문에 두 그룹 이상을 지정해야한다고 말한 이유입니다. 추한 것입니다.
Taryn

1
@CharlesForest 더 나은 솔루션을 찾았다 고 생각합니다. 편집본을 참조하십시오
Taryn

1
이 글을 읽는 사람을위한 메모 : 버전은 변수가 정확하다는 것입니다. 그러나 MySQL은 표현식의 평가 순서를 보장하지 않으며 SELECT실제로는 순서가 잘못된 경우도 있습니다. 솔루션의 핵심은 모든 변수 할당을 단일 표현식에 넣는 것입니다. 여기에 예입니다 stackoverflow.com/questions/38535020/... .
Gordon Linoff

1
@GordonLinoff 지적 해 주셔서 감사합니다. 또한 업데이트하는 데 너무 오래 걸렸습니다.
Taryn

63

다른 데이터베이스에서는을 사용하여이 작업을 수행 할 수 있습니다 ROW_NUMBER. MySQL은 지원하지 않지만 ROW_NUMBER변수를 사용하여 에뮬레이션 할 수 있습니다.

SELECT
    person,
    groupname,
    age
FROM
(
    SELECT
        person,
        groupname,
        age,
        @rn := IF(@prev = groupname, @rn + 1, 1) AS rn,
        @prev := groupname
    FROM mytable
    JOIN (SELECT @prev := NULL, @rn := 0) AS vars
    ORDER BY groupname, age DESC, person
) AS T1
WHERE rn <= 2

온라인 작업 참조 : sqlfiddle


편집 나는 단지 bluefeet가 그에게 +1이라는 매우 유사한 대답을 게시 한 것을 알았습니다. 그러나이 답변에는 두 가지 작은 장점이 있습니다.

  1. 단일 쿼리입니다. 변수는 SELECT 문 내에서 초기화됩니다.
  2. 질문에 설명 된대로 이름을 알파벳 순서로 지정합니다.

누군가를 도울 수 있도록 여기에 남겨 두겠습니다.


1
Mark- 이것은 우리를 위해 잘 작동합니다. @bluefeet의 칭찬에 대한 또 다른 좋은 대안을 제공해 주셔서 감사합니다.
Yarin

+1. 이것은 나를 위해 일했습니다. 정말 깨끗하고 정답입니다. 이것이 정확히 어떻게 작동하는지 설명해 주시겠습니까? 이것의 논리는 무엇입니까?
Aditya Hajare

3
니스 솔루션하지만 ORDER BY 절은이 문제를 해결하는 것이 최고 결과를 반환하지 않도록 선택 내 대체 솔루션을 참조 후 적용되기 때문에 내 환경 (MySQL은 5.6)에서 작동하지 않는 것 같습니다
로랑 PELE

이것을 실행하는 동안 나는 삭제할 수있었습니다 JOIN (SELECT @prev := NULL, @rn := 0) AS vars. 빈 변수를 선언하는 아이디어가 있지만 MySql에는 적합하지 않은 것 같습니다.
조셉 조

1
이것은 MySQL 5.7에서 저에게 효과적이지만 누군가 어떻게 작동하는지 설명 할 수 있다면 정말 좋을 것입니다.
George B

41

이 시도:

SELECT a.person, a.group, a.age FROM person AS a WHERE 
(SELECT COUNT(*) FROM person AS b 
WHERE b.group = a.group AND b.age >= a.age) <= 2 
ORDER BY a.group ASC, a.age DESC

데모


6
가장 간단한 해결책으로 어디서나 스 너핑이 나옵니다! 루도 / 빌 카윈 보다 더 우아합니까? 합니까? 몇 가지 논평을 할 수 있습니까
Yarin

흠, 그것이 더 우아한 지 확실하지 않습니다. 그러나 투표로 볼 때, bluefeet가 더 나은 해결책을 가지고 있다고 생각합니다.
snuffn

2
이것에 문제가 있습니다. 그룹 내에서 2 위를 차지할 경우 하나의 상위 결과 만 반환됩니다. 수정 된 데모
Yarin

2
원한다면 문제가되지 않습니다. 의 순서를 설정할 수 있습니다 a.person.
Alberto Leal

아니요, 제 경우에는 작동하지 않으며 DEMO도 작동하지 않습니다.
Choix

31

자체 조인 사용은 어떻습니까?

CREATE TABLE mytable (person, groupname, age);
INSERT INTO mytable VALUES('Bob',1,32);
INSERT INTO mytable VALUES('Jill',1,34);
INSERT INTO mytable VALUES('Shawn',1,42);
INSERT INTO mytable VALUES('Jake',2,29);
INSERT INTO mytable VALUES('Paul',2,36);
INSERT INTO mytable VALUES('Laura',2,39);

SELECT a.* FROM mytable AS a
  LEFT JOIN mytable AS a2 
    ON a.groupname = a2.groupname AND a.age <= a2.age
GROUP BY a.person
HAVING COUNT(*) <= 2
ORDER BY a.groupname, a.age DESC;

나에게 준다 :

a.person    a.groupname  a.age     
----------  -----------  ----------
Shawn       1            42        
Jill        1            34        
Laura       2            39        
Paul        2            36      

Bill Karwin의 답변에서 영감을 얻었습니다. 을 통해 각 카테고리별로 상위 10 개 레코드 선택했습니다.

또한 SQLite를 사용하고 있지만 MySQL에서 작동합니다.

다른 것 : 위의 group열을 열로 바꿨습니다.groupname 편의를 위해 열입니다.

편집하다 :

누락 된 타이 결과에 관한 OP의 의견에 이어, 나는 모든 관계를 보여주기 위해 snuffin의 답변을 증가시켰다. 즉, 마지막 행이 연결되어 있으면 아래와 같이 2 개 이상의 행이 리턴 될 수 있습니다.

.headers on
.mode column

CREATE TABLE foo (person, groupname, age);
INSERT INTO foo VALUES('Paul',2,36);
INSERT INTO foo VALUES('Laura',2,39);
INSERT INTO foo VALUES('Joe',2,36);
INSERT INTO foo VALUES('Bob',1,32);
INSERT INTO foo VALUES('Jill',1,34);
INSERT INTO foo VALUES('Shawn',1,42);
INSERT INTO foo VALUES('Jake',2,29);
INSERT INTO foo VALUES('James',2,15);
INSERT INTO foo VALUES('Fred',1,12);
INSERT INTO foo VALUES('Chuck',3,112);


SELECT a.person, a.groupname, a.age 
FROM foo AS a 
WHERE a.age >= (SELECT MIN(b.age)
                FROM foo AS b 
                WHERE (SELECT COUNT(*)
                       FROM foo AS c
                       WHERE c.groupname = b.groupname AND c.age >= b.age) <= 2
                GROUP BY b.groupname)
ORDER BY a.groupname ASC, a.age DESC;

나에게 준다 :

person      groupname   age       
----------  ----------  ----------
Shawn       1           42        
Jill        1           34        
Laura       2           39        
Paul        2           36        
Joe         2           36        
Chuck       3           112      

@ Ludo- Bill Karwin의 답변을 보았습니다. 여기에 적용 해 주셔서 감사합니다
Yarin

Snuffin의 답변에 대해 어떻게 생각하십니까? 나는 두 가지를 비교하려고합니다
Yarin

2
이것에 문제가 있습니다. 그룹 내에서 2 위를 차지할 경우 상위 결과는 하나만 반환됩니다. 데모
Yarin

1
@ Ludo- 원래의 요구 사항은 각 그룹이 어떤 관계로, 정확한 n 개의 결과를 반환이었다 순으로 해결되는
Yarin

관계를 포함하는 편집이 작동하지 않습니다. 내가 얻을 ERROR 1242 (21000): Subquery returns more than 1 row아마도 때문에의, GROUP BY. SELECT MIN하위 쿼리 만 실행하면 세 개의 행이 생성 34, 39, 112됩니다. 두 번째 값은 39가 아니라 36이어야합니다.
verbamour

12

Snuffin 솔루션은 행이 많을 때 실행 속도가 느린 것처럼 보이며 Mark Byers / Rick James 및 Bluefeet 솔루션은 select by 실행 후 order by가 적용되기 때문에 내 환경 (MySQL 5.6)에서 작동하지 않으므로 여기에 변형이 있습니다. 이 문제를 해결하기위한 Marc Byers / Rick James 솔루션 (별도의 함축 된 선택) :

select person, groupname, age
from
(
    select person, groupname, age,
    (@rn:=if(@prev = groupname, @rn +1, 1)) as rownumb,
    @prev:= groupname 
    from 
    (
        select person, groupname, age
        from persons 
        order by groupname ,  age desc, person
    )   as sortedlist
    JOIN (select @prev:=NULL, @rn :=0) as vars
) as groupedlist 
where rownumb<=2
order by groupname ,  age desc, person;

5 백만 행이있는 테이블에서 비슷한 쿼리를 시도했지만 3 초 미만의 결과를 반환합니다.


3
이것은 내 환경에서 작동하는 유일한 쿼리입니다. 감사!
herrherr

3
LIMIT 9999999을 사용하여 파생 테이블에 추가하십시오 ORDER BY. 이로 인해 가 무시되는 것을 막을 수 있습니다ORDER BY .
Rick James

나는 수천 개의 행을 포함하는 테이블에서 비슷한 쿼리를 실행했으며 하나의 결과를 반환하는 데 60 초가 걸렸습니다. 포스트 덕분에 시작했습니다. (ETA : 5 초 이하. 양호!)
Evan

10

이것 좀 봐:

SELECT
  p.Person,
  p.`Group`,
  p.Age
FROM
  people p
  INNER JOIN
  (
    SELECT MAX(Age) AS Age, `Group` FROM people GROUP BY `Group`
    UNION
    SELECT MAX(p3.Age) AS Age, p3.`Group` FROM people p3 INNER JOIN (SELECT MAX(Age) AS Age, `Group` FROM people GROUP BY `Group`) p4 ON p3.Age < p4.Age AND p3.`Group` = p4.`Group` GROUP BY `Group`
  ) p2 ON p.Age = p2.Age AND p.`Group` = p2.`Group`
ORDER BY
  `Group`,
  Age DESC,
  Person;

SQL 피들 : http://sqlfiddle.com/#!2/cdbb6/15


5
남자, 다른 사람들은 훨씬 간단한 솔루션을 발견했습니다 ... 나는 이것에 대해 15 분 정도를 보냈고 그러한 복잡한 솔루션을 생각해 낸 것에 대해 매우 자랑스럽게 생각했습니다. 짜증나
Travesty3

- 내가 현재 1보다 낮았다 내부 버전 번호를 확인했다이 나에게이 할 수있는 대답했다 max(internal_version - 1): 스트레스 때문에 덜 -
제이미 스트라우스을

8

다른 답변이 충분히 빠르지 않은 경우이 코드 를 사용해보십시오.

SELECT
        province, n, city, population
    FROM
      ( SELECT  @prev := '', @n := 0 ) init
    JOIN
      ( SELECT  @n := if(province != @prev, 1, @n + 1) AS n,
                @prev := province,
                province, city, population
            FROM  Canada
            ORDER BY
                province   ASC,
                population DESC
      ) x
    WHERE  n <= 3
    ORDER BY  province, n;

산출:

+---------------------------+------+------------------+------------+
| province                  | n    | city             | population |
+---------------------------+------+------------------+------------+
| Alberta                   |    1 | Calgary          |     968475 |
| Alberta                   |    2 | Edmonton         |     822319 |
| Alberta                   |    3 | Red Deer         |      73595 |
| British Columbia          |    1 | Vancouver        |    1837970 |
| British Columbia          |    2 | Victoria         |     289625 |
| British Columbia          |    3 | Abbotsford       |     151685 |
| Manitoba                  |    1 | ...

귀하의 사이트를 살펴 보았습니다. 도시 인구에 대한 데이터 소스는 어디에서 얻을 수 있습니까? TIA와 rgs.
Vérace

maxmind.com/en/worldcities- 위도 / 경도 검색 , 쿼리, 파티셔닝 등 을 실험하는 데 편리하다는 것을 알았습니다 . 흥미를 가질 정도로 크지 만 답을 인식 할 수있을 정도로 읽기 쉽습니다 . 캐나다 하위 집합은 이런 종류의 질문에 편리합니다. (미국 도시보다 지방이 적습니다.)
Rick James

2

내가 작업중 인 자바 프로그램에서 이것을 구현하는 쉬운 방법을 찾기 위해 오랜 시간을 보냈기 때문에 이것을 공유하고 싶었다. 이것은 당신이 찾고있는 출력을 제공하지는 않지만 가깝습니다. mysql의 함수는 GROUP_CONCAT()각 그룹에서 반환 할 결과 수를 지정하는 데 실제로 효과적이었습니다. LIMITCOUNT작업을 시도하는 다른 멋진 방법을 사용 하거나 사용 하지 못했습니다. 따라서 수정 된 출력을 기꺼이 받아들이려면 훌륭한 솔루션입니다. 학생 ID, 성별 및 gpa가 포함 된 '학생'이라는 표가 있다고 가정하겠습니다. 각 성별에 대해 최고 5 gpa를 원한다고 가정하겠습니다. 그런 다음과 같은 쿼리를 작성할 수 있습니다

SELECT sex, SUBSTRING_INDEX(GROUP_CONCAT(cast(gpa AS char ) ORDER BY gpa desc), ',',5) 
AS subcategories FROM student GROUP BY sex;

매개 변수 '5'는 각 행에 연결할 항목 수를 나타냅니다.

그리고 출력은 다음과 같습니다

+--------+----------------+
| Male   | 4,4,4,4,3.9    |
| Female | 4,4,3.9,3.9,3.8|
+--------+----------------+

ORDER BY변수를 변경하고 다른 방식으로 정렬 할 수도 있습니다 . 따라서 학생의 나이가 있다면 'gpa desc'를 'age desc'로 바꿀 수 있으며 작동합니다! 또한 출력에서 ​​더 많은 열을 얻기 위해 그룹 별 명령문에 변수를 추가 할 수 있습니다. 그래서 이것은 내가 찾은 방법으로 꽤 유연하고 결과 만 나열해도 괜찮습니다.


0

SQL Server row_numer()에서 다음과 같이 쉽게 결과를 얻을 수있는 강력한 기능입니다

select Person,[group],age
from
(
select * ,row_number() over(partition by [group] order by age desc) rn
from mytable
) t
where rn <= 2

8.0과 10.2가 GA이므로이 답변은 합리적입니다.
Rick James

@RickJames 'GA가된다'는 것은 무엇을 의미합니까? 창 함수 ( dev.mysql.com/doc/refman/8.0/en/window-functions.html )는 내 문제를 매우 잘 해결했습니다.
iedmrc

1
@iedmrc- "GA"는 "일반적으로 사용 가능"을 의미합니다. "프라임 타임 준비"또는 "릴리스"용으로 기술적으로 사용됩니다. 그들은 버전을 개발하고 있으며 놓친 버그에 초점을 맞출 것입니다. 이 링크에서는 MariaDB 10.2의 구현과 다를 수있는 MySQL 8.0의 구현에 대해 설명합니다.
Rick James

-1

이 문제에 대한 정말 좋은 답변이 있습니다. MySQL -각 그룹 당 상위 N 행을 얻는 방법

참조 링크의 솔루션을 기반으로 한 쿼리는 다음과 같습니다.

SELECT Person, Group, Age
   FROM
     (SELECT Person, Group, Age, 
                  @group_rank := IF(@group = Group, @group_rank + 1, 1) AS group_rank,
                  @current_group := Group 
       FROM `your_table`
       ORDER BY Group, Age DESC
     ) ranked
   WHERE group_rank <= `n`
   ORDER BY Group, Age DESC;

어디에 n있는 top nyour_table 테이블의 이름입니다.

참고 문헌의 설명이 정말 명확하다고 생각합니다. 빠른 참조를 위해 여기에 복사하여 붙여 넣습니다.

현재 MySQL은 그룹 내에서 시퀀스 번호를 할당 할 수있는 ROW_NUMBER () 함수를 지원하지 않지만 해결 방법으로 MySQL 세션 변수를 사용할 수 있습니다.

이러한 변수는 선언이 필요하지 않으며 계산에서 중간 결과를 저장하기 위해 쿼리에서 사용할 수 있습니다.

@current_country : = country이 코드는 각 행에 대해 실행되며 국가 열의 값을 @current_country 변수에 저장합니다.

@country_rank : = IF (@current_country = country, @country_rank + 1, 1)이 코드에서 @current_country가 동일하면 순위가 증가하고 그렇지 않으면 1로 설정됩니다. 첫 번째 행 @current_country가 NULL이므로 rank는 1로 설정되었습니다.

정확한 순위를 위해서는 ORDER BY 국가, 인구 DESC가 있어야합니다


Marc Byers, Rick James 및 광산 솔루션에서 사용되는 원칙입니다.
Laurent PELE

어려운 (스택 오버플로 또는 SQLlines)가 처음이었다 포스트 대답
로랑 PELE

@LaurentPELE-Mine은 2015 년 2 월에 게시되었습니다. SQLline에 타임 스탬프 나 이름이 표시되지 않습니다. MySQL 블로그는 오래 전부터 사용되어 왔으며 일부는 오래되어 제거해야합니다. 사람들은 잘못된 정보를 인용하고 있습니다.
Rick James
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.