[데이터베이스실습] 05. 오라클 함수 - 숫자·날짜·변환
문자 함수에 이어, 이번에는 숫자·날짜를 다루는 함수와 자료형을 서로 바꾸는 변환 함수, 그리고 NULL과 조건을 처리하는 함수를 정리했다. 실무 쿼리에서 "보기 좋은 형태로 가공하는" 일의 대부분이 여기서 이뤄진다고 한다.
1. 숫자 함수
| 함수 | 설명 |
|---|---|
| ROUND(숫자, 위치) | 특정 위치에서 반올림 (생략 시 소수 첫째 자리) |
| TRUNC(숫자, 위치) | 특정 위치에서 버림 (생략 시 소수 첫째 자리) |
| CEIL(숫자) | 올림 (그보다 크거나 같은 가장 작은 정수) |
| FLOOR(숫자) | 내림 (그보다 작거나 같은 가장 큰 정수) |
| MOD(숫자, 나눌수) | 나눈 나머지 |
SELECT ROUND(1234.5678, 2), -- 1234.57
TRUNC(1234.5678, 2), -- 1234.56
ROUND(1234.567, -2) -- 1200 (음수 위치: 정수부 반올림)
FROM DUAL;
SELECT ROUND(SAL, -2) FROM EMP; -- 급여를 100단위로 반올림
위치 인자에 음수를 주면 정수부에서 반올림·버림한다는 점이 포인트다. ROUND(1234.567, -2)는 백의 자리에서 반올림해 1200이 된다. 음수 위치는 처음엔 헷갈렸는데 직접 -1, -2를 넣어보니 자리수 감각이 잡혔다.
2. 날짜 함수
오라클의 날짜는 DATE 형이다. "날짜 + 1"은 하루 뒤를 뜻해 유효하지만, "날짜 + 날짜"는 의미가 없어 허용되지 않는다. 반대로 "날짜 − 날짜"는 두 날짜 사이의 일수를 돌려준다.
| 함수 | 설명 |
|---|---|
| SYSDATE | DB 서버의 현재 날짜·시간 |
| ADD_MONTHS(날짜, 개월수) | 몇 개월 뒤의 날짜 |
| MONTHS_BETWEEN(날짜1, 날짜2) | 두 날짜 사이 개월 수 |
| NEXT_DAY(날짜, 요일) | 다음에 돌아오는 해당 요일 |
| LAST_DAY(날짜) | 그 달의 마지막 날 |
SELECT SYSDATE, SYSDATE+1 AS 내일, SYSDATE-1 AS 어제 FROM DUAL;
-- 입사 10주년 날짜와, 지금까지의 근속 개월수(정수)
SELECT ENAME,
ADD_MONTHS(HIREDATE, 10*12) AS "10주년",
TRUNC(MONTHS_BETWEEN(SYSDATE, HIREDATE)) AS 근속개월
FROM EMP;
-- 입사 42년이 아직 안 된 사원
SELECT ENAME FROM EMP WHERE ADD_MONTHS(HIREDATE, 42*12) > SYSDATE;
"날짜 − 날짜"가 일수를 돌려준다는 걸 직접 확인하니, 날짜도 결국 내부적으로는 숫자로 다뤄진다는 감이 잡혔다.
3. 형 변환 함수
자료형을 서로 바꾸는 세 함수가 핵심이다. 특히 TO_CHAR는 날짜·숫자를 원하는 서식의 문자열로 바꿔 출력 형태를 다듬을 때 가장 많이 쓴다.
TO_CHAR(날짜/숫자, 출력형식) -- 날짜·숫자 → 문자
TO_NUMBER(문자, 숫자형식) -- 문자 → 숫자
TO_DATE(문자, 날짜형식) -- 문자 → 날짜
자주 쓰는 날짜 서식: YYYY(년 4자리), MM(월), DD(일), DY/DAY(요일 약자/전체), HH24(24시간), MI(분), SS(초)
자주 쓰는 숫자 서식: 9(숫자 한 자리), 0(빈자리 0 채움), ,(천 단위), $/L(통화 기호)
SELECT TO_CHAR(SYSDATE, 'YYYY"년" MM"월" DD"일" (DY) HH24:MI:SS') FROM DUAL;
-- 5월에 입사한 사원
SELECT ENAME FROM EMP WHERE TO_CHAR(HIREDATE, 'MM') = '05';
-- 급여를 통화 형식 문자열로
SELECT SAL, TO_CHAR(SAL, '$999,999') AS 통화 FROM EMP;
-- 문자열을 날짜로 변환해 비교 (입력 형식을 명시)
SELECT ENAME, HIREDATE FROM EMP
WHERE HIREDATE > TO_DATE('1985-02-01', 'YYYY-MM-DD');
TO_DATE에서 입력 형식을 명시하는 습관이 중요하다. "02-01-1985"가 2월 1일인지 1월 2일인지는 형식 문자열(MM-DD-YYYY 등)이 있어야 분명해지기 때문이다.
4. NULL 처리 함수
| 함수 | 설명 |
|---|---|
| NVL(데이터, 대체값) | NULL이면 대체값으로 바꿈 |
| NVL2(데이터, NULL아닐때, NULL일때) | NULL 여부에 따라 둘 중 선택 |
NULL과 산술 연산을 하면 결과가 통째로 NULL이 되어버리는 문제를, NVL로 0 등을 채워 막는다. 앞서 연봉 계산에서 COMM이 NULL이라 결과가 통째로 사라지던 게, 여기서 NVL로 깔끔하게 해결됐다.
-- 수당(COMM)이 NULL인 사원도 합계가 제대로 나오도록
SELECT ENAME, SAL, COMM, SAL + NVL(COMM, 0) AS 총액 FROM EMP;
-- 수당 유무 표시, 1년 소득 계산
SELECT ENAME, COMM,
NVL2(COMM, 'O', 'X') AS 수당여부,
NVL2(COMM, SAL*12+COMM, SAL*12) AS "1년 소득"
FROM EMP;
5. 조건 처리: DECODE와 CASE
값에 따라 다른 결과를 내보내는 두 방법이다.
-- DECODE: 동일(=) 비교만 가능. if/switch 와 유사
DECODE(열, 값1, 결과1, 값2, 결과2, ..., 기본값)
-- CASE: 함수가 아니며, 범위 비교 등 자유로운 조건 가능
CASE WHEN 조건1 THEN 결과1
WHEN 조건2 THEN 결과2
ELSE 기본값
END
DECODE는 오라클 전용이고 "값이 무엇과 같은가"라는 동일 비교만 된다. CASE는 표준 SQL이며 "급여가 3000 이상이면" 같은 범위 비교까지 자유롭게 쓸 수 있다. 단순 매핑엔 DECODE가 간결하고, 복잡한 조건엔 CASE가 적합하다.
실습 쿼리로 익히기
-- 입사 3개월 후 첫 월요일을 재계약일로, 수당은 있으면 표시·없으면 'N/A'
SELECT EMPNO, ENAME,
TO_CHAR(HIREDATE, 'YYYY/MM/DD') AS 입사일,
TO_CHAR(NEXT_DAY(ADD_MONTHS(HIREDATE, 3), '월요일'), 'YYYY-MM-DD') AS 재계약일,
NVL2(COMM, TO_CHAR(COMM), 'N/A') AS 수당
FROM EMP;
이 한 쿼리에 날짜 계산(ADD_MONTHS, NEXT_DAY), 서식 변환(TO_CHAR), NULL 처리(NVL2)가 모두 들어 있다. 함수들을 이렇게 겹겹이 중첩해 "원하는 출력 형태"를 만들어내는 것이 실무 SELECT의 핵심 기술이라는 걸 이 한 줄로 체감했다.
수업에서 직접 친 쿼리
"입사 42년이 안 된 사원"을 여러 방식으로 풀어보며, 같은 조건도 날짜를 어느 쪽으로 옮겨 비교하느냐에 따라 식이 달라진다는 걸 익혔다.
-- ADD_MONTHS로 42년 뒤 날짜를 만들어 오늘과 비교 (가장 깔끔)
SELECT ENAME FROM EMP
WHERE ADD_MONTHS(HIREDATE, 42*12) > SYSDATE;
근속 개월수는 MONTHS_BETWEEN이 실수로 나와서, TRUNC/FLOOR로 정수화하는 걸 비교해봤다.
SELECT ENAME,
MONTHS_BETWEEN(SYSDATE, HIREDATE) AS BET,
TRUNC(MONTHS_BETWEEN(SYSDATE, HIREDATE)) AS "TRUNC",
FLOOR(MONTHS_BETWEEN(SYSDATE, HIREDATE)) AS "FLOOR"
FROM EMP;
TO_CHAR로 급여를 통화·만원 단위로 바꾸는 것도 직접 쳐봤다.
SELECT SAL,
TO_CHAR(SAL, '$999,999.00') AS SAL3,
TO_CHAR(SAL, '9,999') || '만원' -- 만원 단위 표기
FROM EMP;
NVL/NVL2로 COMM의 NULL을 다루는 흐름도 단계적으로 쳐봤다. SAL+COMM이 통째로 NULL이 되던 걸 NVL로 막고, NVL2로 1년 소득을 분기했다.
SELECT ENAME, SAL, COMM,
SAL + NVL(COMM, 0) AS 총액,
NVL2(COMM, SAL*12+COMM, SAL*12) AS "1년 소득"
FROM EMP;
오늘 느낀 점
- 문자·숫자·날짜 함수가 따로 노는 게 아니라, 결국 "원하는 출력 형태로 가공한다"는 한 목적으로 중첩해 쓰인다는 게 보였다. 마지막 실습 쿼리 한 줄에 세 종류가 다 들어가 있었다.
- NVL로 NULL을 0으로 채우니, 앞 실습에서 막혔던 연봉 계산이 풀렸다. 개념(NULL은 모름)과 도구(NVL)가 연결되는 순간이었다.
- DECODE(동일 비교)와 CASE(범위 비교)의 경계를 알아두니, 어떤 조건엔 무엇을 쓸지 고민이 줄었다.
한 걸음 더
ROUND·TRUNC는 숫자뿐 아니라 날짜에도 쓸 수 있다.TRUNC(SYSDATE)는 시·분·초를 잘라내 그날 자정(00:00:00)을 돌려주는데, "오늘 0시 기준" 조건을 만들 때 자주 쓰인다. 같은 함수가 숫자와 날짜 양쪽에 동작한다는 점이 오라클 함수의 특징이다.- 날짜를 비교할 때
TO_CHAR(HIREDATE,'MM')='05'처럼 문자로 바꿔 비교하면 직관적이지만, 열에 함수를 씌우는 순간 그 열의 인덱스를 못 타게 된다. 대용량에서 성능이 중요하면 날짜 범위(>= ... AND < ...)로 거는 편이 낫다. TO_CHAR에 형식을 항상 명시하는 습관은 환경 의존성을 없애는 효과도 있다. 날짜 서식이나 통화 기호의 기본값은 세션의 지역 설정(NLS)에 따라 달라질 수 있어, 형식을 직접 지정하면 어느 환경에서 실행해도 결과 모양이 일정해진다.