프로그래머스 문제를 푸는데 뭔가 안풀려서 사람들이 푸는걸 봤더니 다들 재귀 쿼리라는 것을 사용하는거다.
프로그래머스 문제ANIMAL_OUTS 테이블은 동물 보호소에서 입양 보낸 동물의 정보를 담은 테이블입니다. ANIMAL_OUTS 테이블 구조는 다음과 같으며, ANIMAL_ID, ANIMAL_TYPE, DATETIME, NAME, SEX_UPON_OUTCOME는 각각 동물의 아이디, 생물 종, 입양일, 이름, 성별 및 중성화 여부를 나타냅니다.
| NAME | TYPE | NULLABLE |
|---|---|---|
| ANIMAL_ID | VARCHAR(N) | FALSE |
| ANIMAL_TYPE | VARCHAR(N) | FALSE |
| DATETIME | DATETIME | FALSE |
| NAME | VARCHAR(N) | TRUE |
| SEX_UPON_OUTCOME | VARCHAR(N) | FALSE |
보호소에서는 몇 시에 입양이 가장 활발하게 일어나는지 알아보려 합니다. 0시부터 23시까지, 각 시간대별로 입양이 몇 건이나 발생했는지 조회하는 SQL문을 작성해주세요. 이때 결과는 시간대 순으로 정렬해야 합니다.
SQL문을 실행하면 다음과 같이 나와야 합니다.
| HOUR | COUNT |
|---|---|
| 0 | 0 |
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 0 |
| 6 | 0 |
| 7 | 3 |
| 8 | 1 |
| 9 | 1 |
| 10 | 2 |
| 11 | 13 |
| 12 | 10 |
| 13 | 14 |
| 14 | 9 |
| 15 | 7 |
| 16 | 10 |
| 17 | 12 |
| 18 | 16 |
| 19 | 2 |
| 20 | 0 |
| 21 | 0 |
| 22 | 0 |
| 23 | 0 |
일단 아는대로라도 풀고 싶어서 다른 방법도 찾아봤지만, 풀이의 방법은 대략적으로 2가지가 될 수 있는데,
가 있었다.
union으로 푸는 방법은 다음과 같은데
select HOUR, max(COUNT) COUNT from
(
(select 0 HOUR, 0 COUNT) UNION // 모든 시간대를 생성. (0~23시, 입양간 동물 카운트)
(select 1, 0) UNION
(select 2, 0) UNION
(select 3, 0) UNION
(select 4, 0) UNION
(select 5, 0) UNION
(select 6, 0) UNION
(select 7, 0) UNION
(select 8, 0) UNION
(select 9, 0) UNION
(select 10, 0) UNION
(select 11, 0) UNION
(select 12, 0) UNION
(select 13, 0) UNION
(select 14, 0) UNION
(select 15, 0) UNION
(select 16, 0) UNION
(select 17, 0) UNION
(select 18, 0) UNION
(select 19, 0) UNION
(select 20, 0) UNION
(select 21, 0) UNION
(select 22, 0) UNION
(select 23, 0) UNION
(select hour(datetime) HOUR, count(*) COUNT from animal_outs group by 1)
) TMP
group by 1
order by 1 asc
서브쿼리로 각 시간마다 입양간 동물을 카운트하고 union으로 다 직접 합쳐서 나열하는 방식이다.
가능은 하지만 비효율적인 방식이기 때문에 대부분의 사람들이 재귀 쿼리를 사용한 것으로 보인다.
그래서 재귀쿼리가 뭔데요

뜻을 보자면 재귀의 영문명인 recursive는 '반복되는', '순환하는'의 뜻을 가진다.
쉽게 말해서 자기 자신을 응용할 수 있는 쿼리를 말한다.
유사한 말로, 재귀, recursion은 자기 자신을 서브루틴으로 부르는 형태를 말한다.
for문 처럼 일종의 반복문처럼 활용할 수 있다.
WITH RECURSIVE cte_name (column_name(s)) // 1. WITH RECURSIVE 의 형태로 재귀쿼리를 사용할 것이라 선언 AS ( // 2. AS(재귀 쿼리문 작성) -- Base case // 3. 재귀 쿼리문: Base case + UNION(ALL) + Recursive case SELECT ... // 기본 케이스(루프의 시작)와 재귀 케이스를 UNION ALL // 유니온 또는 유니온 올로 결합하여 작성 -- Recursive case // 4. recursive case의 FROM 절이 윗쪽의 cte_name을 받으면서 재귀(자체참조) SELECT ... // 5. 이때 Where 절이 terminate 조건이 됨 FROM cte_name ... ) SELECT * FROM cte_name; // 결과 테이블CTE을 사용할 쿼리
- 참고로 CTE(Common Table Expression)는 임시로 이름이 지정된 결과 집합을 의미하고, 결과테이블이라고도 한다.
반복되어 생성되는 이 테이블은 쿼리가 끝날때까지만 임시로 메모리에 저장되었다가 삭제된다.
참조
https://inpa.tistory.com/entry/MYSQL-📚-RECURSIVE-재귀-쿼리#
https://codedamn.com/news/sql/recursive-queries-in-sql
이래도 이해가 안된다면 이 영상을 다시 보고 이해하기
WITH RECURSIVE TBL // 0시~23시까지의 시간을 뽑아야 하므 AS ( SELECT // 기본 쿼리를 0, HOUR로 지정 0 AS HOUR UNION ALL SELECT HOUR + 1 // 재귀쿼리는 +1씩 더해지고 FROM TBL WHERE HOUR < 23 // 23 미만일때 종료됨 (22+1=23이니까!) ) SELECT t.HOUR, COUNT(a.ANIMAL_ID) AS COUNT FROM TBL t // tbl과, LEFT JOIN (SELECT // 각 시간마다 입양된 동물을 뽑는 쿼리를 레프트 조인함(빈값 고려) ANIMAL_ID, HOUR(DATETIME) AS HOUR FROM ANIMAL_OUTS) a ON t.HOUR = a.HOUR // 시간을 기준으로 조인 GROUP BY t.HOUR ORDER BY t.HOUR ASC;