Spring Boot와 MyBatis로 사내 업무 시스템을 만드는 개발자를 위한 Oracle 19c 문법 참조입니다. 예시 테이블 데이터를 기준으로 예상 결과를 표기했을 뿐 실제 실행 결과는 아닙니다.
Ctrl+F로 키워드를 검색해서 사용하세요.
| 구문 | 설명 | 예제 | 결과 |
|---|---|---|---|
SELECT ... WHERE |
조회 | SELECT * FROM emp WHERE deptno=10 |
조건에 맞는 행 |
ORDER BY col DESC |
정렬 | ORDER BY sal DESC |
급여 내림차순 |
GROUP BY ... HAVING |
그룹 집계 | GROUP BY deptno HAVING COUNT(*)>2 |
3명 이상 부서 |
JOIN ... ON |
조인 | emp e JOIN dept d ON e.deptno=d.deptno |
부서명 포함 조회 |
INSERT INTO ... VALUES |
삽입 | INSERT INTO dept VALUES (40,'IT') |
1행 추가 |
UPDATE ... SET ... WHERE |
수정 | UPDATE emp SET sal=sal*1.1 WHERE deptno=10 |
급여 인상 |
DELETE FROM ... WHERE |
삭제 | DELETE FROM emp WHERE empno=7900 |
1행 삭제 |
MERGE INTO |
UPSERT | MERGE INTO dept USING ... |
있으면 수정 없으면 삽입 |
NVL(col, 기본값) |
NULL 대체 | NVL(mgr,0) |
NULL이면 0 |
TO_CHAR(date, fmt) |
날짜 포맷 | TO_CHAR(hiredate,'YYYY-MM-DD') |
문자열 변환 |
TO_DATE(str, fmt) |
문자열→날짜 | TO_DATE('2024-01-01','YYYY-MM-DD') |
DATE 값 |
SUBSTR(str, pos, len) |
부분 문자열 | SUBSTR(ename,1,3) |
앞 3글자 |
COUNT/SUM/AVG/MAX/MIN |
집계 함수 | SUM(sal) |
급여 합계 |
ROW_NUMBER() OVER(...) |
순번 부여 | ROW_NUMBER() OVER(ORDER BY sal DESC) |
순위 값 |
OFFSET n ROWS FETCH NEXT m ROWS ONLY |
페이징 | OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY |
10건 |
WITH cte AS (...) |
공통 테이블 식 | WITH t AS (SELECT ...) |
임시 결과집합 |
CASE WHEN ... THEN ... END |
조건식 | CASE WHEN sal>2000 THEN '고' ELSE '저' END |
분기 결과 |
EXISTS (subquery) |
존재 검사 | WHERE EXISTS (SELECT 1 FROM dept ...) |
참/거짓 |
SELECT ... FOR UPDATE |
행 잠금 | SELECT * FROM emp WHERE empno=7900 FOR UPDATE |
잠금 획득 |
EXPLAIN PLAN FOR |
실행계획 | EXPLAIN PLAN FOR SELECT ... |
PLAN_TABLE 저장 |
아래 두 테이블을 이후 모든 예제에서 공통으로 사용합니다.
dept
| deptno | dname |
|---|---|
| 10 | ACCOUNTING |
| 20 | RESEARCH |
| 30 | SALES |
emp
| empno | ename | deptno | sal | hiredate | mgr |
|---|---|---|---|---|---|
| 7839 | KING | 10 | 5000 | 1981-11-17 | (NULL) |
| 7566 | JONES | 20 | 2975 | 1981-04-02 | 7839 |
| 7788 | SCOTT | 20 | 3000 | 1987-04-19 | 7566 |
| 7369 | SMITH | 20 | 800 | 1980-12-17 | 7788 |
| 7698 | BLAKE | 30 | 2850 | 1981-05-01 | 7839 |
| 7654 | MARTIN | 30 | 1250 | 1981-09-28 | 7698 |
| 7900 | JAMES | 30 | 950 | 1981-12-03 | 7698 |
emp와 dept는 부서번호 deptno로 연결됩니다. mgr는 같은 emp 테이블의 empno를 참조하는 셀프 참조 컬럼입니다.
CREATE TABLE dept (
deptno NUMBER(2) CONSTRAINT dept_pk PRIMARY KEY,
dname VARCHAR2(20) NOT NULL
);
CREATE TABLE emp (
empno NUMBER(4) CONSTRAINT emp_pk PRIMARY KEY,
ename VARCHAR2(20) NOT NULL,
deptno NUMBER(2) CONSTRAINT emp_dept_fk REFERENCES dept(deptno),
sal NUMBER(9,2) CONSTRAINT emp_sal_ck CHECK (sal >= 0),
hiredate DATE DEFAULT SYSDATE,
mgr NUMBER(4) REFERENCES emp(empno),
email VARCHAR2(50) UNIQUE
);시퀀스로 기본키를 채번합니다. 12c부터는 IDENTITY 컬럼도 사용할 수 있습니다.
CREATE SEQUENCE emp_seq START WITH 8000 INCREMENT BY 1 NOCACHE;
INSERT INTO emp (empno, ename, deptno, sal)
VALUES (emp_seq.NEXTVAL, 'ALLEN', 30, 1600);
SELECT emp_seq.CURRVAL FROM dual;
-- → 8000, 같은 세션에서 방금 뽑은 값 재조회
-- 12c 이후: 시퀀스 없이 자동 증가
CREATE TABLE emp2 (
empno NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ename VARCHAR2(20)
);인덱스와 컬럼 변경, 주석은 다음과 같이 작성합니다.
CREATE INDEX idx_emp_deptno ON emp(deptno);
CREATE INDEX idx_emp_deptno_sal ON emp(deptno, sal);
CREATE INDEX idx_emp_upper_ename ON emp(UPPER(ename)); -- 함수 기반
ALTER TABLE emp ADD hp_no VARCHAR2(20);
ALTER TABLE emp MODIFY sal NUMBER(10,2);
ALTER TABLE emp DROP COLUMN hp_no;
COMMENT ON TABLE emp IS '직원 테이블';
COMMENT ON COLUMN emp.sal IS '월급, 원 단위';-- 단건 삽입
INSERT INTO dept (deptno, dname) VALUES (40, 'IT');
-- INSERT ... SELECT: 조회 결과를 그대로 적재
INSERT INTO dept_backup SELECT * FROM dept;
-- INSERT ALL: 여러 테이블/행에 한 번에 삽입
INSERT ALL
INTO dept VALUES (50, 'HR')
INTO dept VALUES (60, 'QA')
SELECT * FROM dual;
-- 서브쿼리로 갱신: deptno=30 급여를 부서 평균으로
UPDATE emp e
SET sal = (SELECT AVG(sal) FROM emp WHERE deptno = e.deptno)
WHERE deptno = 30;
-- DELETE: 행 단위, ROLLBACK 가능, 트리거 발생
DELETE FROM emp WHERE empno = 7900;
-- TRUNCATE: 테이블 전체 비움, ROLLBACK 불가, HWM 초기화
TRUNCATE TABLE dept_backup;MERGE INTO는 있으면 UPDATE, 없으면 INSERT하는 UPSERT 문입니다.
MERGE INTO dept d
USING (SELECT 40 AS deptno, 'IT팀' AS dname FROM dual) s
ON (d.deptno = s.deptno)
WHEN MATCHED THEN
UPDATE SET d.dname = s.dname
WHEN NOT MATCHED THEN
INSERT (deptno, dname) VALUES (s.deptno, s.dname);
-- → deptno=40 있으면 dname 갱신, 없으면 새 행 삽입-- WHERE 연산자
SELECT * FROM emp WHERE sal BETWEEN 1000 AND 3000;
SELECT * FROM emp WHERE deptno IN (10, 20);
SELECT * FROM emp WHERE ename LIKE 'S%';
SELECT * FROM emp WHERE ename LIKE 'S\_MITH' ESCAPE '\'; -- _를 문자로
SELECT * FROM emp WHERE mgr IS NULL; -- KING만 해당
-- ORDER BY: NULL 위치 지정
SELECT ename, mgr FROM emp ORDER BY mgr NULLS FIRST;
-- DISTINCT
SELECT DISTINCT deptno FROM emp;
-- → 10, 20, 30
-- 별칭과 큰따옴표: 공백·한글 별칭은 큰따옴표로 감싼다
SELECT sal AS "월 급여" FROM emp WHERE empno = 7839;
-- DUAL: 테이블 없이 값 하나를 조회할 때 쓰는 가상 테이블
SELECT SYSDATE, 1 + 1 AS calc FROM dual;-- INNER JOIN: 양쪽 다 있는 행만
SELECT e.ename, d.dname
FROM emp e
JOIN dept d ON e.deptno = d.deptno;
-- LEFT JOIN: emp 기준, 매칭 없으면 dname은 NULL
SELECT e.ename, d.dname
FROM emp e
LEFT JOIN dept d ON e.deptno = d.deptno;
-- FULL JOIN: 양쪽 어느 한쪽에만 있어도 표시
SELECT e.ename, d.dname
FROM emp e
FULL JOIN dept d ON e.deptno = d.deptno;
-- Oracle 구식 (+) 표기: LEFT JOIN e.deptno = d.deptno(+) 와 동일
SELECT e.ename, d.dname
FROM emp e, dept d
WHERE e.deptno = d.deptno(+);
-- 셀프 조인: 직원과 그 상급자
SELECT e.ename AS 직원, m.ename AS 상급자
FROM emp e
LEFT JOIN emp m ON e.mgr = m.empno;
-- CROSS JOIN: 모든 조합, 카티전 곱
SELECT e.ename, d.dname FROM emp e CROSS JOIN dept d;LEFT JOIN에서 조인 조건과 WHERE 조건은 결과가 다릅니다.
-- ON에 조건: deptno=10이 아닌 emp도 남고, 조건 안 맞으면 dname만 NULL
SELECT e.ename, d.dname
FROM emp e
LEFT JOIN dept d ON e.deptno = d.deptno AND d.deptno = 10;
-- WHERE에 조건: LEFT JOIN 결과에서 dname=NULL인 행까지 걸러짐
SELECT e.ename, d.dname
FROM emp e
LEFT JOIN dept d ON e.deptno = d.deptno
WHERE d.deptno = 10;
-- → 사실상 INNER JOIN과 같은 결과-- COUNT(*): 전체 행 수, COUNT(col): NULL 제외한 개수
SELECT COUNT(*), COUNT(mgr) FROM emp;
-- → COUNT(*)=7, COUNT(mgr)=6 (KING의 mgr가 NULL)
SELECT deptno, SUM(sal), AVG(sal), MAX(sal), MIN(sal)
FROM emp
GROUP BY deptno;
-- HAVING: 그룹 결과에 대한 조건, WHERE보다 뒤에 평가
SELECT deptno, COUNT(*) cnt
FROM emp
GROUP BY deptno
HAVING COUNT(*) >= 3;
-- → deptno=20, 30만 남음
-- ROLLUP: 부서별 합계 + 전체 합계 소계 행 추가
SELECT deptno, SUM(sal)
FROM emp
GROUP BY ROLLUP(deptno);
-- LISTAGG: 그룹의 값을 구분자로 이어붙인 문자열로
SELECT deptno, LISTAGG(ename, ', ') WITHIN GROUP (ORDER BY ename) AS 명단
FROM emp
GROUP BY deptno;
-- → 20: JONES, SCOTT, SMITH
-- GROUPING: ROLLUP 소계 행인지 구분 (1이면 소계)
SELECT deptno, SUM(sal), GROUPING(deptno) AS is_total
FROM emp
GROUP BY ROLLUP(deptno);-- 순위: RANK/DENSE_RANK는 동점 처리 방식이 다르다
SELECT ename, sal,
ROW_NUMBER() OVER (ORDER BY sal DESC) AS rn,
RANK() OVER (ORDER BY sal DESC) AS rk,
DENSE_RANK() OVER (ORDER BY sal DESC) AS drk
FROM emp;
-- PARTITION BY: 부서별로 나눠서 순위 계산
SELECT ename, deptno, sal,
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS dept_rank
FROM emp;
-- LAG/LEAD: 이전/다음 행 값 참조
SELECT ename, hiredate,
LAG(hiredate) OVER (ORDER BY hiredate) AS 이전입사일,
LEAD(hiredate) OVER (ORDER BY hiredate) AS 다음입사일
FROM emp;
-- 누적 합계
SELECT ename, hiredate, sal,
SUM(sal) OVER (ORDER BY hiredate) AS 누적급여
FROM emp;
-- FIRST_VALUE, NTILE: 부서 내 최고 급여자, 4분위 그룹
SELECT ename, deptno, sal,
FIRST_VALUE(ename) OVER (PARTITION BY deptno ORDER BY sal DESC) AS top,
NTILE(4) OVER (ORDER BY sal DESC) AS quartile
FROM emp;
-- 그룹별 상위 N: 부서별 급여 1위만 뽑기
SELECT * FROM (
SELECT e.*, RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) rk
FROM emp e
) WHERE rk = 1;ROWNUM은 정렬 전에 매겨지므로 정렬 후 자르려면 서브쿼리로 감싸야 합니다.
-- ROWNUM 함정: 이 쿼리는 "정렬 안 된" 상태에서 3건을 자른다
SELECT * FROM emp ORDER BY sal DESC; -- 여기에 WHERE ROWNUM<=3을 붙이면 안 됨
-- 올바른 3중 서브쿼리 패턴: 정렬 → ROWNUM 부여 → 범위 자르기
SELECT * FROM (
SELECT a.*, ROWNUM rn FROM (
SELECT * FROM emp ORDER BY sal DESC
) a WHERE ROWNUM <= 10
) WHERE rn > 5;
-- → 급여 6~10위
-- 12c 이후: OFFSET FETCH로 훨씬 간결
SELECT * FROM emp
ORDER BY sal DESC
OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY;
-- ROW_NUMBER 방식: 총 건수와 함께 페이지 계산이 필요할 때
SELECT * FROM (
SELECT e.*, ROW_NUMBER() OVER (ORDER BY sal DESC) rn FROM emp e
) WHERE rn BETWEEN 6 AND 10;
-- 키셋 페이징: 마지막으로 본 id 이후만 조회, 대용량에서 빠름
SELECT * FROM emp
WHERE empno > 7654
ORDER BY empno
FETCH NEXT 10 ROWS ONLY;MyBatis에서는 마지막으로 조회한 empno를 파라미터로 넘깁니다.
<!-- lastEmpno=null이면 처음부터, 아니면 그 이후부터 -->
SELECT * FROM emp
WHERE 1=1
<if test="lastEmpno != null">AND empno > #{lastEmpno}</if>
ORDER BY empno
FETCH NEXT #{pageSize} ROWS ONLYSELECT SUBSTR('HELLO', 1, 3) FROM dual; -- → HEL
SELECT SUBSTR('HELLO', -3) FROM dual; -- → LLO, 음수는 뒤에서부터
SELECT INSTR('HELLO', 'L') FROM dual; -- → 3, 첫 위치
SELECT LENGTH('안녕'), LENGTHB('안녕') FROM dual;
-- → LENGTH=2(문자 수), LENGTHB=6(UTF-8 바이트 수)
SELECT UPPER('abc'), LOWER('ABC'), INITCAP('abc def') FROM dual;
-- → ABC, abc, Abc Def
SELECT LPAD('7', 4, '0'), RPAD('AB', 5, '*') FROM dual;
-- → 0007, AB***
SELECT TRIM(' hi '), LTRIM(' hi'), RTRIM('hi ') FROM dual;
SELECT REPLACE('2024-01-01', '-', '/') FROM dual; -- → 2024/01/01
SELECT TRANSLATE('abc', 'ab', 'AB') FROM dual; -- → ABc, 문자 단위 치환
SELECT ename || '(' || deptno || ')' FROM emp WHERE empno = 7839;
SELECT CONCAT('A', 'B') FROM dual; -- → AB, 두 인자만 받음
-- 정규식: LIKE보다 세밀한 패턴 매칭
SELECT * FROM emp WHERE REGEXP_LIKE(ename, '^[JS]');
SELECT REGEXP_SUBSTR('a-01', '[0-9]+') FROM dual; -- → 01
SELECT REGEXP_REPLACE('010-1234-5678', '-', '') FROM dual; -- → 01012345678SELECT ROUND(1234.567, 1), ROUND(1234.567, -2) FROM dual;
-- → 1234.6, 1200 (음수는 정수 자릿수 반올림)
SELECT TRUNC(1234.567, 1), TRUNC(1234.567, -2) FROM dual;
-- → 1234.5, 1200 (버림, 반올림 없음)
SELECT MOD(10, 3), CEIL(1.1), FLOOR(1.9), ABS(-5), POWER(2, 10) FROM dual;
-- → 1, 2, 1, 5, 1024
SELECT TO_NUMBER('1,234', '9,999') FROM dual; -- → 1234
-- 0으로 나누기 방지: NULLIF로 분모가 0이면 NULL 처리
SELECT sal / NULLIF(0, 0) FROM dual; -- → NULL, ORA-01476 대신 안전
SELECT 100 / NULLIF(deptno - 10, 0) FROM emp WHERE empno = 7839;
-- NUMBER(p,s): p는 전체 자릿수, s는 소수점 이하 자릿수
-- NUMBER(9,2)는 정수부 7자리 + 소수부 2자리까지 저장SELECT SYSDATE, SYSTIMESTAMP, CURRENT_DATE FROM dual;
-- SYSDATE/SYSTIMESTAMP: DB 서버 시각, CURRENT_DATE: 세션 시간대 시각포맷 코드는 TO_CHAR, TO_DATE에 같이 씁니다.
| 코드 | 의미 | 코드 | 의미 |
|---|---|---|---|
YYYY |
4자리 연도 | HH24 |
24시간 시 |
MM |
월(01~12) | MI |
분 |
DD |
일(01~31) | SS |
초 |
DAY |
요일 전체(한글) | DY |
요일 축약 |
IW |
ISO 주차 | Q |
분기 |
SELECT TO_CHAR(hiredate, 'YYYY-MM-DD DY') FROM emp WHERE empno = 7839;
-- → 1981-11-17 화
SELECT TO_DATE('20240315', 'YYYYMMDD') FROM dual;
-- 날짜 산술: 정수는 일 단위, 1/24는 1시간
SELECT hiredate + 1 AS 다음날, SYSDATE + 1/24 AS 한시간후 FROM emp WHERE empno=7839;
SELECT ADD_MONTHS(hiredate, 3) FROM emp WHERE empno = 7839; -- 3개월 후
SELECT MONTHS_BETWEEN(SYSDATE, hiredate) FROM emp WHERE empno = 7839;
SELECT LAST_DAY(hiredate) FROM emp WHERE empno = 7839; -- 그 달 말일
SELECT NEXT_DAY(hiredate, '월요일') FROM emp WHERE empno = 7839;
SELECT TRUNC(hiredate, 'MM') FROM emp WHERE empno = 7839; -- 그 달 1일
SELECT EXTRACT(YEAR FROM hiredate) FROM emp WHERE empno = 7839;
SELECT hiredate + INTERVAL '1' MONTH FROM emp WHERE empno = 7839;
-- 월별 집계 패턴
SELECT TRUNC(hiredate, 'MM') AS 입사월, COUNT(*)
FROM emp
GROUP BY TRUNC(hiredate, 'MM');기간 검색은 컬럼에 함수를 씌우지 말고 범위로 비교합니다.
-- 나쁜 예: hiredate에 TRUNC를 씌우면 인덱스를 못 탄다
SELECT * FROM emp WHERE TRUNC(hiredate) = TO_DATE('1981-11-17', 'YYYY-MM-DD');
-- 좋은 예: 컬럼은 그대로, 범위로 비교
SELECT * FROM emp
WHERE hiredate >= TO_DATE('1981-11-17', 'YYYY-MM-DD')
AND hiredate < TO_DATE('1981-11-18', 'YYYY-MM-DD');SELECT NVL(mgr, 0) FROM emp WHERE empno = 7839; -- → 0
SELECT NVL2(mgr, '있음', '없음') FROM emp WHERE empno = 7839; -- → 없음
SELECT COALESCE(mgr, deptno, 0) FROM emp; -- 첫 NOT NULL 값
SELECT NULLIF(sal, 0) FROM emp; -- 같으면 NULL
-- DECODE: 동등 비교만, CASE: 범위·조건식 가능(검색 CASE)
SELECT DECODE(deptno, 10, '회계', 20, '연구', '기타') FROM emp;
SELECT CASE WHEN sal >= 3000 THEN '고'
WHEN sal >= 1500 THEN '중'
ELSE '저' END AS 급여등급
FROM emp;
-- NULL 비교 규칙: = NULL은 항상 거짓, IS NULL만 참
SELECT * FROM emp WHERE mgr = NULL; -- → 0행, 항상 거짓
SELECT * FROM emp WHERE mgr IS NULL; -- → KING 1행
-- NOT IN과 NULL 함정: 목록에 NULL이 있으면 전체가 알 수 없음(0행)
SELECT * FROM emp WHERE deptno NOT IN (SELECT deptno FROM dept WHERE dname = 'X');
-- 서브쿼리 결과에 NULL이 하나라도 있으면 결과가 통째로 사라진다-- 스칼라 서브쿼리: SELECT 절에서 값 하나
SELECT ename, (SELECT dname FROM dept d WHERE d.deptno = e.deptno) AS dname
FROM emp e;
-- 인라인 뷰: FROM 절의 서브쿼리
SELECT * FROM (SELECT deptno, AVG(sal) avg_sal FROM emp GROUP BY deptno) WHERE avg_sal > 1500;
-- EXISTS vs IN: EXISTS는 존재 여부만 확인, 대용량 상관 서브쿼리에 유리
SELECT * FROM emp e WHERE EXISTS (SELECT 1 FROM dept d WHERE d.deptno = e.deptno);
SELECT * FROM emp e WHERE e.deptno IN (SELECT deptno FROM dept);
-- WITH(CTE): 복잡한 쿼리를 이름 붙여 재사용
WITH dept_avg AS (
SELECT deptno, AVG(sal) avg_sal FROM emp GROUP BY deptno
)
SELECT e.ename, d.avg_sal FROM emp e JOIN dept_avg d ON e.deptno = d.deptno;
-- 재귀 WITH: 조직도처럼 스스로를 참조하는 계층 조회
WITH org (empno, ename, mgr, lvl) AS (
SELECT empno, ename, mgr, 1 FROM emp WHERE mgr IS NULL
UNION ALL
SELECT e.empno, e.ename, e.mgr, o.lvl + 1
FROM emp e JOIN org o ON e.mgr = o.empno
)
SELECT * FROM org ORDER SIBLINGS BY ename;
-- CONNECT BY: Oracle 전통 계층 조회 문법
SELECT LEVEL, ename, SYS_CONNECT_BY_PATH(ename, '/') AS 경로
FROM emp
START WITH mgr IS NULL
CONNECT BY PRIOR empno = mgr
ORDER SIBLINGS BY ename;UPDATE emp SET sal = sal + 100 WHERE empno = 7900;
SAVEPOINT sp1;
UPDATE emp SET sal = sal + 100 WHERE empno = 7654;
ROLLBACK TO sp1; -- sp1 이후 변경만 취소
COMMIT;
-- 다른 트랜잭션이 끝날 때까지 대기 없이 즉시 실패/건너뛰기
SELECT * FROM emp WHERE empno = 7900 FOR UPDATE NOWAIT;
SELECT * FROM emp WHERE deptno = 30 FOR UPDATE SKIP LOCKED;
SELECT * FROM emp WHERE empno = 7900 FOR UPDATE WAIT 3;
-- 낙관적 잠금: version 컬럼으로 동시 수정 감지
UPDATE emp SET sal = sal + 100, version = version + 1
WHERE empno = 7900 AND version = 3;
-- → 영향받은 행 수가 0이면 다른 사용자가 먼저 수정한 것데드락은 두 트랜잭션이 서로 다른 순서로 행을 잠글 때 발생합니다. 갱신 순서를 통일하면 예방할 수 있습니다. JDBC는 기본이 autocommit이므로 트랜잭션 단위로 묶으려면 setAutoCommit(false)가 필요합니다.
EXPLAIN PLAN FOR
SELECT * FROM emp WHERE deptno = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- → PLAN_TABLE에 저장된 실행 계획을 트리 형태로 출력인덱스를 못 타는 대표 패턴입니다.
| 패턴 | 예시 | 문제 |
|---|---|---|
| 컬럼 가공 | WHERE TRUNC(hiredate)=... |
컬럼에 함수, 인덱스 무효 |
| 암시적 형변환 | WHERE empno = '7900' |
문자를 숫자로 변환, 무효 |
앞쪽 %LIKE |
LIKE '%SMITH' |
접두 검색만 인덱스 유효 |
OR 조건 |
deptno=10 OR sal>3000 |
조건별 인덱스 각각 필요 |
NVL(col) |
WHERE NVL(mgr,0)=0 |
컬럼에 함수, 인덱스 무효 |
바인드 변수(#{})를 쓰면 값이 달라져도 같은 실행 계획을 재사용해서 파싱 비용을 줄입니다. 리터럴을 직접 쓰면 매번 새로 파싱하고 캐시도 낭비됩니다. 힌트(/*+ INDEX(e idx) */, /*+ FULL */)는 옵티마이저가 잘못 판단할 때만 쓰는 최후 수단입니다. 통계 정보가 오래되면 옵티마이저가 실행 계획을 잘못 고르므로 주기적으로 갱신해야 합니다.
SELECT table_name FROM user_tables;
SELECT column_name, data_type, nullable FROM user_tab_columns WHERE table_name = 'EMP';
SELECT index_name, uniqueness FROM user_indexes WHERE table_name = 'EMP';
SELECT index_name, column_name FROM user_ind_columns WHERE table_name = 'EMP';
SELECT constraint_name, constraint_type FROM user_constraints WHERE table_name = 'EMP';
SELECT sequence_name, last_number FROM user_sequences;
SELECT text FROM user_source WHERE name = 'MY_PROC' ORDER BY line;v$session은 조회 권한이 별도로 필요할 수 있으니 DBA에게 권한을 요청합니다.
MyBatis에서 자주 헷갈리는 부분을 정리합니다.
#{}는 바인드 변수로 값이 치환되고, ${}는 SQL 문자열 그대로 삽입됩니다. ${}는 정렬 컬럼처럼 화이트리스트로 검증된 값에만 씁니다.<foreach>로 만든 IN 절은 1000개 제한이 있으므로 리스트가 크면 여러 조각으로 나눠 실행합니다.<selectKey>로 INSERT 전에 시퀀스 NEXTVAL을 뽑아 파라미터에 채웁니다.jdbcType=VARCHAR처럼 타입을 명시하면 파라미터가 null일 때도 바인딩 오류가 나지 않습니다.java.time.LocalDateTime으로 매핑하는 것이 java.util.Date보다 안전합니다.useGeneratedKeys="true"는 Oracle에서 컬럼이 IDENTITY일 때만 동작하고, 시퀀스 기반 PK에는 selectKey를 씁니다.