Posts 프로그래머스 Level 4 - 입양 시각 구하기(2) (MySQL)
Post
Cancel

프로그래머스 Level 4 - 입양 시각 구하기(2) (MySQL)

프로그래머스 - Level4 입양 시각 구하기(2)

문제 설명

ANIMAL_OUTS 테이블은 동물 보호소에서 입양 보낸 동물의 정보를 담은 테이블입니다. ANIMAL_OUTS 테이블 구조는 다음과 같으며, ANIMAL_IDANIMAL_TYPEDATETIMENAMESEX_UPON_OUTCOME는 각각 동물의 아이디, 생물 종, 입양일, 이름, 성별 및 중성화 여부를 나타냅니다.

(테이블 생략)

보호소에서는 몇 시에 입양이 가장 활발하게 일어나는지 알아보려 합니다. 0시부터 23시까지, 각 시간대별로 입양이 몇 건이나 발생했는지 조회하는 SQL문을 작성해주세요. 이때 결과는 시간대 순으로 정렬해야 합니다.

🙋‍♂️나의 풀이

SQL 문법을 잘 몰라서 다른 분들의 풀이를 참고해서 풀었다.

작성 코드

1
2
3
4
5
6
SET @hour := -1;

SELECT (@hour := @hour + 1) as HOUR,
(SELECT COUNT(*) FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = @hour) as COUNT
FROM ANIMAL_OUTS
WHERE @hour < 23
  • SET 은 변수명과 초기값을 설정하는 명령어이다.
    • @ 가 붙은 변수는 프로시저가 종료되어도 유지된다.
    • 따라서 값을 누적하여 0부터 23까지 표현이 가능하다.
  • := 은 PL/SQL 문법에서 비교 연산자 = 과 혼동을 피하기 위해 사용하는 대입 연산자이다.
  • SELECT (@hour := @hour +1)@hour 값에 1씩 증가시키면서 SELECT 문을 실행한다.
  • 그리고 서브쿼리에서 @hour 값과 일치하는 데이터를 찾아서 COUNT 한다.

👀 참고한 풀이

WITH RECURSIVE 를 활용해서 재귀적으로 쿼리를 작성하였다.

  • Recursive CTE(Common Table Expression)은 CTE 중 자기 자신을 반복적으로 호출하는 CTE이다. 흔히 조직도와 같은 계층적 데이터의 처리나 BOM(Bill of Materials) 등을 쿼리하는 데 많이 사용한다.
  • 구문은 다음과 같다.
1
2
3
4
5
6
WITH RECURSIVE 생성할__이름 AS
(
	초기 SQL
	UNION ALL
	반복할_SQL(반복을_멈출_WHERE_포함)
)

MySQL 공식 사이트에 나온 예제를 살펴보자.

1
2
3
4
5
6
7
WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte WHERE n < 5
)
SELECT * FROM cte;
1
2
3
4
5
6
7
8
9
+------+
| n    |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
+------+
  • Recursive 구문을 통해 생성하는 뷰 이름은 cte 이고, cte 에는 n 이라는 컬럼이 존재한다.
  • 그리고 SELECT 1 구문을 통해 1만 담긴 초기값 열을 하나 생성한다.
  • UNION ALL 아래의 반복문을 수행하면서 나온 결과를 초기값과 모두 합친다.

1
2
3
4
5
6
7
8
9
10
11
WITH RECURSIVE cte (HOUR) AS
(
    SELECT 0
    UNION ALL
    SELECT HOUR + 1 FROM cte WHERE HOUR < 23
)
SELECT cte.HOUR, COUNT(ANIMAL_OUTS.ANIMAL_ID) AS 'COUNT'
FROM cte
LEFT JOIN ANIMAL_OUTS
ON cte.HOUR = HOUR(ANIMAL_OUTS.DATETIME)
GROUP BY cte.HOUR
  • HOUR 컬럼에 0부터 23까지 값을 가진 cte 뷰를 생성한다.
  • 이렇게 만들어진 cte와 ANIMAL_OUTS를 LEFT JOIN을 수행하는데, cte.HOUR와 ANINAL_OUTS의 HOUR(ANIMAL_OUTS.DATETIME)이 같은 값을 기준으로 조인한다.
  • LEFT JOIN을 하는 이유는 오른쪽 테이블에 맞는 컬럼이 없어도, 왼쪽 테이블을 기준으로 무조건 JOIN 하기 때문이다. 만약, HOUR(ANIMAL_OUTS.DATETIME)에 18시가 없어도, cte에는 존재하므로 cte.HOUR 18에 해당하는 COUNT는 0으로 row가 생성된다.
  • 그리고 GROUP BY로 합계를 처리할 때, COUNT(*)을 많이 사용하는데, 여기서는 다른 방식을 접근해야 한다.
  • 위의 예시처럼 HOUR(ANIMAL_OUTS.DATETIME)에 18시가 없어도, cte.HOUR에는 18이 있기 때문에 LEFT JOIN한 테이블에는 ANIMAL_OUTS 값이 NULL로 채워진 cte.HOUR 18 row가 존재하게 된다.
  • 따라서 COUNT(*)는 모든 row의 개수를 세는 것이기 때문에, 입양 데이터가 없어도 cte 뷰로 인해 생성된 row도 count 되어 COUNT(*)의 값은 무조건 1이상이 된다.

참고자료

This post is licensed under CC BY 4.0 by the author.