MySQL을 통해 그룹당 최대 레코드 가져오기

15212 단어 MySQL

제목.


아래 테이블에서 모든 고객(customer id)의 구매금액(amount)의 최대 날짜와 시간(datetime)을 알고 싶습니다.
purchase_id
customer_id
amount
datetime
1
101
100
2000-01-01T00:00:00Z
2
102
180
2000-01-02T00:00:00Z
3
103
200
2000-01-03T00:00:00Z
4
101
70
2000-01-04T00:00:00Z
5
103
280
2000-01-05T00:00:00Z
6
102
310
2000-01-06T00:00:00Z
7
101
10
2000-01-07T00:00:00Z
8
103
280
2000-01-08T00:00:00Z

샘플 데이터


SQL Fiddle 브라우저로 테스트
CREATE TABLE IF NOT EXISTS `purchase_header` (
  `purchase_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `customer_id` int(10) unsigned NOT NULL,
  `amount` int(10) unsigned NOT NULL,
  `datetime` datetime NOT NULL,
  PRIMARY KEY (`purchase_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
;

INSERT INTO `purchase_header` (`customer_id`, `amount`, `datetime`) VALUES (101, 100, '2000-01-01 00:00:00');
INSERT INTO `purchase_header` (`customer_id`, `amount`, `datetime`) VALUES (102, 180, '2000-01-02 00:00:00');
INSERT INTO `purchase_header` (`customer_id`, `amount`, `datetime`) VALUES (103, 200, '2000-01-03 00:00:00');
INSERT INTO `purchase_header` (`customer_id`, `amount`, `datetime`) VALUES (101, 70, '2000-01-04 00:00:00');
INSERT INTO `purchase_header` (`customer_id`, `amount`, `datetime`) VALUES (103, 280, '2000-01-05 00:00:00');
INSERT INTO `purchase_header` (`customer_id`, `amount`, `datetime`) VALUES (102, 310, '2000-01-06 00:00:00');
INSERT INTO `purchase_header` (`customer_id`, `amount`, `datetime`) VALUES (101, 10, '2000-01-07 00:00:00');
INSERT INTO `purchase_header` (`customer_id`, `amount`, `datetime`) VALUES (103, 280, '2000-01-08 00:00:00');

안 되는 그림 1 SELECT와 MAX(amount)로 날짜 정렬


이거 내가 MySQL을 처음 기억했을 때 try부터 했어.
SELECT
    customer_id, MAX(amount), datetime
FROM
    purchase_header
GROUP BY customer_id
;
amount의 최대치를 얻었지만 예상한 데이터를 얻지 못했다.

원래 GROUPBY가 지정하지 않았기 때문에 종합되지 않은 데이터에 대해 SELECT를 하는 것은 표준 SQL에서 정확하지 않지만 MySQL에서는 허용된다.
MySQL에서 GRUPBY 처리

모드 1 WHERE 문장의 하위 질의에서 MAX()


하위 조회에서 구매 금액의 최대치를 얻고 외부 조회에서 이와 같은 기록을 표시한다.
SELECT
    ph.customer_id, ph.amount, ph.datetime
FROM
    purchase_header AS ph
WHERE
    ph.amount = (
        SELECT
            MAX(sub_ph.amount)
        FROM
            purchase_header AS sub_ph
        WHERE
            ph.customer_id = sub_ph.customer_id
        GROUP BY sub_ph.customer_id
    )
;
예기한 결과를 얻었다.
최대치가 두 개인 경우에도 두 개를 찾을 수 있다.

단, 관련 하위 조회로서 내부의 하위 조회× 외부 조회에 대한 평가를 통해 원가가 쉽게 높아진다.
EXPLAIN 및 subph는 DEPENDENT SUBQUERY입니다.

모드 2LEFT JOIN을 통한 자가 결합


생각을 바꿔 같은 표를 LEFT JOIN, ON을 활용한다.
pH1보다 큰 기록이 없는 pH2=pH1의 기록이 가장 크다.
ph2는 존재하지 않습니다. IS NULL로 표현됩니다.
SELECT
    ph1.customer_id, ph1.amount, ph1.datetime
FROM
          purchase_header AS ph1
LEFT JOIN purchase_header AS ph2 ON (ph1.customer_id = ph2.customer_id AND ph1.amount < ph2.amount)
WHERE
    ph2.amount IS NULL
;
응, 이 일을 알았을 때 눈을 뜰 수가 없었어.
공연도 좋았어요.


모드 3 WHERE NOT EXISTS


@nora1962jp선생님이 주셨어요.감사합니다.
SELECT
    ph1.customer_id, ph1.amount, ph1.datetime
FROM
    purchase_header AS ph1
WHERE
    NOT EXISTS
    ( SELECT 1 FROM purchase_header AS ph2
      WHERE (ph1.customer_id = ph2.customer_id AND ph1.amount < ph2.amount) )
;

모드 4FROM 문에서 GROUPBY, 최대치를 위한 테이블 준비


@shinx55선생님이 주셨어요.감사합니다.
SQL에서 뜻을 쉽게 이해할 수 있고, 속도도 그런대로 괜찮은 다른 방안이다.
SELECT
    ph.customer_id AS customer_id, ph.amount AS max_amount, ph.datetime AS datetime
FROM
    purchase_header AS ph, 
    (
        SELECT
            customer_id, MAX(amount) AS max_amount
        FROM
            purchase_header
        GROUP BY customer_id
    ) AS mh
WHERE ( ph.customer_id = mh.customer_id AND ph.amount = mh.max_amount )

참고 자료

  • MySQL5.6 참조 매뉴얼은 특정 열의 그룹당 최대 값을 저장하는 행
  • 좋은 웹페이지 즐겨찾기