[SQL] 59412 입양 시각 구하기 (1)
https://school.programmers.co.kr/learn/courses/30/lessons/59412
📌문제
- ANIMAL_OUTS : 동물 보호소에서 입양 보낸 동물의 정보를 담은 테이블
- 보호소 입양 데이터에서 09:00~19:59까지 시간별 입양 건수를 구하고,
- 시간순으로 정렬하는 문제
💡 정답 쿼리
SELECT HOUR(DATETIME) AS HOUR, COUNT(*) AS COUNT
FROM animal_outs
WHERE HOUR(DATETIME) BETWEEN 9 AND 19
GROUP BY HOUR
ORDER BY HOUR;
오답
처음에는 DATE_FORMAT()으로 '%k' 포매팅을 했는데, 정렬 결과 9시가 맨 아래로 내려갔다.
- 문제에서 포맷 함수를 쓰는 걸 의도 하지 않은 경우이다.
- 그냥 시간만 뽑는 문제인데 %H나 %k로 풀어서 오답처리 됐다.
- %k까지 썼는데도 틀렸던 이유가 아마 “문자열 출력” 때문일 확률이 높다.
-- 코드를 입력하세요
SELECT DATE_FORMAT(DATETIME, '%k') AS HOUR, COUNT(*)
FROM animal_outs
WHERE DATE_FORMAT(DATETIME, '%k') BETWEEN 9 AND 19
GROUP BY HOUR
ORDER BY HOUR
9시가 맨 뒤로 나오는 건 ORDER BY
기준이 문자열로 처리돼서 그렇다. DATE_FORMAT(DATETIME, '%k')
는 결과값이 0~23 정수로 나오지만
MySQL이 이걸 문자열로 인식해서 정렬할 때 10
, 11
이 9
보다 앞에 오게 된다.
해결방법
- CAST로 숫자형 변환해서 정렬하기
SELECT DATE_FORMAT(DATETIME, '%k') AS HOUR, COUNT(*) AS COUNT
FROM animal_outs
WHERE DATE_FORMAT(DATETIME, '%k') BETWEEN 9 AND 19
GROUP BY HOUR
ORDER BY CAST(HOUR AS UNSIGNED);
- DATE_FORMAT 대신 HOUR() 함수 사용하기
HOUR()
함수는 DATETIME에서 시간 부분을 정수로 바로 뽑아줘서 정렬할 때 문제 없다.
SELECT HOUR(DATETIME) AS HOUR, COUNT(*) AS COUNT
FROM animal_outs
WHERE HOUR(DATETIME) BETWEEN 9 AND 19
GROUP BY HOUR
ORDER BY HOUR;
SELECT가 GROUP BY절보다 나중에 시행되는데 별칭만 써도 되는 이유
일반적으로 SQL 실행 순서에서는 SELECT
절이 GROUP BY
보다 나중에 실행되기 때문에, 이론적으로는 SELECT
에서 만든 별칭(alias)을 GROUP BY
에서 바로 쓰면 안 된다.
하지만 MySQL에서만 예외적으로 허용해줘서 가능한 것이다.
이유를 좀 더 자세히 설명하면
- 표준 SQL 실행 순서
FROM
→WHERE
→GROUP BY
→HAVING
→SELECT
→ORDER BY
순서로 실행된다.- 따라서
GROUP BY
단계에서는SELECT
의 별칭이 아직 생성되지 않은 상태이다.
- MySQL의 특별 처리
- MySQL은 사용자의 편의를 위해
GROUP BY
와ORDER BY
에서SELECT
별칭을 참조할 수 있도록 설계되었다. - 실제 실행 계획에서는
GROUP BY
에 들어가는 표현식을 다시 파싱해서 실행하지만, 문법적으로 별칭을 써도 오류를 내지 않고 처리한다.
- MySQL은 사용자의 편의를 위해
- 결과적으로
- MySQL에서는
GROUP BY HOUR
처럼SELECT
에서 정의한 별칭을 사용해도 문법 오류 없이 작동한다. - 하지만 다른 DBMS(오라클, SQL Server 등)에서는 이게 안 될 수 있어서, 이식성을 위해서는 원래 표현식(
DATE_FORMAT(DATETIME, '%k')
)을GROUP BY
에 직접 쓰는 것이 안전하다.
- MySQL에서는
DATE_FORMAT() 에서의 BETWEEN 범위
BETWEEN
은 끝점 포함(inclusive) 이다.
즉, BETWEEN 9 AND 19
는
- 9시 00분 00초부터
- 19시 59분 59초까지 (사실은 19시까지 모든 시간 포함)
를 뜻한다.
하지만 여기서 중요한 점은, MySQL에서 DATE_FORMAT(DATETIME, '%k')
는
- 시간 부분만 정수로 뽑아내서
0
부터23
까지 반환한다는 것이다.
예를 들어,
09:00:00
,09:30:00
,09:59:59
모두9
로 나온다.19:00:00
부터19:59:59
까지 모두19
로 나온다.
따라서 BETWEEN 9 AND 19
조건은 9시부터 19시 59분 59초까지 전부 포함하는 효과가 있다.
그래서 19시 00분부터 19시 59분까지 데이터가 모두 조회된다.
만약 19시 정각까지만 포함하고 싶다면, DATE_FORMAT(DATETIME, '%k') < 20
과 같이 조건을 쓰는 게 명확하다.
요약하면:
BETWEEN 9 AND 19
= 9:00:00 ~ 19:59:59 (19시대 전체)- 19:00:00까지만 포함하려면
DATE_FORMAT(DATETIME, '%k') <= 19
또는DATE_FORMAT(DATETIME, '%k') < 20
사용
DATE_FORMAT 시간 포매터 정리
MySQL에서 %h
와 %H,
%h
는 모두 시간(hour) 포맷이지만, 기준이 다르다.
%H
→ 00 ~ 23 (2자리, 0 포함) 24시간제%h
→ 01 ~ 12 (12시간제, 앞에 0 포함) AM/PM 체계%k
→ 0 ~ 23 (앞에 0 안붙음)
참고 자료 : chatGPT