sql, group by를 연산자와 함께 사용하기
연속된 숫자를 그룹핑하여 그룹간 첫 번째와 마지막을 구하기 1285. Find the Start and End Number of Continuous Ranges
- https://leetcode.com/problems/find-the-start-and-end-number-of-continuous-ranges/
- 칼럼이 하나(log_id)인 테이블이 있다. 레코드가 늘어나면 칼럼의 값은 이전의 레코드보다 크다. 값이 1씩 증가하는 레코드 묶음을 하나의 그룹으로 만든다. 그리고 해당 그룹의 처음 값과 마지막 값을 구한다.
Input:
Logs table:
+------------+
| log_id |
+------------+
| 1 |
| 2 |
| 3 |
| 7 |
| 8 |
| 10 |
+------------+
Output:
+------------+--------------+
| start_id | end_id |
+------------+--------------+
| 1 | 3 |
| 7 | 8 |
| 10 | 10 |
+------------+--------------+
문제의 해소
- 시작하는 레코드는 자신의 값에서 1을 뺀 값이 테이블 내에 존재해서는 안된다.
- 종료하는 레코드는 자신의 값에서 1을 더한 값이 테이블 내에 존재해서는 안된다.
with tb(id, s, e) as(
select *
from logs l
left join logs s on l.log_id = s.log_id + 1
left join logs e on l.log_id = e.log_id - 1
)
select * from tb;
- 결과 :
{"headers":
["id", "s", "e"],
"values":
[[1, null, 2],
[2, 1, 3],
[3, 2, null],
[7, null, 8],
[8, 7, null],
[10, null, null]]}
- s와 e를 합친다.
with tb(id, s, e) as(
select
*
from logs l
left join logs s on l.log_id = s.log_id + 1
left join logs e on l.log_id = e.log_id - 1
)
select
s.id 'start_id'
, e.id 'end_id'
from (
select
(@idx1:=@idx1+1) idx
, t.id
from tb t
,(select @idx1:=0) i
where t.s is null
) s
join (
select
(@idx2:=@idx2+1) idx
, t.id
from tb t
,(select @idx2:=0) i
where t.e is null
) e
on s.idx = e.idx
훨씬 쉬운 방법
- 성능은 나쁘지 않았으나 쿼리가 다소 길고 이해하기 어렵다. 그래서 다른 사람이 쿼리를 살펴봤다.
- 완전 쉬운 방법이 있었다!
+------------+-----+-------------+
| log_id | idx |log_id - idx |
+------------+-----+-------------+
| 1 |1 |0 |
| 2 |2 |0 |
| 3 |3 |0 |
| 7 |4 |3 |
| 8 |5 |3 |
| 10 |6 |4 |
+------------+-----+-------------+
- 숫자의 순서대로 인덱스를 부여한다. 그리고 log_id와 인덱스를 뺀다. 뺀 값이 동일한 레코드가 바로 같은 그룹이 된다.
- 각각의 그룹(위에서는 0, 3, 4)에서 log_id의 최댓값과 최솟값을 구한다.
- 그룹핑에 대한 완전 새로운 접근이었다!
- 더하여, group by에 연산자(-)까지 넣었다.
select
min(log_id) as start_id
, max(log_id) as end_id
from(
select
log_id
, row_number() over(order by log_id) as num
from logs
) a
group by log_id - num