251022 TIL
2025. 10. 22. 17:33ㆍCourses/아이티윌 오라클 DBA 과정
Null 관련 함수
nullif(exp1, exp2)
- 두 표현식(인수)을 비교해서 같으면 null을 리턴하고 같지 않으면 exp1을 리턴하는 함수
PL/SQL에서 IF문
IF exp1 = exp2 THEN
return null;
ELSE
return exp1;
END IF;
SELECT
employee_id,
length(last_name),
length(first_name),
nullif(length(last_name), length(first_name))
FROM hr.employees;

lnnvl
- Logical Negation of NVL(NVL의 논리적 부정)
- 조건절 FALSE 또는 NULL을 조회하는 함수
- 아래의 경우
department_id is null조건이 없으면 부서가 null인 employee에 대해 누락 발생
SELECT *
FROM hr.employees
WHERE department_id <> 50
OR department_id is null;
- lnnvl 함수를 사용하면 위 쿼리와 동일한 결과
SELECT *
FROM hr.employees
WHERE LNNVL(department_id = 50);
조건 제어문
- SQL에서는 IF문을 사용해서 조건 제어문을 수행할 수 없음
- decode 함수, case 표현식
PL/SQL에서 IF문
IF 기준값 = 비교값1 THEN
참값1
ELSE IF 기준값 = 비교값2 THEN
참값2
ELSE IF 기준값 = 비교값3 THEN
참값3
ELSE
기본값
END IF;
decode 함수
- decode(기준값, 비교값1, 참값1, 비교값2, 참값2, 비교값3, 참값3, 기본값)
- decode 함수는 기준값과 비교값은 내부적으로 같다 비교연산자만 사용
- decode 함수 안에 decode 함수 중첩 가능
SELECT
employee_id,
salary,
job_id,
hire_date,
decode(job_id, 'IT_PROG', salary * 1.1,
'ST_CLERK', salary * 1.2,
'SA_REP', salary * 1.3,
salary) revised_salary,
decode(job_id, 'IT_PROG', decode(to_char(hire_date, 'yyyy'), '2005', salary * 1.15, salary * 1.1), salary) revised_salary2
FROM hr.employees;

case 표현식
- 기준값과 비교값에 대해서 모든 비교 연산자를 사용 가능(9i)
case 기준값
when 비교값1 then 참값1
when 비교값2 then 참값2
when 비교값3 then 참값3
else 기본값
end
- 기준값 (=, >, >=, <, <=, <>, !=, ^=, in, between, and, like) 비교값, 논리적(not, and, or)
SELECT
employee_id,
salary,
job_id,
hire_date,
case
when job_id = 'IT_PROG' then salary * 1.1
when job_id = 'ST_CLERK' then salary * 1.2
when job_id = 'SA_REP' then salary * 1.3
else salary
end revised_salary
FROM hr.employees;
- 같다 비교 연산은 아래처럼도 가능
SELECT
employee_id,
salary,
job_id,
hire_date,
case job_id
when 'IT_PROG' then salary * 1.1
when 'ST_CLERK' then salary * 1.2
when 'SA_REP' then salary * 1.3
else salary
end revised_salary
FROM hr.employees;
- 중첩 가능
SELECT
employee_id,
salary,
job_id,
hire_date,
case
when job_id = 'IT_PROG' then case when to_char(hire_date, 'yyyy') = '2005' then salary * 1.15 else salary * 1.1 end
when job_id = 'ST_CLERK' then salary * 1.2
when job_id = 'SA_REP' then salary * 1.3
else salary
end revised_salary
FROM hr.employees;
문제
-- [문제22] 사원들의 급여를 표기준 이용해서 출력해주세요.
/*
~4999 : low
5000~9999 : medium
10000~19999 : good
20000~ : excellent
*/
SELECT
employee_id,
salary,
case
when salary < 5000 then 'low'
when salary < 10000 then 'medium'
when salary < 20000 then 'good'
else 'excellent'
end grade
FROM hr.employees;

decode 함수, case 표현식에서 null check 방법
- decode 함수는 null keyword 이용해서 null check
- case 표현식은 is null 연산자를 이용해서 null check
SELECT
employee_id,
salary,
commission_pct,
salary * 12 + salary * 12 * nvl(commission_pct, 0) ann_sal_1,
nvl2(commission_pct, salary * 12 + salary * 12 * commission_pct, salary * 12) ann_sal2,
decode(commission_pct, null, salary * 12, salary * 12 + salary * 12 * commission_pct) ann_sal3,
case
when commission_pct is null then salary * 12
else salary * 12 + salary * 12 * commission_pct
end ann_sal4
FROM hr.employees;

그룹 함수
- 여러 행 당 하나의 결과를 반환하는 함수
- sum, avg, median, variance, staddev, max, min, count
- 그룹 함수에 입력값으로 수치형만 입력해야하는 함수 : sum, avg, median, variance, stddev
- 그룹 함수에 입력값으로 모든 데이터 유형이 가능한 함수 : max, min, count
- 그룹 함수는 null을 포함하지 않음
- 단 count(*) 는 null 포함한 행수를 구한다
count
- 행의 수를 구하는 함수
SELECT count(*) FROM hr.employees; -- 107, null 포함
SELECT count(department_id) FROM hr.employees; -- 106, null 포함 X
SELECT count(commission_pct) FROM hr.employees; -- 35, null 포함 X
- 중복을 제거해서 행수 구하기
SELECT count(distinct department_id) FROM hr.employees;
SELECT count(unique department_id) FROM hr.employees;
sum
- 합
SELECT sum(salary) FROM hr.employees;

avg
- 평균
SELECT avg(salary) FROM hr.employees;

SELECT avg(commission_pct) FROM hr.employees;
SELECT avg(nvl(commission_pct, 0)) FROM hr.employees;
- 두 쿼리 결과 다름 주의 필요
- 데이터가 1, null, 3 인 경우
- (1 + 3) / 2
- (1 + 0 + 3) / 3
- 데이터가 1, null, 3 인 경우
median
- 중앙값
SELECT median(salary) FROM hr.employees;

variance
- 분산
SELECT variance(salary) FROM hr.employees;

stddev
- 표준편차
SELECT stddev(salary) FROM hr.employees;

max
- 최대값
SELECT max(salary) FROM hr.employees;

min
- 최소값
SELECT min(salary) FROM hr.employees;

주의
SELECT
count(last_name),
count(hire_date),
max(last_name),
max(hire_date),
min(last_name),
min(hire_date)
FROM hr.employees;
- count, max, min만 문자형, 날짜형도 가능
- 나머지 함수는 숫자형만 가능
Group by절
- 테이블의 행을 작은 그룹으로 나눌 수 있는 절
SELECT department_id, sum(salary)
FROM hr.employees
GROUP BY department_id;
---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 12 |00:00:00.01 | 6 | | | |
| 1 | HASH GROUP BY | | 1 | 11 | 12 |00:00:00.01 | 6 | 941K| 941K| 1736K (0)|
| 2 | TABLE ACCESS FULL| EMPLOYEES | 1 | 107 | 107 |00:00:00.01 | 6 | | | |
---------------------------------------------------------------------------------------------------------------------

- 해시 group by를 사용하기 때문에 정렬되지 않음
그룹 함수 사용 시 주의점
- null 포함하지 않음(count(*) 제외)
- SELECT 절의 개별 컬럼(개별 포현식)은 모두 GROUP BY 절에 명시해야 함
SELECT department_id, job_id, sum(salary)
FROM hr.employees
GROUP BY department_id;

- 그룹 함수를 사용할 경우 group by 없이 개별 컬럼을 함께 사용하면 오류 발생
SELECT department_id, sum(salary)
FROM hr.employees;

- GROUP BY 절에는 컬럼의 별칭, 위치표기법 사용 불가(오류발생)
SELECT department_id, job_id job, sum(salary)
FROM hr.employees
GROUP BY department_id, job;

- WHERE 절에서는 그룹 함수의 결과를 제한 불가
- WHERE은 행을 제한하는 절, GROUP BY보다 먼저 처리됨
SELECT department_id, sum(salary)
FROM hr.employees
WHERE sum(salary) >= 15000
GROUP BY department_id;

- 그룹 함수 중첩 시 개별 컬럼 사용 불가
-- 오류 -> 서브 쿼리 활용해야 함
SELECT department_id, max(sum(salary))
FROM hr.employees
GROUP BY department_id;
SELECT max(sum(salary))
FROM hr.employees
GROUP BY department_id;

HAVING 절
- 그룹 함수의 결과를 제한하는 절
SELECT department_id, sum(salary)
FROM hr.employees
GROUP BY department_id
HAVING sum(salary) >= 15000;
주의
SELECT department_id, sum(salary)
FROM hr.employees
GROUP BY department_id
HAVING department_id in (10, 20);
----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 6 | | | |
|* 1 | FILTER | | 1 | | 2 |00:00:00.01 | 6 | | | |
| 2 | HASH GROUP BY | | 1 | 1 | 12 |00:00:00.01 | 6 | 941K| 941K| 1739K (0)|
| 3 | TABLE ACCESS FULL| EMPLOYEES | 1 | 107 | 107 |00:00:00.01 | 6 | | | |
----------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(("DEPARTMENT_ID"=10 OR "DEPARTMENT_ID"=20))
SELECT department_id, sum(salary)
FROM hr.employees
WHERE department_id in (10, 20)
GROUP BY department_id;
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 3 |
| 1 | SORT GROUP BY NOSORT | | 1 | 2 | 2 |00:00:00.01 | 3 |
| 2 | INLIST ITERATOR | | 1 | | 3 |00:00:00.01 | 3 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 2 | 3 | 3 |00:00:00.01 | 3 |
|* 4 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 2 | 3 | 3 |00:00:00.01 | 2 |
-------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access(("DEPARTMENT_ID"=10 OR "DEPARTMENT_ID"=20))
- 전체 데이터를 읽어 그룹핑 후 having 조건에 해당하는 데이터를 필터링하고 있음 → 비효율, 인덱스 사용 불가
- where절에서 먼저 행을 제한 후 그룹핑하면 더 적은 데이터를 읽고(인덱스 사용), 그룹핑 연산도 줄일 수 있음
문제
-- [문제23] 2008년도에 입사한 사원들의 job_id별 인원수를 구하고 인원수가 많은 순으로 출력하세요.
SELECT job_id, count(*) cnt
FROM hr.employees
WHERE hire_date between to_date('20080101', 'yyyymmdd') and to_date('20090101', 'yyyymmdd') - 1/24/60/60
GROUP BY job_id
ORDER BY cnt desc;
-- [문제24] 년도별 입사 인원수를 출력해주세요.
SELECT to_char(hire_date, 'yyyy') year, count(*) cnt
FROM hr.employees
GROUP BY to_char(hire_date, 'yyyy')
ORDER BY year;
-- [문제25] 년도별 입사 인원수를 아래 화면과 같이 출력해주세요.
SELECT
count(*) TOTAL,
count(case when to_char(hire_date, 'yyyy') = '2001' then 1 end) "2001년",
count(case when to_char(hire_date, 'yyyy') = '2002' then 1 end) "2002년",
count(case when to_char(hire_date, 'yyyy') = '2003' then 1 end) "2003년"
FROM hr.employees;
/*
TOTAL 2001년 2002년 2003년
---------- ---------- ---------- ----------
107 1 7 6
*/
- 25번 쿼리의 경우 데이터 개수만큼 count 함수를 3번씩 호출 → 각각의 경우를 먼저 집계한 후 합치는 방법이 더 효율적일 수 있음
JOIN(조인)
- 두 개 이상의 테이블에서 내가 원하는 데이터를 가지고 오는 방법
1. Cartesian Product(카티션 곱)
- 조인조건이 생략된 경우
- 조인조건이 잘못 생성된 경우
- 첫 번째 테이블 행의 수와 두 번째 테이블의 행 수가 곱해짐
SELECT *
FROM hr.employees, hr.departments;
- employees: 107건, departments: 27건 → 107 * 27 = 2889건 조회
2. equi join, inner join, simple join, 등가 조인
- 조인 키 값이 일치하는 데이터만 추출하는 조인
- 조인 컬럼에 테이블명을 지정하지 않으면, 어떤 테이블의 컬럼인지 알 수 없기 때문에 오류 발생(semantic check 시 컬럼 정의의 모호성 발생)
SELECT e.department_id, department_name
FROM hr.employees, hr.departments
WHERE department_id = department_id;

SELECT e.department_id, department_name
FROM hr.employees, hr.departments
WHERE hr.employees.department_id = hr.departments.department_id;
- 테이블명을 붙이면 너무 길고, 이로 인해 메모리 사용량이 많아질 수 있기 때문에 별칭 사용
SELECT e.department_id, d.department_name
FROM hr.employees e, hr.departments d
WHERE e.department_id = d.department_id;

- 조인 컬럼(department_id) 일치 조건으로 매칭되지 않는 컬럼은 결과에서 제외됨
- department_id가 null인 경우 빠짐
- n개 테이블을 조인하려면 조인조건술어는 n-1개의 조인조건술어를 작성해야 함
현재 사원들이 근무하는 도시 조회
SELECT e.employee_id, l.city
FROM hr.employees e, hr.departments d, hr.locations l
WHERE e.department_id = d.department_id
AND d.location_id = l.location_id;

- employees 테이블과 departments 테이블을 먼저 조인 후, 조인한 결과 집합과 locations 테이블을 조인
현재 사원들이 근무하는 국가 조회
SELECT e.employee_id, c.country_name
FROM hr.employees e, hr.departments d, hr.locations l, hr.countries c
WHERE e.department_id = d.department_id
AND d.location_id = l.location_id
AND l.country_id = c.country_id;

'Courses > 아이티윌 오라클 DBA 과정' 카테고리의 다른 글
| 251024 TIL (0) | 2025.10.24 |
|---|---|
| 251023 TIL (0) | 2025.10.23 |
| 251021 TIL (0) | 2025.10.21 |
| 251020 TIL (0) | 2025.10.20 |
| 251017 TIL (0) | 2025.10.17 |