각 그룹화 된 결과 그룹에 대한 상위 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()각 그룹에서 반환 할 결과 수를 지정하는 데 실제로 효과적이었습니다. LIMIT이 COUNT작업을 시도하는 다른 멋진 방법을 사용 하거나 사용 하지 못했습니다. 따라서 수정 된 출력을 기꺼이 받아들이려면 훌륭한 솔루션입니다. 학생 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 n및your_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.