Skip to main content

[SQL] 59412 입양 시각 구하기 (1)

https://school.programmers.co.kr/learn/courses/30/lessons/59412

image.png

image.png

📌문제

  • 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

image.png

9시가 맨 뒤로 나오는 건 ORDER BY 기준이 문자열로 처리돼서 그렇다.
DATE_FORMAT(DATETIME, '%k')는 결과값이 0~23 정수로 나오지만
MySQL이 이걸 문자열로 인식해서 정렬할 때 10, 119보다 앞에 오게 된다.


해결방법

  1. 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);
  1. 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에서만 예외적으로 허용해줘서 가능한 것이다.

이유를 좀 더 자세히 설명하면

  1. 표준 SQL 실행 순서
    • FROMWHEREGROUP BYHAVINGSELECTORDER BY 순서로 실행된다.
    • 따라서 GROUP BY 단계에서는 SELECT의 별칭이 아직 생성되지 않은 상태이다.
  2. MySQL의 특별 처리
    • MySQL은 사용자의 편의를 위해 GROUP BYORDER BY에서 SELECT 별칭을 참조할 수 있도록 설계되었다.
    • 실제 실행 계획에서는 GROUP BY에 들어가는 표현식을 다시 파싱해서 실행하지만, 문법적으로 별칭을 써도 오류를 내지 않고 처리한다.
  3. 결과적으로
    • MySQL에서는 GROUP BY HOUR처럼 SELECT에서 정의한 별칭을 사용해도 문법 오류 없이 작동한다.
    • 하지만 다른 DBMS(오라클, SQL Server 등)에서는 이게 안 될 수 있어서, 이식성을 위해서는 원래 표현식(DATE_FORMAT(DATETIME, '%k'))을 GROUP BY에 직접 쓰는 것이 안전하다.

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