-- ALL 구문
SELECT column_name
FROM table_name
WHERE column_name operator ALL (subquery);
-- ANY 구문
SELECT column_name
FROM table_name
WHERE column_name operator ANY (subquery);
비교 연산자(operator) 종류:
(큼)
= (크거나 같음)
ALL 예시
SELECT ProductName
FROM Products
WHERE Price > ALL (
SELECT Price
FROM Products
WHERE CategoryID = 2
);
이 쿼리는 카테고리 2의 모든 제품보다 가격이 비싼 제품명을 조회함
ANY 예시
SELECT ProductName
FROM Products
WHERE ProductID = ANY (
SELECT ProductID
FROM OrderDetails
WHERE Quantity = 10
);
이 쿼리는 주문 수량이 10인 제품 중 하나라도 해당하는 제품명을 조회함
| 특징 | ALL | ANY |
|---|---|---|
| 조건 충족 | 모든 값 만족 | 하나라도 만족 |
| 주요 용도 | 최대값 비교 | 최소값 비교 |
| 결과 범위 | 더 제한적 | 더 포괄적 |
CUBE는 SQL에서 다차원적인 소계를 계산할 수 있는 그룹 함수로, GROUP BY 절에서 사용됨. 결합 가능한 모든 값에 대하여 다차원 집계를 생성하며, 표시된 인수들에 대한 계층별 집계를 구할 수 있음.
SELECT column1, column2, aggregate_function
FROM table_name
GROUP BY CUBE(column1, column2);
데이터 출력 형태
ROLLUP과의 차이점
SELECT department, position, SUM(salary)
FROM employees
GROUP BY CUBE(department, position);
결과 예시
| department | position | sum(salary) |
|------------|----------|-------------|
| IT | Manager | 50000 |
| IT | NULL | 80000 |
| NULL | Manager | 120000 |
| NULL | NULL | 200000 |
SELECT 문의 구조
LAST_VALUE 함수
LAST_VALUE(컬럼명) OVER (
PARTITION BY 그룹화할_컬럼
ORDER BY 정렬할_컬럼
RANGE BETWEEN start_point AND end_point
)
PARTITION BY
RANGE BETWEEN 구문
| EMPNO | ENAME | JOB | SAL | C1 |
|---|---|---|---|---|
| 7369 | SMITH | CLERK | 800 | 1100 |
| 7876 | ADAMS | CLERK | 1100 | 1100 |
| 7566 | JONES | MANAGER | 2975 | 2975 |
| 7788 | SCOTT | ANALYST | 3000 | 3000 |
UNBOUNDED FOLLOWING은 현재 행부터 파티션의 마지막 행까지의 모든 행을 포함하는 범위를 지정하는 윈도우 프레임 옵션
ROWS BETWEEN start_point AND UNBOUNDED FOLLOWING
-- 또는
RANGE BETWEEN start_point AND UNBOUNDED FOLLOWING
-- 현재 행부터 마지막까지의 최소값 찾기
SELECT Year, DepartmentID, Revenue,
MIN(Revenue) OVER (
PARTITION BY DepartmentID
ORDER BY [YEAR]
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) as MinRevenueBeyond
FROM REVENUE;
UNBOUNDED PRECEDING
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
UNBOUNDED FOLLOWING
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
-- UNBOUNDED PRECEDING 예시
SELECT empno, ename, sal,
SUM(sal) OVER (
ORDER BY sal
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as running_total
FROM emp;
-- UNBOUNDED FOLLOWING 예시
SELECT empno, ename, sal,
MAX(sal) OVER (
ORDER BY sal
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) as max_remaining
FROM emp;
| 옵션 | 범위 | 주요 용도 |
|---|---|---|
| UNBOUNDED PRECEDING | 처음 ~ 현재 | 누적 계산 |
| UNBOUNDED FOLLOWING | 현재 ~ 끝 | 역방향 계산 |
| BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING | 처음 ~ 끝 | 전체 범위 계산 |
BETWEEN 포함 구문
OVER (
ORDER BY column_name
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
BETWEEN 없는 구문
OVER (
ORDER BY column_name
RANGE UNBOUNDED PRECEDING
)
| 구문 | 의미 | 사용 시점 |
|---|---|---|
| RANGE BETWEEN | 명시적 범위 지정 | 상세한 범위 필요시 |
| RANGE만 사용 | 기본값 사용 | 간단한 누적 계산시 |
-- BETWEEN 사용
SELECT sal,
SUM(sal) OVER (
ORDER BY sal
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as sum1
-- BETWEEN 생략
SELECT sal,
SUM(sal) OVER (
ORDER BY sal
RANGE UNBOUNDED PRECEDING
) as sum2
두 쿼리는 동일한 결과를 반환함
WHERE EXISTS (
SELECT 1
FROM EMP X
WHERE X.DEPTNO
AND ROWNUM = 1
);
EXISTS 연산자
ROWNUM = 1
-- 부서가 있는 직원만 조회
SELECT * FROM EMPLOYEE e
WHERE EXISTS (
SELECT 1
FROM DEPARTMENT d
WHERE d.DEPT_ID = e.DEPT_ID
AND ROWNUM = 1
);
SYS_CONNECT_BY_PATH
SUBSTR
SELECT ... FROM table_name
START WITH 시작조건
CONNECT BY [NOCYCLE] 연결조건
START WITH
CONNECT BY
-- 입력 데이터 예시
ENAME MGR
KING null
JONES 7839
SCOTT 7566
-- 결과 예시
PATH
SCOTT>JONES>KING
SELECT REGEXP_SUBSTR('ABBBC', 'AB{1,3}') AS C1 FROM DUAL;
정규표현식 패턴 'AB{1,3}'
-- B가 정확히 2번 반복
'AB{2}' -- 'ABB' 매칭
-- B가 1번 이상 반복
'AB+' -- 'ABBB' 매칭
-- B가 0번 이상 반복
'AB*' -- 'A', 'AB', 'ABB' 등 매칭
구성요소
-- 매칭되는 이름 예시
'ALLEN' -- 'LL' 때문에 매칭됨
'SCOTT' -- 'TT' 때문에 매칭됨
'JAMES' -- 매칭 안됨 (중복 문자 없음)
-- 다른 유형의 패턴들
'(.)(.)\\2\\1' -- ABBA 형태 찾기
'(.).*\\1' -- 같은 문자가 어디든 있는 경우
'([A-Z])\\1' -- 대문자가 연속으로 반복
-- 연속된 같은 문자가 있는 모든 이름 찾기
SELECT ename
FROM emp
WHERE REGEXP_LIKE(ename, '(.)\1');
-- 3번 연속된 같은 문자 찾기
SELECT ename
FROM emp
WHERE REGEXP_LIKE(ename, '(.)\1\1');
| ENAME | 매칭되는 부분 | 설명 |
|---|---|---|
| ALLEN | LL | L이 연속 2번 |
| SCOTT | TT | T가 연속 2번 |
| MILLER | LL | L이 연속 2번 |
INSERT FIRST
WHEN C1 >= 2 THEN
INTO T1
WHEN C1 >= 3 THEN
INTO T2
ELSE
INTO T3
SELECT C1 FROM T4;
동작 방식
| C1 값 | 실행되는 INSERT | 설명 |
|-------|----------------|------|
| 1 | T3 | 모든 조건 불만족, ELSE 실행 |
| 2 | T1 | 첫 번째 조건 만족 |
| 3 | T1 | C1>=3이지만 첫 조건에서 이미 처리 |
| 4 | T1 | 마찬가지로 첫 조건에서 처리 |
-- INSERT FIRST: 첫 조건만 실행
INSERT FIRST
WHEN sal >= 5000 THEN INTO high_salary
WHEN sal >= 3000 THEN INTO mid_salary
SELECT * FROM employees;
-- INSERT ALL: 모든 조건 실행
INSERT ALL
WHEN sal >= 5000 THEN INTO high_salary
WHEN sal >= 3000 THEN INTO mid_salary
SELECT * FROM employees;
급여가 6000인 경우:
DELETE FROM T
WHERE ROWID IN (
SELECT ROWID
FROM (
SELECT ROWID,
ROW_NUMBER() OVER (
PARTITION BY C1, C2, C3
ORDER BY ROWID
) AS RN
FROM T1
)
WHERE RN > 1
);
ROW_NUMBER()
실행 결과 예시
| ROWID | RN | 결과 |
|---|---|---|
| 1 | 1 | 유지 |
| 2 | 2 | 삭제 |
| 3 | 1 | 유지 |
| 4 | 2 | 삭제 |
| 5 | 1 | 유지 |
| 6 | 2 | 삭제 |
| 7 | 3 | 삭제 |
-- ROWID 조회
SELECT ROWID, last_name
FROM employees
WHERE department_id = 20;
-- ROWID로 특정 행 접근
SELECT * FROM employees
WHERE ROWID = 'AAASd4AABAAAAX3AAA';
ROW_NUMBER()는 결과 집합의 각 행에 순차적인 고유 번호를 부여하는 윈도우 함수입니다. 파티션별로 정렬된 순서에 따라 각 행에 고유한 순번을 매깁니다.
파티션이 없는 경우
SELECT ROW_NUMBER() OVER (ORDER BY salary DESC) as rank,
employee_name,
salary
FROM employees;
파티션이 있는 경우
SELECT ROW_NUMBER() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) as dept_rank,
department_id,
employee_name,
salary
FROM employees;
| department_id | employee_name | salary | dept_rank |
|---|---|---|---|
| 10 | Smith | 5000 | 1 |
| 10 | Jones | 4000 | 2 |
| 20 | Brown | 6000 | 1 |
| 20 | Davis | 5500 | 2 |
ROW_NUMBER() OVER (
PARTITION BY C1, C2, C3
ORDER BY ROWID
)
C1, C2, C3
1, 1, 1 -> RN=1
1, 1, 1 -> RN=2 (중복 데이터)
1, 2, 1 -> RN=1 (새로운 파티션)
1, 2, 2 -> RN=1 (새로운 파티션)
1, 1, 3 -> RN=1 (새로운 파티션)
1, 1, 3 -> RN=2 (중복 데이터)
| C1 | C2 | C3 | ROW_NUMBER |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 1 | 1 | 1 | 2 |
| 1 | 2 | 1 | 1 |
| 1 | 2 | 2 | 1 |
| 1 | 1 | 3 | 1 |
| 1 | 1 | 3 | 2 |
-- 테이블 생성
CREATE TABLE T1 (
C1 VARCHAR2(8)
);
-- CHECK 제약조건 추가
ALTER TABLE T1 ADD CONSTRAINT T1_C1 CHECK (
C1 = TO_CHAR(
TO_DATE(C1, 'YYYYMMDD'),
'YYYYMMDD'
)
);
-- 성공 케이스
INSERT INTO T1 VALUES ('20240101'); -- 유효한 날짜
INSERT INTO T1 VALUES ('20231225'); -- 유효한 날짜
-- 실패 케이스
INSERT INTO T1 VALUES ('20241301'); -- 13월은 없음
INSERT INTO T1 VALUES ('20240231'); -- 2월 31일은 없음
INSERT INTO T1 VALUES ('ABCD1234'); -- 날짜 형식 아님
| 입력값 | 결과 | 이유 |
|---|---|---|
| 20240101 | 성공 | 유효한 날짜 |
| 20241301 | 실패 | 유효하지 않은 월 |
| ABCD1234 | 실패 | 날짜로 변환 불가 |
| 20240229 | 성공 | 윤년의 유효한 날짜 |
| 20230229 | 실패 | 평년의 존재하지 않는 날짜 |
TO_CHAR(
TO_DATE(C1, 'YYYYMMDD'), -- 1단계
'YYYYMMDD' -- 2단계
)
입력값 'C1' = '20240101' 경우:
TO_DATE(C1, 'YYYYMMDD')
TO_CHAR(날짜, 'YYYYMMDD')
-- 케이스 1: 유효한 날짜
'20240101' -> 2024/01/01 -> '20240101' (성공)
-- 케이스 2: 잘못된 날짜
'20241301' -> 변환 실패 (13월은 없음)
'ABCD1234' -> 변환 실패 (날짜 형식 아님)
| 단계 | 형식 지정자 역할 |
|---|---|
| TO_DATE | 입력 문자열을 어떻게 해석할지 |
| TO_CHAR | 날짜를 어떤 형식으로 출력할지 |
-- 다른 형식 사용 시
TO_CHAR(
TO_DATE('20240101', 'YYYYMMDD'),
'DD-MON-YYYY'
)
-- 결과: '01-JAN-2024'
이렇게 되면 원본 값과 달라져서 CHECK 제약조건 실패
| 권한 | 레벨 | 주요 용도 | 포함된 권한 |
|---|---|---|---|
| RESOURCE | 개발자용 롤 | 객체 생성/관리 | CREATE TABLE 등 다수 |
| CONNECT | 기본 접속용 롤 | DB 접속 | CREATE SESSION |
| CREATE SESSION | 시스템 권한 | 로그인 | 단일 권한 |
| CREATE USER | 관리자 권한 | 계정 관리 | 사용자 생성/관리 |
-- 일반 사용자용
GRANT CONNECT, RESOURCE TO username;
-- 관리자용
GRANT CONNECT, RESOURCE, CREATE USER TO username;
-- 최소 권한
GRANT CREATE SESSION TO username;
WITH ADMIN OPTION
WITH GRANT OPTION
| 구분 | WITH ADMIN OPTION | WITH GRANT OPTION |
|---|---|---|
| 적용 대상 | 시스템 권한, 롤 | 객체 권한 |
| 권한 범위 | 전체 시스템 레벨 | 특정 객체 레벨 |
| 권한 회수 | 상위 사용자 권한 회수 시 하위 사용자 권한 유지 | 상위 사용자 권한 회수 시 하위 사용자 권한도 함께 회수 |
ADMIN OPTION
GRANT OPTION
-- ADMIN OPTION 사용
GRANT CREATE SESSION TO user1 WITH ADMIN OPTION;
GRANT CREATE USER TO scott WITH ADMIN OPTION;
-- GRANT OPTION 사용
GRANT SELECT ON employees TO user1 WITH GRANT OPTION;
GRANT SELECT ON dept TO oracle WITH GRANT OPTION;