그룹당 가장 높은 / 가장 작은 <무엇이든> 레코드 가져 오기


88

그렇게하는 방법?

이 질문의 이전 제목은 " using rank (@Rank : = @Rank + 1) in complex query with subqueries-will it work? "였습니다. 왜냐하면 순위를 사용하여 솔루션을 찾고 있었기 때문입니다. 훨씬 낫습니다.

원래 질문 :

정의 된 순서가 주어지면 각 그룹에서 마지막 레코드를 가져 오는 쿼리를 작성하려고합니다.

SET @Rank=0;

select s.*
from (select GroupId, max(Rank) AS MaxRank
      from (select GroupId, @Rank := @Rank + 1 AS Rank 
            from Table
            order by OrderField
            ) as t
      group by GroupId) as t 
  join (
      select *, @Rank := @Rank + 1 AS Rank
      from Table
      order by OrderField
      ) as s 
  on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField

식은 @Rank := @Rank + 1일반적으로 순위에 사용되지만 나에게는 두 개의 하위 쿼리에서 사용하면 의심 스럽지만 한 번만 초기화됩니다. 이런 식으로 작동합니까?

둘째, 여러 번 평가되는 하나의 하위 쿼리와 함께 작동합니까? where (or having) 절의 하위 쿼리와 유사합니다 (위를 작성하는 또 다른 방법).

SET @Rank=0;

select Table.*, @Rank := @Rank + 1 AS Rank
from Table
having Rank = (select max(Rank) AS MaxRank
              from (select GroupId, @Rank := @Rank + 1 AS Rank 
                    from Table as t0
                    order by OrderField
                    ) as t
              where t.GroupId = table.GroupId
             )
order by OrderField

미리 감사드립니다!


2
여기에 더 고급 질문 stackoverflow.com/questions/9841093/…
TMS

답변:


174

따라서 OrderField그룹당 가장 높은 행을 얻고 싶 습니까? 나는 이렇게 할 것입니다.

SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
  ON t1.GroupId = t2.GroupId AND t1.OrderField < t2.OrderField
WHERE t2.GroupId IS NULL
ORDER BY t1.OrderField; // not needed! (note by Tomas)

( Tomas 편집 : 동일한 그룹 내에 동일한 OrderField를 가진 레코드가 더 있고 그중 정확히 하나가 필요한 경우 조건을 확장 할 수 있습니다.

SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
  ON t1.GroupId = t2.GroupId 
        AND (t1.OrderField < t2.OrderField 
         OR (t1.OrderField = t2.OrderField AND t1.Id < t2.Id))
WHERE t2.GroupId IS NULL

편집 끝.)

즉, 동일 하고 더 큰 t1다른 행 t2이 존재 하지 않는 행 을 반환합니다 . 때 NULL, 그것은 왼쪽 외부 그러한 일치를 발견 조인 의미, 따라서 의 가장 큰 값이 그룹에 있습니다.GroupIdOrderFieldt2.*t1OrderField

순위도, 하위 쿼리도 없습니다. 이것은 빠르게 실행되어야하며에 복합 인덱스가있는 경우 "Using index"를 사용하여 t2에 대한 액세스를 최적화해야합니다 (GroupId, OrderField).


성능과 관련 하여 각 그룹의 마지막 레코드 검색에 대한 내 답변을 참조하십시오 . Stack Overflow 데이터 덤프를 사용하여 하위 쿼리 방법과 조인 방법을 시도했습니다. 차이점은 놀랍습니다. 조인 방법은 테스트에서 278 배 더 빠르게 실행되었습니다.

최상의 결과를 얻으려면 올바른 인덱스가 있어야합니다!

@Rank 변수를 사용하는 방법은 쿼리가 첫 번째 테이블을 처리 한 후 @Rank 값이 0으로 재설정되지 않기 때문에 작성한대로 작동하지 않습니다. 예를 하나 보여 드리겠습니다.

그룹당 가장 큰 행을 제외하고는 null 인 추가 필드와 함께 더미 데이터를 삽입했습니다.

select * from `Table`;

+---------+------------+------+
| GroupId | OrderField | foo  |
+---------+------------+------+
|      10 |         10 | NULL |
|      10 |         20 | NULL |
|      10 |         30 | foo  |
|      20 |         40 | NULL |
|      20 |         50 | NULL |
|      20 |         60 | foo  |
+---------+------------+------+

순위가 첫 번째 그룹의 경우 3, 두 번째 그룹의 경우 6으로 증가하고 내부 쿼리가 다음을 올바르게 반환 함을 보여줄 수 있습니다.

select GroupId, max(Rank) AS MaxRank
from (
  select GroupId, @Rank := @Rank + 1 AS Rank
  from `Table`
  order by OrderField) as t
group by GroupId

+---------+---------+
| GroupId | MaxRank |
+---------+---------+
|      10 |       3 |
|      20 |       6 |
+---------+---------+

이제 조인 조건없이 쿼리를 실행하여 모든 행의 카티 전 곱을 적용하고 모든 열도 가져옵니다.

select s.*, t.*
from (select GroupId, max(Rank) AS MaxRank
      from (select GroupId, @Rank := @Rank + 1 AS Rank 
            from `Table`
            order by OrderField
            ) as t
      group by GroupId) as t 
  join (
      select *, @Rank := @Rank + 1 AS Rank
      from `Table`
      order by OrderField
      ) as s 
  -- on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;

+---------+---------+---------+------------+------+------+
| GroupId | MaxRank | GroupId | OrderField | foo  | Rank |
+---------+---------+---------+------------+------+------+
|      10 |       3 |      10 |         10 | NULL |    7 |
|      20 |       6 |      10 |         10 | NULL |    7 |
|      10 |       3 |      10 |         20 | NULL |    8 |
|      20 |       6 |      10 |         20 | NULL |    8 |
|      20 |       6 |      10 |         30 | foo  |    9 |
|      10 |       3 |      10 |         30 | foo  |    9 |
|      10 |       3 |      20 |         40 | NULL |   10 |
|      20 |       6 |      20 |         40 | NULL |   10 |
|      10 |       3 |      20 |         50 | NULL |   11 |
|      20 |       6 |      20 |         50 | NULL |   11 |
|      20 |       6 |      20 |         60 | foo  |   12 |
|      10 |       3 |      20 |         60 | foo  |   12 |
+---------+---------+---------+------------+------+------+

위에서 볼 수 있듯이 그룹당 최대 순위는 정확하지만 두 번째 파생 테이블을 처리함에 따라 @Rank는 7 이상으로 계속 증가합니다. 따라서 두 번째 파생 테이블의 순위는 첫 번째 파생 테이블의 순위와 전혀 겹치지 않습니다.

두 테이블을 처리하는 사이에 @Rank를 0으로 재설정하도록 다른 파생 테이블을 추가해야합니다 (최적화 프로그램이 테이블을 평가하는 순서를 변경하지 않기를 바랍니다. 그렇지 않으면 STRAIGHT_JOIN을 사용하여이를 방지합니다).

select s.*
from (select GroupId, max(Rank) AS MaxRank
      from (select GroupId, @Rank := @Rank + 1 AS Rank 
            from `Table`
            order by OrderField
            ) as t
      group by GroupId) as t 
  join (select @Rank := 0) r -- RESET @Rank TO ZERO HERE
  join (
      select *, @Rank := @Rank + 1 AS Rank
      from `Table`
      order by OrderField
      ) as s 
  on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;

+---------+------------+------+------+
| GroupId | OrderField | foo  | Rank |
+---------+------------+------+------+
|      10 |         30 | foo  |    3 |
|      20 |         60 | foo  |    6 |
+---------+------------+------+------+

그러나이 쿼리의 최적화는 끔찍합니다. 인덱스를 사용할 수없고 두 개의 임시 테이블을 만들고 어려운 방식으로 정렬하며 임시 테이블을 조인 할 때 인덱스를 사용할 수 없기 때문에 조인 버퍼도 사용합니다. 다음은 다음의 출력 예입니다 EXPLAIN.

+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
| id | select_type | table      | type   | possible_keys | key  | key_len | ref  | rows | Extra                           |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
|  1 | PRIMARY     | <derived4> | system | NULL          | NULL | NULL    | NULL |    1 | Using temporary; Using filesort |
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL | NULL    | NULL |    2 |                                 |
|  1 | PRIMARY     | <derived5> | ALL    | NULL          | NULL | NULL    | NULL |    6 | Using where; Using join buffer  |
|  5 | DERIVED     | Table      | ALL    | NULL          | NULL | NULL    | NULL |    6 | Using filesort                  |
|  4 | DERIVED     | NULL       | NULL   | NULL          | NULL | NULL    | NULL | NULL | No tables used                  |
|  2 | DERIVED     | <derived3> | ALL    | NULL          | NULL | NULL    | NULL |    6 | Using temporary; Using filesort |
|  3 | DERIVED     | Table      | ALL    | NULL          | NULL | NULL    | NULL |    6 | Using filesort                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

왼쪽 외부 조인을 사용하는 내 솔루션은 훨씬 더 최적화됩니다. 임시 테이블과 보고서 "Using index"를 사용하지 않으므로 데이터를 건드리지 않고 인덱스 만 사용하여 조인을 해결할 수 있습니다.

+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key     | key_len | ref             | rows | Extra                    |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
|  1 | SIMPLE      | t1    | ALL  | NULL          | NULL    | NULL    | NULL            |    6 | Using filesort           |
|  1 | SIMPLE      | t2    | ref  | GroupId       | GroupId | 5       | test.t1.GroupId |    1 | Using where; Using index |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+

당신은 아마도 그들의 블로그에서 "조인하면 SQL이 느리게 만든다"고 주장하는 사람들을 읽을 것입니다. 그러나 그것은 말도 안됩니다. 최적화가 제대로 이루어지지 않으면 SQL이 느려집니다.


이것은 (OP에도) 매우 유용 할 수 있지만 슬프게도 두 가지 질문에 모두 대답하지 않습니다.
Andriy M

Bill에게 감사합니다. 순위를 피하는 방법은 좋은 생각이지만 ... 조인이 느리지 않을까요? 조인 (where 절 제한 없음)은 내 쿼리보다 크기가 훨씬 큽니다. 어쨌든 아이디어 주셔서 감사합니다! 그러나 나는 또한 원래 질문, 즉 순위가 이런 식으로 작동하는지에 대해서도 흥미로울 것입니다.
TMS

훌륭한 답변에 감사드립니다, 빌. 그러나 각 하위 쿼리에 대해 @Rank1및을 사용하면 어떻게 @Rank2됩니까? 문제가 해결 될까요? 이것이 귀하의 솔루션보다 빠를까요?
TMS

@Rank1및 사용하면 @Rank2차이가 없습니다.
Bill Karwin

2
훌륭한 솔루션에 감사드립니다. 나는 그 문제로 오랫동안 고심하고 있었다. 다른 필드 (예 : "foo")에 대한 필터를 추가하려는 사람들 ... AND t1.foo = t2.foo의 경우 나중에 올바른 결과를 얻기 위해 조인 조건 에 추가해야합니다.WHERE ... AND foo='bar'
ownking
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.