251028_BootCamp Day 7_SQL 날짜 & 서브쿼리

2025. 10. 28. 20:33TIL

1. SQL 날짜 연산 내장 함수

여러 사이트에 있는 SQL 문제를 풀면서, 생각보다 날짜(date, time) 관련한 연산이 많이 필요하다는 것을 알게 되었다.
데이터를 구분할 때 날짜를 기준으로 하는 경우가 많을 것 같다. (특정 기간에 발생한 이벤트, 일정 기간 이후 신규 가입자 등)
그런데 날짜를 계산하는 방식을 잘 모르다 보니, 이번 기회에 MySQL에서 사용하는 날짜 연산 내장 함수들에 대해 제대로 알아보기로 하였다.

 

현재 날짜 및 시간 조회

NOW() -- 현재 날짜와 시간 반환(YYYY-MM-DD HH:MM:SS)
CURDATE() -- 현재 날짜 반환(YYYY-MM-DD)
CURTIME() -- 현재 시간 반환(HH:MM:DD)

날짜 및 시간 덧셈/뺄셈

DATE_ADD(date, INTERVAL number unit) -- 일정 간격의 날짜 더하기
	DATE_ADD('2025-10-28', INTERVAL 3 day) --2025-10-28에 3일을 더함 -> 2025-10-31
DATE_SUB(date, INTERVAL number unit) -- 일정 간격의 날짜 빼기
	DATE_SUB('2025-10-28', INTERVAL 2 month) -- 2025-10-28에 2달을 뺌 -> 2025-08-28

날짜/시간 요소 추출

YEAR('2025-10-28') -- 날짜에서 연도 추출 -> 2025
MONTH('2025-10-28') -- 날짜에서 월 추출 -> 10
DAY('2025-10-28') -- 날짜에서 일 추출 -> 28
HOUR('17:08:12') -- 시간에서 시 추출 -> 17
MINUTE('17:08:12') -- 시간에서 분 추출 -> 8
SECOND('17:08:12') -- 시간에서 초 추출 -> 12
DAYNAME('2025-10-28') -- 날짜에서 요일 추출 -> Tuesday
DAYOFWEEK('2025-10-28') -- 날짜에서 요일 추출 후 숫자로 변환 -> 3(1:일요일 ~ 7:토요일)
EXTRACT(unit FROM date) -- 날짜에서 다양한 단위 추출
	EXTRACT(year_month FROM '2025-10-28') -- 202510
	EXTRACT(week FROM '2025-10-28') -- 44 (1년의 몇 주차인지)

날짜/시간 형식 변환(Formatting)

DATE_FORMAT(date, format) -- 날짜를 지정된 format의 문자열로 반환
	DATE_FORMAT('2025-10-28 17:13:20', '%Y-%m-%d') -- 2025-10-28
	DATE_FORMAT('2025-10-28 17:13:20', '%Y년 %m월 %d일 %H시 %i분') -- 2025년 10월 28일 17시 13분
STR_TO_DATE(str, format) -- 문자열을 지정된 형식을 기준으로 날짜 값으로 변환
	STR_TO_DATE('2025-10-28', '%Y-%m-%d') -- 2025-10-28(date type)

날짜/시간 차이 계산

DATEDIFF(date1, date2) -- 두 날짜 사이의 일 수(days) 차이를 반환 (date1-date2)
	DATEDIFF('2025-10-28', '2025-10-24') -- 4
TIEMDIFF(time1, time2) -- 두 시간 사이의 차이를 반환 (time1 - time2)
	TIMEDIFF('17:30:00', '16:25:00') -- '1:05:00'
TIMESTAMPDIFF(unit, datetime_expr1, datetime_expr2) -- 두 날짜/시간 사이의 차이를 지정된 단위로 반환

 

2. 서브쿼리 (Subquery)

서브쿼리는 말 그대로 서브로 존재하는 쿼리이다.
아니 메인 쿼리 하나로 다 되는 거 아니었어? 아쉽게도 SQL은 그렇지가 않다.
실제로는 다뤄야 하는 컬럼들이 매우 많고, 그 컬럼들을 이용해서 다양한 연산을 한꺼번에 수행하다 보니, 서브쿼리를 이용해 각 연산들을 구조적으로 수행/기록해 최종적인 결과를 메인쿼리에서 만드는 경우가 많다.
N개의 쿼리를 작성해 모든 연산들을 따로 계산한 후에 결과값을 하나로 합치는 것보다는 하나의 쿼리에서 모든 작업을 수행하는 것이 훨씬 더 효율적이고, 가독성도 더 우수하기 때문이다.

 

서브쿼리의 특징

서브쿼리를 쓸 때는 반드시 괄호 ()를 이용해서 쿼리를 묶어주어야 한다.

서브쿼리의 끝에는 세미콜론(;)을 작성하면 안 된다.

ORDER BY 절을 사용하지 않는다.(사용할 수는 있으나, 특별히 의미가 없다.)

서브쿼리는 그 위치에 따라 중첩 서브쿼리, 스칼라 서브쿼리, 인라인 뷰로 나뉜다.

서브쿼리의 위치에 따른 분류. 출처 : 나

중첩(일반) 서브쿼리 WHERE 절에서 사용
서브쿼리의 결과를 메인 컬럼을 필터링하는 조건으로 사용하고 싶을 때
상관 서브쿼리 : 메인 쿼리의 컬럼을 참조 ➡️ 메인 쿼리의 행과 하나씩 비교한다.
비상관 서브쿼리 : 메인 쿼리의 내용과 관련 없음
스칼라 서브쿼리 SELECT 절에서 사용
서브쿼리의 결과를 하나의 컬럼처럼 사용하고 싶을 때
스칼라 서브쿼리는 보통 다른 테이블의 컬럼을 메인 쿼리에 쓰고 싶을 때 사용한다.
인라인 뷰 FROM 절에서 사용
서브쿼리를 하나의 테이블처럼 사용하고 싶을 때
가장 중요하고 가장 많이 쓰이는 서브쿼리 유형이다.
인라인 뷰는 as를 이용해 alias를 반드시 지정해줘야 한다.

 

3. 서브쿼리 실습

서브쿼리의 활용

select a.first_login_date,
       a.actor_cnt
from (
      select first_login_date,
             count(distinct game_actor_id) as actor_cnt
      from basic.users
      group by first_login_date
     ) as a
where a.actor_cnt > 10
;

기본적인 인라인 뷰를 사용한 쿼리이다. 인라인 뷰의 값을 메인 쿼리에서 추가적인 연산에 사용하지 않는다.

서브쿼리의 응용

select a.actor_cnt, 
       count(distinct game_account_id) as accnt
from
(
    select game_account_id, count(distinct game_actor_id) as actor_cnt
    from basic.users
    where level >= 30
    group by game_account_id
    having count(distinct game_actor_id) >= 2
) as a
group by a.actor_cnt
;

인라인 뷰의 값을 이용해 메인 쿼리에서 연산을 하는, 조금 더 복잡한 형태의 쿼리이다.