고객별 가장 최근 주문의 날짜뿐 아니라 주문 번호와 금액도 함께 필요할 때가 있습니다. 날짜에 MAX()를 적용하는 것만으로는 그 날짜에 해당하는 행의 다른 열까지 자동으로 선택되지 않아요.
이번 예제에서는 고객마다 주문을 최신순으로 정렬해 번호를 붙인 뒤 첫 행을 선택하겠습니다. 문법은 MySQL 8.4 기준이며, 예제 결과는 SQLite에서 검증합니다.
주문 시각이 같으면 주문 번호가 큰 행을 선택한다는 규칙을 사용하겠습니다. 주문 번호는 기본 키이므로 이 기준까지 적용하면 순서가 하나로 정해집니다. 이것은 동률 처리 규칙이지 큰 번호가 실제로 더 늦게 주문되었다는 증명은 아닙니다.
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
ordered_at DATETIME NOT NULL,
amount INTEGER NOT NULL
);
INSERT INTO orders VALUES
(101, 1, '2026-09-27 09:00:00', 10000),
(102, 1, '2026-09-28 10:00:00', 20000),
(103, 1, '2026-09-28 10:00:00', 15000),
(201, 2, '2026-09-27 11:00:00', 7000);
WITH ranked AS (
SELECT order_id, customer_id, ordered_at, amount,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY ordered_at DESC, order_id DESC
) AS rn
FROM orders
)
SELECT order_id, customer_id, ordered_at, amount
FROM ranked
WHERE rn = 1
ORDER BY customer_id;
| order_id | customer_id | ordered_at | amount |
|---|---|---|---|
| 103 | 1 | 2026-09-28 10:00:00 | 15000 |
| 201 | 2 | 2026-09-27 11:00:00 | 7000 |
PARTITION BY는 번호를 매길 구간을 고객별로 나눕니다. 그 안에서 주문 시각과 번호를 내림차순으로 정렬하고 ROW_NUMBER()가 1부터 번호를 붙여요. 바깥 쿼리는 1번 행만 남깁니다.
윈도 함수 안의 ORDER BY는 번호를 붙일 기준입니다. 최종 결과의 표시 순서는 바깥쪽 ORDER BY로 별도 지정했습니다.
SELECT customer_id,
MAX(ordered_at) AS latest_time,
MAX(amount) AS max_amount
FROM orders
GROUP BY customer_id
ORDER BY customer_id;
| customer_id | latest_time | max_amount |
|---|---|---|
| 1 | 2026-09-28 10:00:00 | 20000 |
| 2 | 2026-09-27 11:00:00 | 7000 |
이 쿼리는 최신 시각과 최대 금액을 각각 구합니다. 선택 규칙에 따른 103번 주문의 금액은 15000인데, 최대 금액은 102번 주문의 20000입니다. 두 집계 결과가 같은 원본 행에서 왔다고 볼 수는 없어요.
같은 최신 시각의 주문을 모두 반환해야 한다면 지금의 한 건 선택 규칙은 맞지 않습니다. 시각만 기준으로 순위를 매기는 RANK() 등 다른 방법을 검토해야 해요. 또한 주문이 없는 고객은 orders 테이블에 행이 없으므로 이 결과에 나타나지 않습니다.
이 예제는 모든 주문 시각이 같은 시간 기준으로 저장되며 NULL이 없다고 가정합니다. 실제 데이터에서는 시간대와 누락 값의 처리 기준도 함께 확인해야 합니다.