본문 바로가기

oracle SQL

2024.12.12 12일차 수업

■ 테이블 복제 (CTAS 용어 꼭 알기!)
   - 테이블 구조, 행(데이터), 제약 조건 중에 NOT NULL 복제된다.
 
ex 1)

drop table hr.emp purge;

create table hr.emp
as
select * from hr.employees;

select * from hr.emp;

출력

▶ hr.employees에 있는 사원정보를 emp 테이블에 이관하는 작업이다.
 
♣ group by절에는 서브쿼리를 쓰지 않는다.
 
ex 2)

drop table hr.emp purge;

create table hr.emp
as
select employee_id, last_name||' '|| first_name name, salary*12 sal
from hr.employees;

select * from hr.emp;

출력

 
ex 3)

drop table hr.emp purge;

create table hr.emp
as
select * 
from hr.employees
where 1 = 2;  -- 테이블 구조만 복제, 행은 복제 되지 않는다.

select * from hr.emp;

출력

 
■ insert subquery (데이터 이관 작업 시)
  - 테이블에 데이터가 없고 뼈대만 있을 때 이관 작업을 통해 데이터가 다 들어있는 테이블을 만들 수 있다.
  - 추출(select밖에 못함)과 입력(insert)을 한다.
  - ETL 업무이기도 하다.
 
ex 1)

insert into hr.emp
select * from hr.employees;

select * from hr.emp;

commit;

출력

▶ 뼈대만 있었던 emp 테이블에 hr.employees 테이블에 있는 데이터들이 이관된 걸 볼 수 있다.
 
ex 2) 

create table hr.test
as
select employee_id, last_name, salary, department_id
from hr.employees
where department_id in(10,20,30);

desc hr.test; -- 출력 1

select * from hr.test; - -출력 2

출력 1
출력

▶ employees테이블에 있는 사원ID가 10,20,30 인 사원들의 employee_id, last_name, salary, department_id 뽑아서 hr.test 테이블에 복제하는 작업을 한 것이다.
 
ex 4) 50번 사원도 넣고 싶을 경우

insert into hr.test
select employee_id, last_name, salary, department_id
from hr.employees
where department_id = 50;

출력

▶ 꼭 타입이 같아야 한다.
 
# rollback을 했을 경우

rollback;

select * from hr.test;

출력

▶ department_id가 10,20,30인 데이터만 남게 된다.
 
ex 5) 열 별칭을 이용해서 테이블을 만들 경우

drop table hr.test purge;

create table hr.test
as
select employee_id id, last_name name, salary sal, department_id dept_id
from hr.employees
where department_id in(10,20,30);

select * from hr.test;

출력

▶ 열 별칭을 이용해서 이관 작업을 할 수도 있다.
 
ex 6)

insert into hr.test(id, sal, dept_id)
select employee_id, salary, department_id 
from hr.employees
where department_id = 40;

출력

▶ 오류가 뜨는 경우이다.
▶ 테이블 구조를 확인해 보면

desc hr.test;

출력

▶ 제약 조건을 보면 name에 NOT NULL 조건이 있다. 이 말은 즉 insert를 할 때 꼭 넣어줘어야 한다. 하지만 name 데이터가 빠졌기 때문에 오류가 난 것이다.
 
#  출력을 하고 싶을 경우

insert into hr.test(id, name, sal)
as 
select employee_id, last_name, salary
from hr.employees
where department_id = 40;

rollback;

출력

▶ 결과는 나오지만 department_id가 40인 사원의 값이 null이 나온 걸 볼 수 있다. 그 이유는 위에서 CTAS 할 때 40인 값은 CTAS를 안 해줬기 때문에 null이 나온다.


select * from user_users;
select * from user_sys_privs; -- 내가 받은 시스템 권한 확인하는 법
select * from role_sys_privs; -- 내가 받은 role 권한 확인
select * from session_privs; -- 위에 2개를 한꺼번에 보고 싶을 때
select * from user_ts_quotas; -- user session 후 자신의 정보를 확인하고 싶을 경우

 

create table hr.mgr(
			id number(3),
			name varchar2(30),
			day date)
tablespace users;

select * from hr.mgr;

출력

▶ 아직 insert문에 데이터를 안 넣어줬기 때문에 비어있는 값이 나오는 것이다.
 
# 주의할 것

create table hr.test
tablespace users
as
select employee_id, last_name, salary, department_id
from hr.employees
where department_id in(10,20,30);

▶ 꼭 CTAS를 이용할 때 create문 밑에 tablespace users를 쓰는 습관을 가지는 게 좋다.


문제) employees 테이블에 있는 관리자 사원들의 employee_id, last_name, hire_date를 mgr 테이블에 데이터 이행작업을 수행하세요.

insert into hr.mgr
select employee_id, last_name, hire_date
from hr.employees e
where exists(select 'x'
                from hr.employees
                where manager_id = e.employee_id);
                
select * from hr.mgr;

commit;

출력

▶ hr.employees 테이블 안에 employee_id와 관리자 사원 id가 포함하면 employee_id, last_name, hire_date를 출력해 준다.


select * from hr.emp;
drop table hr.emp;

▶ 위부터 차례대로 실행하면 emp 테이블은 아까 CTAS 작업을 통해 employees 테이블의 복제가 되어있다. 하지만 drop을 통해 테이블을 삭제한 후 다시 emp 테이블을 조회해 보면

출력

▶테이블이 존재하지 않다는 오류가 나온다.
 
# 다시 emp 테이블 생성

create table hr.emp(
			id number(3),
            name varchar2(60),
            dept_id number,
            dept_name varchar2(30))
tablespace users;

desc hr.emp;

 

출력

이름, 주소 등의 길이가 변할 수 있는 값은 VARCHAR를 사용하는 것이 좋고, 

사번, 주민등록번호와 같이 길이가 일정한 데이터는 CHAR를 사용하는 게 좋다.

# emp 테이블 이관 작업

insert into hr.emp(id, name)
select employee_id, last_name||' '||first_name
from hr.employees;

select * from hr.emp;

commit;

출력

▶ employees에 테이블에 있는 employee_id, name을 가져오는데 name을 가져올 때 last_name과 first_name 사이에 공백을 둬서 데이터를 가져오고 emp에 CTAS 작업을 하는 SQL문이다.
▶ dept_id, dept_name에 null이 나오는 이유는 CTAS 작업 시 값을 넣어주지 않았기 때문이다.
 
# 특정 데이터만 보고 싶을 경우

select * from hr.emp where id = 100;

출력

 
■ update subquery
 
ex 1) 기존 방식
   - 100번 사원을 null값으로 바꿀 때

update hr.emp
set name = null
where id = 100;

commit;

select * from hr.emp;

출력

     # update subquery 이용

update hr.emp
set name = (select last_name||' '||first_name
			  from hr.employees
              where employee_id = 100)
where id = 100;

 
ex 2) 

select department_id
from hr.employees 
where department_id = 100;

update hr.emp
set dept_id = 90
where id = 100;

▶ department_id가 100인 사원을 뽑게 되면 여러 명이 나올 것이다.
▶ 밑에 처럼 update문을 통해 dept_id를 다 입력해줘야 한다면 100인 사원의 로우 수만큼 update문을 써줘야 하는 번거로움이 있다.
 
▶ 그래서 update subquery를 통해 간편하게 해결할 수 있다.

update hr.emp e
set dept_id = (select department_id
				from hr.employees
                where employee_id = e.id);
                
select * from hr.emp;

commit;

출력

▶ 사원들의 department_id에 값이 들어간 걸 볼 수 있다.
 
ex) 

update hr.emp e
set dept_name = (select department_name
				   from hr.departments
                   where department_id = e.dept_id);
                   
 select * from hr.emp;
 
 commit;

▶ 현재 데이터에 department_name은 값을 안 줬기 때문에 다 null로 되어있다.
▶ 하지만 update subquery를 통해 emp테이블과 department 테이블의 id로 연결해서 department_name을 넣어주는 방식이다.

출력

 
■ delete subquery

delete from hr.emp
where id in(select employee_id
			  from hr.employees
              where last_name like 'k%');
              
select * from hr.emp;

rollback;

▶ 먼저 employees 테이블에 있는 last_name이 k로 시작하는 employee_id를 다 추출한 것이 emp테이블 id와 일치한 것만 삭제하는 SQL문이다.

출력

▶ 일치한 데이터의 행이 삭제된다.
 
# job_id를 한 번이라도 바꾼 사람을 삭제하고 싶은 경우
  - correlated subquery를 이용한 delete문

select *
from hr.emp e
where exists(select 'x'
  from hr.job_history
  where employee_id = e.id);

출력

delete from hr.emp e
where exists(select 'x'
		  from hr.job_history
		  where employee_id = e.id);
          
rollback;

출력 1
출력 2

 
■ 다중 테이블 INSERT (9i)
   - source 테이블에서 데이터를 추출해서 여러 개의 target 테이블에 데이터로 로드(insert)하는 SQL문
   - ETL (Extraction(추출), Trasnsformation(변형), Loding(적재))
 
1. 무조건 insert all
  - 기존방식은 source에서 terget 쪽으로 부어내면서 수행하는데 이 과정에서 테이블을 최소 2번 이상 엑세스하기 때문에 성능적으로 좋지 않기 때문에 사용한다.
  - 모든 정보를 뽑고 싶을 땐 from절엔 *을 쓸 수 있지만 values()절에는 * 쓸 수 없고 뽑고 싶은 컬럼을 다 적어줘야 한다.
 
ex) 기존 방식

# sal_history 테이블 생성

create table hr.sal_history
tablespace users
as
select employee_id, hire_date, salary
from hr.employees
where 1 = 2;

select * from hr.sal_history;

-----------------------------
# mgr_history 테이블 생성

create table hr.mgr_history
tablespace users
as
select employee_id, manager_id, salary
from hr.employees
where 1 = 2;

select * from hr.mgr_history;

rollback;

sal_history 테이블 출력
mgr_history 테이블 출력

 
▶하지만 이 방법은 좋지 않다. 그 이유는 동일한 코드로 이관 작업을 하게 되면 같은 테이블을 2번 엑세스하기 때문에 성능적으로 안 좋다.
 
# 성능 개선 방법 (insert all 이용)

insert all
into hr.sal_history(employee_id, hire_date, salary) values(id, day, sal)
into hr.mgr_history(employee_id, manager_id, salary) values(id, mgr, sal)
select employee_id id, hire_date day, manager_id mgr, salary * 1.1 sal
from hr.employees;

select * from hr.mgr_history;
select * from hr.sal_history;
rollback;

sal_history 출력
mgr_history 출력

 
■ 조건 insert all
   - 한 조건이 참이어도 뒤에 조건을 체크하고 참이면 데이터가 참인 테이블에 다 들어간다.
 

create table hr.emp_history(id number, day date, sal number)
tablespace useres;

create table hr.emp_sal(id number, comm number(5,2), sal number)
tablespace users;
insert all
when day < to_date('2005-01-01', 'yyyy-mm-dd') and sal >= 5000
into hr.emp_history(id, day, sal) values(id, day, sal)
when comm is not null then
	into hr.emp_sal(id, comm, sal) values(id, comm, sal)
select employee_id id, hire_date day, salary sal, commission_pct comm
from hr.employees;

▶ insert all은 첫 번째 조건이 참이여도 다음 조건을 체크한다. 그래서 다음 조건도 참이면 참인 테이블에 데이터가 다 들어간다고 보면 된다.
▶ 예를 들어 입사한 날짜가 2003년도이고 commission을 받고 있다면 둘 다 참이기 때문에 2 테이블에 데이터가 들어간다.
 

select * from hr.emp_history;

출력

select * from hr.emp_sal;

출력

 
■ 조건 first insert
   - 하나라도 조건이 참이면 뒤에 조건은 체크 안 하고 데이터가 들어가고 넘어간다.
 
ex) 3개의 테이블 생성

create table hr.sal_low(id number, name varchar2(30), sal number)
tablespace users;

create table hr.sal_mid(id number, name varchar2(30), sal number)
tablespace users;

create table hr.sal_high(id number, name varchar2(30), sal number)
tablespace users;

 
ex) first insert 이용

insert first
when sal < 5000 then
		into hr.sal_low(id, name, sal) values(id, name, sal)
when sal between 5000 and 10000 then
		into hr.sal_mid(id, name, sal) values(id, name, sal)
else
	into hr.sal_high(id, name, sal) values(id, name, sal)
select employee_id id, last_name name, salary sal
from hr.employees;

rollback;

▶ employees 테이블의 salary 값이 5000 이하이면 sal_low 테이블에 id, name, sal 해당 사원의 정보를 넣어준다.
 

select * from hr.sal_low;

출력 1

select * from hr.sal_mid;

출력 2

select * from hr.sal_high;

출력 3

▶ 출력 1~3 더하면 107개 잘 나오는 걸 볼 수 있다.
 
ex) 

create table hr.oltp_emp
tablespace users
as
select employee_id, last_name, salary, department_id
from hr.employees;

create table hr.dw_emp
tablespace users
as
select employee_id, last_name, salary, department_id
from hr.employees
where department_id = 20;
select * from hr.oltp_emp;

출력

select * from hr.dw_emp;

출력

 
 # 기존 테이블의 컬럼을 추가하는 방법
 
  1) 일단 테이블의 구조 확인 

desc hr.oltp_emp;

출력

  2) flag라는 컬럼을 추가

alter table hr.oltp_emp where flag char(1);

 
3) 컬럼이 잘 들어갔는지 테이블 구조 다시 확인

desc hr.oltp_emp;

출력

  4) 테이블을 조회해 보면

select * from hr.oltp_emp;

출력

▶ 보면 flag라는 컬럼이 추가가 되었고, 아직 값을 넣지 않았기 때문에 다 null이 나온다.
 
  ex)

select * from hr.oltp_emp where employee_id in (201,202);
select * from hr.dw_emp where employee_id in (201,202);

update hr.oltp_emp
set flag = 'd'
where employee_id = 202;

update hr.oltp_emp
set salary = 20000
where employee_id = 201;

commit;

▶ oltp_emp 테이블에 사원ID가 202번인 사원은 flag 값을 d로 바꿔준다.
▶ oltp_emp 테이블에 사원ID가 201번 사원의 salary값을 20000으로 바꿔준다.
 

출력


문제 1) oltp_emp에 있는 사원들 중에 dw_emp에 존재하는 사원 정보를 출력해 주세요.

select *
from hr.oltp_emp o
where exists(select 'x'
              from hr.dw_emp
              where employee_id = o.employee_id);

출력

 
문제 2) dw_emp에 있는 사원들 중에 oltp_emp에 존재하는 사원들의 급여를 oltp_emp에 급여를 기준으로 10% 인상해 주세요.

select *
from hr.dw_emp d
where exists (select 'x'
		   from hr.oltp_emp
		   where employee_id = d.employee_id);

출력

▶ 인상 전 값이다
 
  # 10% 인상 후

update hr.dw_emp d
set salary = (select salary * 1.1
		   from hr.oltp_emp
		   where employee_id = d.employee_id);
           
rollback;

출력

▶ 밑에 걸 실행하고 위에 걸 다시 조회하면 급여가 10프로 인상된 값이 나온다.
 
 
문제 3) dw_emp에 있는 사원 중에 oltp_emp에 존재하고 flag가 'd'인 사원의 정보를 출력해 주세요.

select *
from hr.dw_emp d
where exists(select 'x'
              from hr.oltp_emp
              where employee_id = d.employee_id
              and flag = 'd');

출력

 
문제 4) oltp_emp에 있는 데이터 중에 dw_emp에 없는 데이터를 dw_emp에 입력해 주세요.
   
    1. oltp_emp에 있는 데이터 중에 dw_emp에 없는 데이터를 먼저 뽑는다.

select *
from hr.oltp_emp o
where not exists(select 'x'
                  from hr.dw_emp
                  where employee_id = o.employee_id);

출력

▶ 2개를 제외한 105개가 출력이 된다.
 

insert into hr.dw_emp(employee_id, last_name, salary, department_id)
select employee_id, last_name, salary, department_id
from hr.oltp_emp o
where not exists(select 'x'
                  from hr.dw_emp
                  where employee_id = o.employee_id);

rollback;

출력


■ merge
  - insert, update, delete문을 한꺼번에 수행하는 SQL문
  - 성능 관점으로도 좋다.
 
ex)

select * from hr.dw_emp; -- target table (실제 update, delete, insert가 되는 곳)
select * from hr.oltp_emp; -- source table
merge into hr.dw_emp d
using hr.oltp_emp o -- 원하는 결과를 조회하는 곳
on (d.employee_id = o.employee_id) -- 조인조건 술어 (존재여부)
when matched then  -- 키 값이 일치가 되면
	update set
		    d.salary = o.salary * 1.1
	delete where o.flag = 'd'
when not matched then
		insert(d.employee_id, d.last_name, d.salary, d.department_id)
		values(o.employee_id, o.last_name, o.salary, o.department_id);

▶ merge도 마찬가지로 트랜젝션이 발생하기 때문에 항상 마지막에 commit 할지 rollback 할지 써줘야 한다.

'oracle SQL' 카테고리의 다른 글

2024.12.17 14일차 수업  (0) 2024.12.17
2024.12.16 13일차 수업  (0) 2024.12.16
2024.12.11 11일차 수업  (0) 2024.12.11
2024.12.10 10일차 수업  (2) 2024.12.10
2024.12.09 9일차 수업  (2) 2024.12.09