1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
    [   계층형 쿼리    ]
    : 오라클 DBMS에서만 제공되는 강력한 기능이다.
    -> 계층적으로 출력되어야하는 경우에 굉장히 유용하다.
     
    ex)  스마트폰의 부품 구성도 정보가 저장되어 있는 테이블이 있다 하자
                스마트폰
                    |
    메인보드,  디스플레이,  배터리, 카메라
        |
CPU,  Memory
이런식으로 스마트폰아래에 메인보드, 디스플레이, 베터리, 카메라가 있고 메인보드 아래는 또 CPU, Memory가 있는 것처럼
계층형으로 데이터가 저장되어 있는 것이다.
 
데이터가 이런식으로 저장되어 있으려면 아래와 같이 보통 테이블에 저장되어 있을 것이다.
 
item_id, parent_id, product_name
100     null            스마트폰
101     100         메인보드
102     100         디스플레이
103     100         배터리
104     100         카메라
105     101         CPU
106     101         Memory
107     102         액정
 
이런식으로 parent_id에 부모에 해당하는 제품의 id를 할당해서 저장되어 있는데 이를 일반적으로는 계층형으로 출력할 수가 없다.
 
하지만, 오라클에서는 가능하다. start with, connect by 절을 이용한 계층형 쿼리를 이용해서!
 
start with (가장 상위의 root node의 조건) // 위의 예에서는 스마트폰은 parent_id가 null이다. -> 따라서, start with parent_id IS NULL로 표현할 수 있다.
connect by (계층을 이루는 조건) // -> connect by prior item_id = parent_id 이때, prior은 item_id의 부모값을 가지고 온다는 의미
-> 따라서 connect by parent_id = prior item_id 로 표현해도 된다.
 
*/
 
// ex) employees테이블은 사원의 employee_id(사번) 정보가 있고 상사의 사번이 manager_id에 저장되어 있다. 이를 이용해 계층형 쿼리를 출력해 보자.
select level, emp.employee_id, emp.first_name || emp.last_name 이름, jb.job_title 직책
from employees emp, jobs jb
where emp.job_id = jb.job_id
start with emp.parent_id IS NULL
connect by prior emp.employee_id = emp.parent_id

  [ 비밀번호 관리 ]


  : 데이터베이스에서 항상 기밀로 유지해야 한다.

    -> 비밀번호 관리 정책이 있어야 한다.

    

    - 사용자 암호를 관리하기 위해서는 profile을 사용한다.

      - profile 생성 구문 형식 )

          create profile 프로파일명 limit

            옵션1,

            옵션2, .....

    

      - 사용자에게 profile을 적용하는 방법 )

          - 기존유저일 경우 : ALTER USER

          - 새로추가하는 유저일 경우 : CREATE USER

          명령을 이용해서 profile을 적용할 수 있다.

    

    - [  암호 관리 유형  ] 

      

      - 1)비밀번호 입력 횟수 제한하는 방법 : 제한횟수 이상의 로그인 시도시 사용자 계정을 lock 시킴.

      

      - 2)비밀번호의 유효기간을 설정하는 방법    

          : 유효기간이 지나면 암호를 재설정하여 사용하도록 함.

      

      - 3)비밀번호의 재사용 금지 방법

          : 암호 재설정시 기존에 사용한 암호를 다시 사용할 수 없도록 하는 방법.


      - 4)복잡한 암호 설정 방법을 사용

          : 0자 이상, 특수문자 포함 등을 포함시켜 패스워드를 생성하게 하는 패스워드 설정 정책

          -> 오라클에서 제공하는 함수를 사용해서 복잡한 암호를 사용하도록 설정할 수 있다.

          -> (password_verify_function 함수) 

*/


-- 1)비밀번호 입력 횟수를 제한하는 profile을 생성

create profile profile_test limit

  FAILED_LOGIN_ATTEMPTS 3 -- 3번 시도 실패하면 

  PASSWORD_LOCK_TIME 5; -- 5일간 패스워드에 lock을 검


-- 만든 profile을 HR(기존에 있는 사용자) 계정에 적용하기

alter user hr profile profile_test;

-- 3번 로그인 실패시 5일 lock이 걸리게 됨


-- lock을 풀어주는 방법

alter user hr account unlock;


-- 다시 lock을 걸기

alter user hr account lock;


-- 사용자 계정에 lock이 걸렸는지 확인해 보기(dba_users에서...)

select username, account_status AS lock유무, to_char(lock_date,'yy/mm/dd hh24:mi') AS 잠긴날짜,

       profile AS 적용된profile

from dba_users

where username = 'HR';





-- 2)profile에 유효기간 설정하기 

create profile profile_test2 limit

  FAILED_LOGIN_ATTEMPTS 3

  PASSWORD_LOCK_TIME 5

  password_life_time 90 -- 90일동안 패스워드가 유효할 수 있도록 설정함

  password_grace_time 5; -- 90일 지난 다음 유예기간을 설정할 수 있음

  -- 이 유예기간 동안은 로그인할 때마다 경고메시지가 뜨게됨

  -- 이때의 5일은 90일 다음날부터 카운트되는 것이 아닌, 사용자가 90일이 지나고

  -- 최초로 로그인을 시도한 날부터 카운트 되게 됨.



-- dba가 계정을 만료시키는 방법)

  --  alter user 사용자ID password expire;

  

  

-- 3)비밀번호 재사용을 금지시키는 방법)

create profile profile_test3 limit

  password_reuse_time 30 -- 설정했던 패스워드를 30일간은 사용할 수 없음

  password_reuse_max unlimit; 

  

create profile profile_test4 limit

  password_reuse_time unlimit

  password_reuse_max 3; -- 지금 사용하는 비밀번호를 3번 바껴야 다시 해당 비밀번호를 사용할 수 있게 됨

  

  -- password_reuse_time과 password_reuse_max는 서로 상호 베타여서 하나가 값이 있으면 하나는 unlimit으로 되어야 한다.!!

  



  [ DB 사용자 관리(생성,변경,삭제) 및 사용자 정보 알아보기 ]


  - 사용자 계정 : 논리적인 의미

  

  - SYS계정

      : 데이터베이스내의 모든 권한을 갖는 최상위 레벨의 사용자

      

  - SYSTEM 계정

      : SYS 사용자로부터 "DBA권한"을 받은 사용자

      . 새로운 사용자를 추가, 변경, 삭제할 수 있다.

      . 사용자별 공간할당, 패스워드관리, 세션관리 등을 할 수 있다.

      . 데이터베이스 오브젝트(테이블, 뷰, 트리거...)는 "사용자별로" 생성된다.

      . 데이터베이스 오브젝트를 생성한 사용자를 그 오브젝트의 소유자(owner)라 한다.




--[ 사용자 생성 ]

--  : dba권한을 가진 사용자만 가능하다.

-- 형식 ) create user 사용자ID identified by 패스워드


--        해당 사용자가 생성하는 객체가 저장될 테이블 스페이스도 지정할 수 있다.


--        create user 사용자ID identified by 패스워드 default tablespace 테이블스페이스명

--        temporary tablespace temp /*정렬을 위한 temporary tablespace도 생성 가능*/

--        quota 20M on appl_data quota 10M on system; /*tablespace 용량 할당*/


-- 테이블스페이스를 지정하지 않으면, 기본적으로 system tablespace를 사용하게 된다.

create user user1 identified by user1

  default tablespace test_1;

  

-- 사용자 정보 검색 : dba_users에서 검색

select username from dba_users;

select * from dba_users;


/*

  [ 사용자 변경 ]

  - 패스워드 변경 >

      alter user 사용자명 identified by 바꿀비밀번호;

  

  - 사용자의 테이블스페이스 변경>

      alter user 사용자명 default tablespace 바꿀테이블스페이스명;

      

  - 사용가능 용량 변경

      alter user 사용자명 quota 10M on 테이블스페이스명; 

  



alter user user1 identified by 1234;

alter user user1 quota 10 on test_1;


-- 전체 사용자에 대한 정보를 검색할 때

  -- 테이블스페이스 정보, 패스워드, 아이디를 포함해 사용자의 정보를 볼 수 있다.

select * from dba_users;

-- 현재 로그인한 사용자에 대한 정보만을 검색할 때

select * from user_users;



  [ 사용자 제거 ]

  형식 ) 

        drop user 사용자ID;

        

        -- 해당사용자가 가지고 있는 객체(뷰,트리거 등)도 전부 삭제하는 방법(cascade)

          -- 잘 사용하지 않는다.(신중하게 해야한다. 대부분 위 방법으로 삭제한다.)

        drop user 사용자ID cascade;

*/

create user user2 identified by user2;

select * from dba_users;


-- 사용자 제거

drop user user2;


select username from dba_users where username = 'user2';


-- 현재 자신의 계정에 대한 정보를 검색할 때

select * from user_users;



  [ 사용자 공간 사용 정보 알아보는 방법 ]

  

    - 사용자 공간 정보 알아보기 위한 딕셔너리 : user_ts_quotas, dba_ts_quotas

*/

select * from USER_TS_QUOTAS;


select * from SYS.DBA_TS_QUOTAS;

-- MAX_BYTES 항목이 -1인 값의 의미는 최대 용량 제한이 없음을 의미(unlimited)





  [ 사용자의 세션 정보를 알아보는 딕셔너리 ]

  -> v$session

  현재 접속중인 사용자들의 세션 정보를 알 수 있다.


select sid, serial#, username, program from v$session;

-- program : 어떤 프로그램을 통해 접속했는지

-- username : 사용자ID


-- HR에 접속한 것의 세션을 끊어버리기

-- 세션을 중지시키기 : alter system kill session 'SID,SERIALNUM';

alter system kill session '93,275';

select sid, serial#, username, program from v$session;


/*

  [ 인덱스(Index)의 개념/종류/주의사항/활용,관리 ]

  

  1. 인덱스(Index)란???

    : 어떤 데이터가 HDD(하드디스크)의 어디에 있는지 위치 정보를 가진 주소록과 같은 개념.

    -> (데이터 - 위치주소(ROWID)) 쌍으로 저장하고 관리됨

    

    - 목적)

      : 빠르게 쿼리 검색을 하오기 위함

      

    데이터 위치 정보(주소) : ROWID -> 총 10Byte

*/

-- 데이터 튜플의 rowid를 검색해 보기

select rowid, empno, ename from scott.emp where empno = 7521;

-- 결과 : 

/*

  ROWID             EMPNO   ENAME

AAAE+3AAEAAAAFfAAC   7521    WARD

*/

-- [ ROWID의 구조 ] : AAAE+3AAEAAAAFfAAC

/*

  -AAAE+3 : 데이터 오브젝트의 번호

  -AAE : 파일 번호

  -AAAAFf : 블럭 번호

  -AAC : ROW(튜플) 번호

*/


/*

  2. [ 인덱스의 생성 원리 ]

  

    : 전체 테이블을 스캔(Table Full Scan) -> PGA내의 Sort Area에서 정렬(Sort) 공간 부족시 Temporary tablespace 이용해 정렬

                                        -> 정렬한 데이터를 기반으로 HDD Block에 기록

    -> 인덱스는 데이터가 ""정렬"" 되어 들어간다!!

*/


/*

  3. [ 인덱스 구조와 작동 원리(B-TREE 인덱스 기준) ]

  

    : 테이블(Table)과 인덱스(Index)의 비교

    - 테이블은 컬럼이 여러개, 데이터가 "정렬되지 않고" 입력된 순서대로 들어간다.

                            vs

    - 인덱스(Index)는 컬럼이 "Key컬럼(사용자가 인덱스를 지정하라고 지정한 컬럼)"과 "ROWID컬럼" 두개로 이루어져 있다.

      (오름차순, 내림차순으로 정렬 가능)

*/

select * from emp where empno = 7902; -- 를 찾을 때

/*

  데이터 파일의 블록이 10만개가 있다고 할 때 sql문을 수행한다면,

  1) 서버 프로세스가 구문파싱 과정을 마친 후 DB Buffer 캐시에 empno가 7902인 정보가 있는지를 먼저 확인한다.

  2) 해당 정보가 캐시에 없다면 디스크 파일에서 7902정보를 가진 블럭을 찾아서 DB Buffer 캐시로 가져온 뒤 해당 정보를 사용자에게 보여줌

  이 경우에

    - Index가 없는 경우 -> 7902정보가 디스크 어떤 블럭에 있는지 모름으로 10만개 전부 DB Buffer 캐시로 복사한 뒤 Full Scan으로 찾게 됨.

    - Index가 있는 경우 -> where절의 조건으로 준 컬럼이 Index의 Key로 생성되어 있는지 확인한 뒤, 인덱스에 먼저 가서 7902정보가

                          어떤 ROWID를 가지고 있는지 확인한 뒤 해당 ROWID에 있는 블럭만 찾아가서 db Buffer 캐시에 복사하게 됨.

*/


/*

  4. [ 인덱스의 종류 ]

    1) B-TREE 인덱스(Index)

        : OLTP(Online Transaction Processing : 실시간 트랜잭션 처리)

          -> 실시간으로 데이터 입력과 수정이 일어나는 환경에 많이 사용함.

        

    2) BITMAP 인덱스(Index)

        : OLAP(Online Analytical Processing : 온라인 분석 처리)

          -> 대량의 데이터를 한꺼번에 입력한 뒤 주로 분석이나 통계 정보를 출력할 때 많이 사용함.

          -> 데이터 값의 종류가 적고 동일한 데이터가 많을 경우에 많이 사용함

        

        (1) [ B-Tree 인덱스 ]

          B : binary, balance 의 약자

          

              ROOT block(branch block에 대한 정보)

               |

            Branch Block(Left Block에 대한 정보)

               |

              Leaf Block(실제 데이터들의 주소)

          

        (1-1) [ B-Tree 인덱스의 종류 ]

          (A) Unique Index  

              : 인덱스 안에 있는 컬럼Key값에 중복되는 데이터가 없다.(성능이 좋음)

              -> 마치 Unique 제약조건과 유사하다. 따라서, Unique 제약조건 사용시에도 자동으로 UNIQUE INDEX가 생성된다.

또한, 기본키를 생성해도 오라클은 자동으로 UNIQUE INDEX를 생성하게 되는데 이때 UNIQUE나 기본키 객체명과 동일하게 생성된다. 


              - 생성 방법)

                SQL > create unique index 인덱스명

                      on 테이블명(key로지정할컬럼명 1 ASC|DESC, 컬럼명2...);

                ASC : 오름차순 정렬(기본값)

                DESC : 내림차순 정렬

                

                EX)

                -- dept테이블과 같은 테이블 하나 생성

                SQL > create table dept2 as select * from dept;

               

                -- dname을 key로하는 unique index를 생성

                SQL > create unique index idx_dept2_dname on dept2(dname); 

                -- 튜플 하나 추가

                SQL > insert into dept2 values(50,'개발부','서울');

                -- 위의 추가한 튜플과 dname값이 일치하는 다른 튜플을 하나 추가할라하면 unique인덱스이기 때문에

                -- 에러가 발생한다.

                SQL > insert into dept2 values(60,'개발부','인천');

                -- 이미 들어가 있는 dname이기 때문!!

            

            (B) Non Unique Index 

                : 중복되는 데이터가 들어가야 하는 경움(key로 지정한 필드의 중복된 값이 들어갈 수 있다.)

                

                - 생성 문법)

                SQL > create index 인덱스명 on 테이블명(컬럼명1 ASC|DESC, 컬럼명2....);

                

                EX)

                   -- 테스트용 테이블 새로 생성

                   SQL > create table dept3 as select * from dept;

                

                   -- dname을 Key로하는 Non-Unique index 생성

                   SQL > create index idx_dept3 on dept3(dname);

                 

                   SQL > insert into dept3 values(50,'개발부','서울');

                 

                   -- 중복되는 dname을 가진 튜플이 삽입이 가능하다.

                   SQL > insert into dept3 values(60,'개발부','인천');


            (C) FBI 인덱스( Function Based Index ) : 함수기반 인덱스

              : - 인덱스는 where절에 오는 조건 컬럼이나 조인에 쓰이는 컬럼으로 만들어야 한다.

                - 인덱스를 사용하려면 where절의 조건을 절대로 다른 형태로 가공해서 사용하면 안된다.

                

                예를들자면)

                  where절의 조건이 sal + 100 > 200 이러한 조건일 경우

                  단순히 index컬럼을 sal로만 지정하게 되면 인덱스가 적용되지 않고 검색쿼리가 수행되게 됨

                  -> 따라서, 인덱스도 테이블명(sal+100)의 형태로 "함수기반 인덱스"로 사용해야 함

                  

                  EX)

                    SQL > create index idx_dept_fbi on emp(sal+100);

                    -- 이때, emp테이블에는 sal+100 컬럼은 없지만 인덱스를 만들 때 저 연산을 수행해서 인덱스를 만듬

                    

                    (주의 사항)

                    - 임시적인 해결책은 될 수 있어도 근본적인 처방은 아니다.

                    - sal + 100을 인덱스로 생성했는데 쿼리 조건이 변경되면 인덱스를 다시 생성해야 한다.

                    - FBI는 기존의 인덱스를 활용할 수 없다.(단점)

              

              (D) Descending Index : 내림차순으로 인덱스를 생성한다.

                    : 큰 값을 많이 조회하는 SQL에 생성하는 것이 좋다.

                    ex) 최근 날짜부터 조회, 회사 매출 조회

                    

                    SQL > create index idx_emp_sal on emp(sal desc);

                    

                    (주의사항)

                      : 하나의 메뉴에 오름차순과 내림차순을 한번에 조회할 경우

                      -> 오름차순, 내림차순 두 개의 인덱스를 만들면 DML의 성능에 악영향을 미침

                      -> 이때는, 힌트를 사용한다.(아래나 위에서부터 읽도록 할 수 있다.)

                

              (E) 결합 인덱스(Composite Index) 

                  : 인덱스 생성시에 두개 이상의 컬럼을 합쳐서 인덱스를 생성하는 인덱스

                  -> 주로 where 절의 조건이 되는 컬럼이 2개 이상으로 and로 연결되는 경우 사용된다.

                  -> 잘못 생성하게 되면 성능에 영향을 미칠 수 있다.

                    (컬럼의 순서에 따라 효율에 차이가 있다.) -> 보통, 자주 사용하는 컬럼을 앞에 위치시키는 것이 좋다.

                    

                    생성 방식)

                      SQL > create index 인덱스명 on 테이블명(컬럼명1, 컬럼명2);

                      

                    EX) (성별, 이름) 두개의 컬럼으로 인덱스를 생성하는 경우

                      SQL > create index idx_emp_composite on emp(gender,dname);

                      

                      -- 생성된 인덱스는 정렬되어 데이터를 저장시키게 되는데

                      -- 이때, 인덱스 생성시 기술한 컬럼순으로 정렬이 된다. 

                      -- 즉, gender(성별)을 기준으로 asc로 정렬한 뒤 같은 성별일 때

                      -- dname을 asc로 정렬해 인덱스를 생성하는 것

                      

                      -- 생성된 인덱스가 어떤 모습으로 저장되어 있는지 검색해 보자.

                      EX) emp테이블의 empno와 deptno 두개의 컬럼을 조건으로 하는 인덱스 생성

                          SQL > create table emp3 as select * from emp;

                          SQL > create index idx_emp3_composite on emp3(deptno,empno);

                          

                          -- 정렬된 상태로 저장되어있는 인덱스 모습을 보기

                          SQL > select * from emp3 where empno > 0 and deptno > '0';

                        


                          

                          -- 이때, 검색 쿼리가 deptno가 20이면서 empno가 7902인 사원정보를 검색한다면

                          SQL > select * from emp3 where deptno = 20 and empno = 7902;

                          

                          -- 인덱스 생성시 dept 컬럼을 앞에 기술했기 때문에 dept=20을 먼저 찾기위해

                          -- 4번 검사를 하고 이때 20이 나옴으로 empno 7369검사(1번) 다음 deptno 검사 1번

                          -- 다시 empno 검사(1번) 식으로해서 최종 찾는 데이터까지

                          -- 총 9번의 검사를 해 찾게된다.

                          

                          -- 이런식으로 검사가 이루어지기 때문에 주의할 사항이 생기는데

                          (deptno, empno)로 생성할지 (empno, deptno)로 생성할지에 따라 검사 효율이 다르게 나타날 수 있다.

                          

                          이때는 평균적인 검사 효율을 어느정도 계산을 해 바서 인덱스를 생성하는 것이 중요하다!!(신중히 생성하자)

       

       

        (2-1) [ BITMAP 인덱스의 종류 ]

            : 데이터 값의 종류가 적고 동일한 데이터가 많을 경우에 많이 사용된다.

            

            Bitmap Index를 생성하려면 데이터의 변경량이 적어야 하고, 값의 종류도 적은 곳이 좋다.

            일반적으로 OLAP환경에서 많이 생성하게 되는대

            

            Bitmap Index는 어떤 데이터가 어디에 있다는 지도정보(MAP)를 Bit로 표기하게 된다.

            데이터가 존재하는 곳은 1로 표시하고, 데이터가 없는 곳은 0으로 표기한다.

            정보를 찾을 때 1인 값만 찾게 된다!

            

            SQL > create bitmap index 인덱스명 on 테이블명(컬럼);

            

            bitmap index를 생성하면 성별 컬럼 값의 종류대로 map이 생성된다.

            남자 : 1 0 1 0 0 -> 남자데이터가 1,3번 튜플에 있다.

            여자 : 0 1 0 1 1 -> 여자데이터가 2,4,5 튜플에 있다.

            

            이때 문제되는게

            bitmap index사용하고 있는 상태에서 만약 컬럼 값이 새로 하나 더 생긴다면?

            기존의 Bitmap Index를 전부 수정해야한다.

            

            -> B-Tree Index는 관련 블럭만 벽여되지만 Bitmap Index는 모든 맵을 다 수정해야 한다는 문제점!

            -> Bitmap Index는 블럭 단위로 lock을 설정해서 같은 블럭에 들어있는 다른 데이터도 수정작업이 안되는 경우가

               종종 발생한다.

                       

*/


/*

  5. [ 인덱스 주의사항 ]

    

    - 인덱스를 사용하면 무조건 효율이 좋을까? NO

   

    -> [ 인덱스는 DML에 취약 ]

    근거)

      1) INSERT 작업의 경우

        : "index split"현상이 발생할 수 있다.

        - Index Split이란? : 인덱스의 Block들이 하나에서 두개로 나누어지는 현상

        -> 인덱스는 데이터가 순서대로 정렬되어 저장되게 되는데, 기존 블럭에 여유 공간이 없는 상황에서

           그 블럭에 새로운 데이터가 입력되어야 하는 경우

           오라클은 기존 블럭의 내용 중 일부를 새 블럭에다가 기록한 다음 기존 블럭에 빈 공간을 만들어서

           새로운 데이터를 추가하게 된다.

           --> 따라서, 성능면에서 매우 불리하다.

           a)Index Split은 새로운 블럭을 할당 받고 key를 옮기는 복잡한 작업을 수행

           b)Index Split이 이루어지는 동안 해당 블럭에 대해 키 값이 변경되면 안되므로 DML이 블로킹된다.

            enq:TX-index contention 대기 이벤트 발생(RAC-gc current split)

      

      2) DELETE 작업의 경우

        : 일반적인 테이블에서 데이터가 delete될 경우 해당 위치 데이터가 지워지고 그 공간을 사용 가능하다.

                        vs

          하지만, Index에서 데이터가 delete될 경우 -> "데이터는 지워지지 않고, 사용하지 않는다는 의미의 표시만 해두게 된다!"

            --> 즉, 테이블에 2만건의 데이터가 있었는데 1만건을 삭제해도 Index에는 데이터가 2만건이 존재한다는 말이된다.

            -> 인덱스를 사용해도 수행속도를 기대하기는 힘들다.

            

      3) UPDATE 작업의 경우

        : 인덱스에는 UPDATE란 작업이 존재하지 않기 때문에

          기존의 데이터를 DELETE한 다음 새로운 값의 데이터를 INSERT하는 두번의 과정으로 작업이 발생하는데

          따라서, 다른 DML작업보다 더 큰 부하를 주게 된다.

   


[ 최종적으로 인덱스 생성시 고려해야할 사항 ]


1 일반적으로 테이블 전체 로우 수의 15%이하의 데이터를 조회할 때 인덱스를 생성한다.


2 테이블 건수가 상당히 적다면 굳이 인덱스를 만들 필요가 없다. -> 테이블 건수가 적으면 인덱스를 경유하기보다 테이블 전체를 스캔하는 것이 더 빠르다.


3 인덱스 생성시 컬럼은 유일성 정도가 좋거나 범위가 넓은 값을 가진 컬럼을 지정하는 것이 좋다. (NULL값을 많이 갖는 컬럼은 피하는 것이 좋다.)


4 결합 인덱스 생성시에는 컬럼의 순서가 중요하다.

-> 보통, 자주 사용하는 컬럼을 앞에 지정한다.


5 테이블에 만들 수 있는 인덱스의 수는 제한이 없으나, 너무 많이 만들면 오히려 성능 부하가 발생한다.

why? -> 인덱스 생성을 해 놓으면 해당 테이블에 DML 작업(insert, delete, update)시 인덱스에도 수정작업이 동시에 발생하기 때문에 과도하게 많은 인덱스를 생성해 놓으면

오히려 성능 부하가 걸릴 수 있다.

-> 일반적으로, 테이블 당 최대 5개를 넘지 않는 것이 좋다.


6 데이터의 검색보다 수정, 삭제, 삽입 작업이 빈번한 테이블에는 인덱스를 생성하지 않는 것이 좋다.

-> 인덱스는 DML작업에는 성능이 좋지 않기 때문에 검색을 위주로 하는 테이블에 생성하는 것이 좋다.(위에서 언급한 성능 이슈들이 발생할 수 있다.)


7 인덱스 생성시 무엇보다 가장 중요한 점은 SQL 쿼리가 인덱스를 잘 활용할 수 있게끔 짜여져야 한다는 것이다.(쿼리를 잘 짜서 만들자!)       

*/

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


  [ 인덱스의 활용,관리의 예 ]

  
  1. [ 인덱스를 조회하기 위한 딕셔너리 ]
    - user_indexes, user_ind_columns
    - dba_indexes, dba_ind_columns


-- DEPT2 테이블에 적용된 인덱스 검색 방법(user_indexes 딕셔너리에서)
select table_name, index_name
from user_indexes
where table_name = 'DEPT2';

-- EMP 테이블에 적용된 인덱스 검색
select table_name, index_name
from user_indexes
where table_name = 'EMP';

-- [index를 rebuild하는 방법]
  -- 인덱스는 한번 만들어 놓으면 영구적으로 좋은 성능을 가질수 없기 때문에
  -- 항상 관리를 해주어야 한다. 그 방법이 rebuild 하는 것이다.
  
  -- 테스트용 테이블 생성
  create table test_rebuild(
    no number
  );
  -- 테스트용 데이터 1000개 넣기
  begin
    for i in 1..1000 loop
      insert into test_rebuild values(i);
    end loop;
  end;
  /
  commit;
  -- 들어간 데이터 확인
  select * from test_rebuild;
  
  -- 인덱스 생성
  create index idx_test
  on test_rebuild(no);
  
  -- 인덱스의 상태 조회
  analyze index idx_test validate structure; --인덱스 상태를 분석함
  
  -- 인덱스 분석 쿼리를 수행하면 index_stats 딕셔너리에 해당 결과가 반영되게 된다.
  -- 또한,  
  -- 테이블 데이터 삭제시 인덱스에는 삭제되지 않고 사용하지 않는다는 표시만 해 두는대
  -- 이때, 관리가 필요함 따라서, 인덱스에 삭제상태로 표시한 데이터가 몇개 있는지(DEL_LF_ROWS_LEN)를
  -- index_stats 에서 조회하는데
  -- 아래 구문은 삭제한데이터/총있는데이터 비율을 구한다.
  select (DEL_LF_ROWS_LEN / LF_ROWS_LEN) * 100 AS balance
  from index_stats;
  -- 0이 나온다면 좋은 상태임을 알 수 있다.
  
  -- 그럼, 데이터를 삭제해 보자.
  delete from test_rebuild where no between 1 and 400; -- 1 ~ 400까지 데이터 삭제
  -- 남은 데이터 개수 : 600개
  select count(*) from test_rebuild;
  
  -- 다시한번 인덱스 분석을 해 주어야 함 index_stats에 반영될 수 있도록...
  analyze index idx_test validate structure;
  
  -- 39.6~~~%가 나오게 됨 -> 40%정도가 인덱스의 균형이 좋지 않다는 것을 의미함.
  select (DEL_LF_ROWS / LF_ROWS) * 100 AS balance
  from index_stats;
  
  select * from index_stats;
  select DEL_LF_ROWS from index_stats; -- 400
  
  select LF_ROWS from index_stats; -- 1000
  
  -- 40%를 성능을 위해 수정하기 위해선 rebuild 작업이 필요하다.
  alter index idx_test rebuild; -- rebuild 명령으로 인덱스를 수정한다.
  
  -- 다시 분석해서 보면
  analyze index idx_test validate structure;
  select (DEL_LF_ROWS / LF_ROWS) * 100 AS balance from index_stats; -- 0이 나오게 됨
  
  -- 이처럼 인덱스는 rebuild를 통해 꾸준히 관리가 필요하다.
  
  
  -- [ 인덱스 활용 예 ]
  create table emp3(
    no number,
    name varchar2(10),
    salary number
  );
  
  insert into emp3 values(1, '강호동', 200);
  insert into emp3 values(2, '이경규', 300);
  insert into emp3 values(3, '이경실', 100);
  insert into emp3 values(4, '유재석', 400);
  insert into emp3 values(5, '홍길동', 150);
  insert into emp3 values(6, '홍길자', 250);
  
  select * from emp3;
  
  -- index 생성
  create index idx_name
  on emp3(name);
  
  -- where절 조건으로 인덱스 컬럼으로 지정한 name을 쓰지 않았음으로 인덱스를 거치지 않고
  -- 결과가 나오기 때문에 정렬이 되어 있지 않고 출력되게 된다.
  select name from emp3;
  
  -- where절에 name조건을 주었기 때문에 인덱스를 거쳐 정렬된 상태로 나오게됨
  select name from emp3 where name > '0'; -- 이처럼, order by 효과를 대신할 수 있음
  
  -- order by시에도 정렬을 위한 수행 시간이 필요한대 인덱스를 활용하면 이러한 시간을 
  -- 줄일 수 있다.
  
  -- [인덱스를 활용해서 최소값을 구해보자]
  
  -- 먼저 index를 쓰지 않고 찾아오는 방식
  select min(name) from emp3; -- 내부적으로 정렬을 한번해서 찾아오게 됨 0.04초 걸렷음.
  
  -- 인덱스를 쓴 경우
  select name from emp3 where name > '0' and rownum = 1; -- 정렬이 발생하지 않고
  -- 가져올 수 있기 때문에 상당히 빠름 -> 0초로 찍힘
  
  -- 최대값도 해보자.
  select max(name) from emp3; -- 인덱스 사용하지 않은 경우 0.002초(정렬이 먼저 발생하기 때문)
  
  -- 인덱스의 hint를 사용해 최대값을 구해오는 방법
    -- hint? : 
    -- 아래의 경우는 실행계획을 담당하는 옵티마이저에게 ~~게 할거라고 알려주는 건대
    -- 즉, 기본적으로는 asc지만, desc로 지정해 가장 큰 값이 위로 올라와 있게 되기 때문에
    -- rownum = 1을 통해 최대값을 가져올 수 있게된다.
  select /*+ index_desc(emp3 idx_name)*/ name
  from emp3
  where name > '0' and rownum = 1;
  
  -- rownum없이도 구해올 수 있는 방법(위의 방법은 인덱스가 수정되는 과정에서 문제가 발생할 수 있음)
  -- 이 방식을 -> "first_row max방법" 이라 부른다.
  select /*+ index_desc(emp3 idx_name)*/ max(name)
  from emp3
  where name > '0';
  
  
  -- 사용하지 않는 인덱스의 경우 삭제하는게 좋다.(주의사항에서 언급되어 있다.)
  -- 이렇게 삭제시 정말 사용하지 않을건지 알고 삭제하는 것이 중요한데
  -- 11g 버전에서는 사용하지 않는 상태로 만들어서 테스트 해볼 수 있도록 제공하는데
  -- invisible 인덱스란 개념을 제공한다.
    -- : 인덱스를 삭제하기 전에 사용안함 상태로 만들어 테스트 할 수 있는 기능.
  
  -- salary 컬럼으로 emp3테이블의 인덱스 생성
  create index idx_sal on emp3(salary);
  
  select table_name, index_name, visibility 
  from user_indexes
  where table_name = 'EMP3';
  
  -- 인덱스를 사용안함 상태로 바꾸기
  alter index idx_sal invisible;
  -- 다시 조회하면 invisible상태로 바껴있음을 알 수 있음.
  -- 실행계획을 세우는 옵티마이저가 해당 인덱스를 사용하지 않겠다는 의미
  -- 하지만, 인덱스가 지워진건 아니고 보여지지 않은 것과 같은 효과란 것
  select table_name, index_name, visibility 
  from user_indexes
  where table_name = 'EMP3';
  
  -- 다시 visible 상태로 바까보기
  alter index idx_sal visible;
  
  


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
  [ 테이블스페이스 관리 ]
  : 처음 생성한 테이블스페이스 크기를 넘어가면 테이블스페이스 저장 용량을 늘려줘야할 것이다.
  이때, 수동으로 하지 않고 저장공간을 자동으로 늘리는 방법에 대해 알아보자.
   
  - 테이블스페이스의 size를 자동으로 조정하는 옵션 ]
   
*/
create tablespace test_3
  datafile 'c:\oradata\test_3.dbf'
  size 10M
  default storage (
                    initial 6M --최초 익스텐트 크기
                    next 1M -- 첫번째 익스텐트 다 사용 후 그 다음 익스텐트 크기
                    MINEXTENTS 1 -- 최소 개수
                    MAXEXTENTS 10
                    PCTINCREASE 0 -- next 익스텐트 다음에 ()프로 저장공간을 할당한다.
                  );
                   
-- 테이블 size를 자동으로 조정하는 방식
alter tablespace test_3
  add datafile 'c:\oradata\test_4.dbf' size 10M
  AUTOEXTEND ON NEXT 10M MAXSIZE 200M; -- 10M초과시 자동으로 10M시 늘리되 최대 200M까지
 
 
-- DBA가 수동으로 size를 조정하는 방법
alter database datafile
  'c:\oradata\test_3.dbf' resize 30M; -- 기존 파일을 30M사이즈로 바꿈
 
 
-- [ 테이블스페이스 관련 Dictionary ] 
/*
    .DBA_TABLESPACES : 모든 테이블스페이스의 저장정보 및 상태정보를 갖고 있는 Dictionary
    .DBA_DATA_FILES : 테이블스페이스의 파일정보
    .DBA_FREE_SPACE : 테이블스페이스의 사용공간에 관한 정보
    .DBA_FREE_SPACE_COALESCED : 테이블스페이스가 수용할 수 있는 익스텐트의 정보
*/
select tablespace_name, initial_extent, next_extent, min_extents, max_extents,
       pct_increase, status, contents
from DBA_TABLESPACES;
select * from dba_tablespaces;
select * from dba_data_files;
select * from dba_free_space;
select * from SYS.DBA_FREE_SPACE_COALESCED;
 
-- extent 수집(coalesced) 명령
--alter tablespace 테이블스페이스명 coalesce;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*
  [ 테이블 스페이스 ]
  : 오라클에서 데이터를 저장할 때 사용하는 논리적 저장공간(하드디스크에서는 실제 여러개의 물리적인
    데이터 파일로 구성될 수 있음)
    -> 오라클 시스템 운영에 필요한 필수 정보를 담고 있음
     
    DB는
      여러개의 테이블 스페이스로 구성되고, 각 테이블 스페이스는 여러개의 세그먼트로 구성된다.
      이때, 세그먼트는 각종 table, trigger, index, package 등 다양한 DB Object들이 될 수 있다.
       
    - 시스템 테이블 스페이스
      : DB설치시 자동으로 기본적으로 가지고 있는 테이블 스페이스로,
        별도로 테이블 스페이스를 지정하지 않고 테이블, 트리거, 프로시저 등을 생성했다면
        이 시스템 테이블 스페이스에 저장되었던 것!
        EX) Data Dictionary 정보, 프로시저, 트리거, 패키지, 시스템 rollback segment, 사용자 데이터 포함
       
        rollback segment란?
          : rollbackcommit하기 전 상태로 돌리는데 그 돌리기 위한 상태를 저장하고 있는 세그먼트
       
    - Non-System 테이블 스페이스
      : EX) Temporary 세그먼트, application Data 세그먼트, index 세그먼트, 사용자 데이터 세그먼트
       Temporary세그먼트란?
        : order by를 해서 데이터를 가져오기 위해선 임시로 정렬할 데이터를 가지고 있을 공간이 필요하고
          그 곳에서 정렬한 뒤 데이터를 가져오는데 이 공간을 가리킨다.
         
     
    - [ 테이블스페이스의 구성 ]
    - 테이블 스페이스
      - 세그먼트(segment) : table, 트리거 등
          - 익스텐트(extent) : 연속적인 데이터블록으로 구성(오라클 입출력 최소 저장 단위)
           
    - [ 테이블 스페이스 생성 구문 ]
    create tablespace 테이블스페이스명
      datafile '저장될 경로 및 사용할 파일명' // DBF, ora 파일로 저장
      size 저장공간 // 기본 크기 지정
      default storage storage_clause;
     
    테이블스페이스 삭제
      drop tablespace 테이블스페이스이름
      [including contents[and datafiles] //테이블 스페이스에 들어있는 데이터도 지울지
      [cascade constraints] // 연계성 있는 테이블스페이스도 삭제할지
 
    이렇게 쿼리문으로 만들수도 잇지만 EM이라는 관리도구를 통해 쉽게 UI적으로 할 수 있다.
     
    - 테이블스페이스 생성은 system 계정이어야 한다.
*/
create tablespace test_1
  datafile 'c:\oradata\test_1.dbf'
  size 100M
  default storage (
                    initial 6M --최초 익스텐트 크기
                    next 1M -- 첫번째 익스텐트 다 사용 후 그 다음 익스텐트 크기
                    MINEXTENTS 1 -- 최소 개수
                    MAXEXTENTS 10
                    PCTINCREASE 0 -- next 익스텐트 다음에 ()프로 저장공간을 할당한다.
                  );
-- 저장공간을 10M 늘리도록 수정해 보자.                 
alter tablespace test_1
  add datafile 'c:\oradata\test_2.dbf' size 10M;
 
create table aaa(
  name varchar2(10)
); -- 그냥 이렇게 하면 시스템 테이블 스페이스에 저장되게 됨
 
-- test_1 테이블 스페이스에 저장하도록 테이블 aaa2를 생성
create table aaa2(
  name varchar2(10)
)tablespace test_1;
 
-- 테이블 스페이스를 지움
drop tablespace test_1; -- 오류 발생 내용이 잇기 때문 따라서 including contents 옵션 사용이 필요
 
drop tablespace test_1
  including contents; -- 테이블 스페이스 내부에 있는 데이터도 같이 삭제한다는 의미임
-- 하고나도 파일은 남아 있음
-- 따라서, 파일까지 지울려면 and datafiles 옵션도 추가해주어야 함
 
 
create tablespace test_3
  datafile 'c:\oradata\test_3.dbf'
  size 100M
  default storage (
                    initial 6M --최초 익스텐트 크기
                    next 1M -- 첫번째 익스텐트 다 사용 후 그 다음 익스텐트 크기
                    MINEXTENTS 1 -- 최소 개수
                    MAXEXTENTS 10
                    PCTINCREASE 0 -- next 익스텐트 다음에 ()프로 저장공간을 할당한다.
                  );
drop tablespace test_3
  including contents and datafiles;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
  [ 트리거 ]
  : 개발자가 호출해서 사용하는 것이 아니라, 특정 이벤트와 연동해서 그 이벤트, 조건이 발생시
    자동적으로 수행하는 동작을 의미한다.
    (데이터베이스가 미리 정해 놓은 조건들을 만족하거나, 특정 이벤트가 발생하면 자동적으로
     수행되는 동작(PL/SQL 블럭)으로 오라클에서 자동적으로 실행되는 PL/SQL 블럭을 의미)
     
    - [ 트리거의 유형 ]
    // 여기선 dml트리거에 대해서만 알아봄
      - insert, update, delete의 결과로 실행되는 "<DML 트리거>"
       
    - ** 트리거는 commit, rollback을 수행할 수 없고, commit,rollback을 수행하는 함수도 사용할 수 없다.
     
    - [ 트리거 구문 형식 ]
    create (or replace) trigger 트리거이름
    timming[before | after] event [insert | update | delete]
    ON 테이블명
     
    begin
      실행명령;
    end;
     
    트리거 내에서는 new와 old키워드를 통해서 DML작업이 일어난 테이블의 필드값을 가지고 올 수 있는데
    :NEW.컬럼명: DML트리거의 수정 또는 삽입문 내에서 사용이 가능하다. -> SQL 반영 후의 컬럼 데이터
    :OLD.컬럼명 : DML트리거의 수정 또는 삭제문 내에서 사용이 가능하다. -> SQL 반영전의 컬럼 데이터
    를 의미하게 됩니다.
     
*/
-- EX)
-- 테스트용 테이블 하나 생성
create table sample_dept(
  dept_id number,
  dept_name varchar2(15),
  loc varchar2(10)
);
desc sample_dept;
 
-- 위의 sample_dept에 데이터가 insert될 때 자동으로 동작하는 트리거를 작성해보자.
create or replace trigger print_msg
-- sample_dept테이블에 insert작업 이후에 실행되도록
after insert ON sample_dept
BEGIN
  DBMS_OUTPUT.PUT_line('부서가 추가되었습니다.');
END;
/
-- 화면 출력 가능하게 세팅
set serveroutput on;
-- 테이블에 insert작업 발생시킴
insert into sample_dept values(10,'마케팅부','서울');
-- 자동으로 print_msg 트리거가 동작하게됨
--------------------------------------------------------------------------------
 
-- 물건 관리를 위한 테이블
create table item(
  code char(6) primary key, -- 물품 코드
  name varchar2(15) not null,
  company varchar2(15),
  price number(8),
  cnt number default 0 -- 재고 수량
);
 
create table warehouse(
  num number(6) primary key, --물품 입고 번호
  code char(6),
  indate date default sysdate, -- 입고 날짜
  incnt number(6),
  inprice number(6),
  totalprice number(8),
  constraint fk_code foreign key(code) references item(code)
);
 
insert into item(code,name,company,price) values('c0001','선풍기','삼성',100000);
insert into item(code,name,company,price) values('c0002','에어컨','LG',50000);
select * from item;
 
-- 창고(warehouse)에 상품이 입고될 때마다 상품(item)의 수량이 늘어나도록!(
    --  재고수량이 자동으로 늘어나도록 !! 트리거로 작성해 보자.
 
-- 재고수량 갱신을 위한 트리거 생성
create or replace trigger cnt_add
after insert on warehouse
for each row -- 각 row마다 반복한다는 의미
  begin
    update item set cnt = cnt + :new.incnt -- new 선언은 insert문,update문에서만 사용가능
    -- new키워드를 통해 warehouse 테이블 데이터에접근할 수 있고, warehouse 테이블에 insert작업이 이루어진 후의
    -- 데이터를 가지고 온다는 의미이다.(new)
    where code = :new.code;
  end;
/
 
insert into warehouse(num, code, incnt, inprice, totalprice)
values(1,'c0001',2,100000,200000);
select * from item;
select * from warehouse;
 
-- 창고에서 물품이 삭제될 때마다 수량을 줄이는 트리거
create or replace trigger cnt_sub
after delete on warehouse
for each row
  begin
    -- delete에서는 new가 아닌 old를 사용해야 함
    -- delete작업이 반영되고 나버리면 데이터가 없기 때문에 반영할 incnt값이 없으니까
    update item set cnt = cnt - :old.incnt
    where code = :old.code;
  end;
/
 
delete from warehouse where code = 'c0001';
select * from item;
--------------------------------------------------------------------------------
 
-- **update**
  -- 수식 주의
create or replace trigger cnt_update
after update on warehouse
  for each row
    begin
      -- +- 는 +와 - 작업을 둘 다 해준다는 의미
      --  update item set cnt = cnt +- :old.incnt + :new.incnt -- 기존의 incnt값을 빼주고, 새로운 incnt값을 더해준다는 의미
      -- ex) 기존에 incnt값이 5가 있었으면 기존에 있었던 5를 빼주고 새로 입력된 7로 갱신해주기 위해 7을 더한다는 의미 5-5+7
      -- 기존의 값을 없애고 새 값을 반영해주도록 update를 짜야한다.
    --  where code = :new.code;
       
     -- update item set cnt = cnt - :old.incnt + :new.incnt where code = :new.code;
      update item set cnt = :new.incnt where code = :new.code;
    end;
/
 
update warehouse set incnt = 11, inprice = 800000 where code = 'c0001';
select * from warehouse;
select * from item;
 
 
-- 조건을 이용한 EX)
Create Trigger testTrigger
    after insert or update or delete -- 삽입, 업데이트, 삭제 중 하나 발생 이후에 실행
    on emp -- emp 테이블에 적용
    for each row -- DML작업에 의해 변경된 각각의 튜플(행)에 대해서
    when (:new.sal > :old.sal) -- 새로 변경된 sal이 변경전의 sal보다 큰 튜플에 대해서만
    -- 아래의 작업을 진행하라.
    BEGIN
        if (inserting) then dbms_output.put_line('inserting'); -- 삽입이면
        elsif (updating) then dbms_output.put_line('updating'); -- 수정이면
        elsif (deleting) then dbms_output.put_line('deleting'); -- 삭제이면
        end if;
    END;
     
    -- 따라서, DML에 의해 변경된 모든 튜플들 중에 when의 조건에 해당하는 튜플(행)들에
    -- ~작업을 하도록 되는 것


[ 적용한 트리거의 활성화/비활성화 설정 방법 ]

Alter Trigger 트리거명 ENABLE|DISABLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
create or replace procedure changeDataProc
IS
  rec_data hanTest%rowtype;
  tempExpireData hanTest.expire_time%type;
  tempIsVal hanTest.is_valid%type;
  tempName hanTest.nfcn%type;
  tempNameCnt number;
  Cursor cu1 IS
    select * from hanTest where is_valid = 0 order by nfuid asc, create_time asc, expire_time asc;
BEGIN
   open cu1;
   -- 맨 처음 row가져오기
   fetch cu1 into rec_data;
   -- 맨 처음 튜플은 그대로 삽입
   insert into hantest2 values rec_data; --
   tempExpireData := rec_data.expire_time;
   tempName := rec_data.nfcn;
    
   -- 두번째 튜플부터 반복문을 돌림
   loop
    fetch cu1 into rec_data;
     
    if ( tempName = rec_data.nfcn ) then
      rec_data.create_time := tempExpireData;
      tempExpireData := rec_data.expire_time;
      insert into hantest2 values rec_data;
      tempName := rec_data.nfcn;
    else
      tempName := rec_data.nfcn;
      insert into hantest2 values rec_data;
      tempExpireData := rec_data.expire_time;
    end if;
     
    exit when cu1%notfound;
   end loop;
   
   close cu1;
   
END;
/
truncate table hanTest2;
exec changeDataProc;
delete from hantest2 where nfcn='이원섭' and nfuid='이원섭' and create_time='20010119 071939';
select * from hantest2;
 
create or replace procedure insertOneData
IS
  recOne hantest%rowtype;
BEGIN
  for temp in (select * from hantest where is_valid=1) loop
    insert into hanTest2 values temp;
  end loop;
END;
/
 
execute insertOneData;
select * from hantest2 where is_valid = 1;
select count(*) from hantest2 where is_valid = 0;
select count(*) from hantest2 where is_valid=1;
select count(*) from hantest2;
  [ 패키지 ]


  : 연관성이 있는 함수나 프로시저를 그룹으로 모아놓은 개념.
  

  [ 패키지 구성 ]
  : 선언부 + 몸체부
  
  **참고로 패키지 선언부에 선언되지 않아도 패키지 몸체부에서
    사용할 수 있다... BUT 권장사항이 아니다.
    
  [ 구문 형식 ]
  - 선언부 형식
  create (or replace) package 패키지명
  IS
    패키지 내 전역 변수 선언;
    procedure 프로시저1(매개변수1,...);
    procedure 프로시저2(매개변수1,...);
    function 함수1(매개변수1,..2)return 타입;
    ...
  end;
  - 몸체부 형식
  create (or replace) package body 패키지명
  IS
  procedure 프로시저 이름
  subprogram bodis : 실제 작동하게될 서브프로그램(프로시저, 함수)
  end;
  
  *패키지 실행 : 패키지는 여러 환경에서 호출되어 실행가능하다.
              패키지에 대한 실행권한을 가진 사용자만 실행시킬 수 있다.
exec 패키지명.프로시저or함수명;
select 패키지명.함수명(매개변수) from ~~
  *패키지 삭제 : 선언부와 몸체부를 모두 삭제할 수 있다. 또는 몸체만 삭제할 수도 있다.
      drop package 패키지명; -- 둘 다 삭제할 경우
      drop package body 패키지명; -- 몸체만 삭제할 경우


- 패키지 내에 전역 변수를 선언하고 해당 변수를 사용할 경우 초기화 작업이 필요한 경우가 있다.
  이런 경우를 지원하기 위해 
  패키지.프로시저 or 함수를 수행할 때 마다 한번 무조건 실행하게 해 초기화 작업등을 할 수 있도록 지원하는 것이 있는데
  이를 ONE_TIME_ONLY_PACKAGE 라고 한다.
 
  방식은, 단순히 패키지 바디의 맨 아래 부분에 BEGIN을 기술하고 초기화 작업등을 해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
set serveroutput on;
-- 선언부
-- emp_sum과 emp_avg 프로시저로 구성된 패키지
create or replace package emp_proc
IS
  entireVariable varchar2; -- 패키지 전역 변수
  procedure emp_sum;
  procedure emp_avg;
  function emp_cnt(emp_id employees.employee_id%type) return number;
end;
/
--몸체부
create or replace package body emp_proc
IS
  -- 프로시저1(위에 선언한 emp_sum 구현부)
  procedure emp_sum
  IS
    cursor emp_tot_sum is
      select count(*), sum(nvl(salary,0))
      from employees;
    tot_num number;
    tot_sum number;
  BEGIN
    open emp_tot_sum;
    fetch emp_tot_sum into tot_num, tot_sum;
    dbms_output.put_line('전체인원수 : '||tot_num||', 급여합계 : '||tot_sum);
    close emp_tot_sum;
  END emp_sum; -- 각 프로시저 끝(emp_sum프로시저의 끝 부분)
 
  -- emp_avg 프로시저
  procedure emp_avg
  IS
    cursor emp_tot_avg is
      select count(*), avg(nvl(salary,0))
      from employees;
    tot_num number;
    tot_avg number;
  BEGIN
    open emp_tot_avg;
    fetch emp_tot_avg into tot_num, tot_avg;
    dbms_output.put_line('전체인원수 : '||tot_num||', 평균급여 : '||tot_avg);
    close emp_tot_avg;
  END emp_avg; -- emp_avg 프로시저 끝
   
  function emp_cnt(emp_id employees.employee_id%type)
  return number
  IS
    cnt number;
  BEGIN
    select count(*) into cnt from employees where employee_id = emp_id;
    return cnt;
  END emp_cnt;
   
  BEGIN -- one time only package
     entireVariable := '초기값 설정';
 
end; -- body의 끝
/
 
-- 패키지 실행 방법
      --  : exec 패키지명.프로시저이름
exec emp_proc.emp_avg;
execute emp_proc.emp_sum;
select emp_proc.emp_cnt(employee_id) from employees;


[ 서브프로그램의 함수 사용 ]


  
  서브프로그램
    - 프로시저
    - 함수
    
    [ 함수와 프로시저의 차이? ]

    - 프로시저는 in, out, in out모드를 사용해서 값을 반환하기도 하고,
      반환하지 않고 종료 할 수도 있다.
      VS
    - 함수는 어떤 작업을 수행한 후에, 결과를 반환한다.

    
    [ 함수의 구문 형식 ]
    create (or replace) function 함수명
    파라미터1 파라미터타입,
    파라미터2 파라미터타입,...
    return datatype
    is
    PL/SQL 블럭;

EX)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
--EX)
create or replace function dept_max_sal
  (dept_id employees.department_id%type)
return number
IS
  max_sal employees.salary%type;
begin
  select max(salary) into max_sal
  from employees
  where department_id = dept_id;
   
  -- number 타입의 max_sal을 리턴시킴
  return max_sal;
end;
/
   
select dept_max_sal(50) from dual;
 
 
create or replace function cnt_number
  (cnt number)
return number
IS
  total_cnt number;
begin
  select count(*) into total_cnt
  from employees
  where department_id = cnt;
   
  return total_cnt;
end;
/
 
select distinct cnt_number(50) from employees;
-- 부서별 인원수
select distinct department_id, cnt_number(department_id) from employees;
 
declare
 
begin
   dbms_output.put_line(cnt_number(50));
end;
 
 
create or replace function avg_sal
  (dept_id employees.department_id%type)
return number
IS
  avg_salary number;
begin
  select round(avg(salary),2) into avg_salary
  from employees
  where department_id = dept_id;
   
  return avg_salary;
end;
/
 
select department_id, avg_sal(department_id) 부서별평균급여
from employees
group by department_id;
 
 
 
create or replace function emp_dept_name
  (emp_id employees.employee_id%type)
return varchar2
IS
  dept_name departments.department_name%type;
BEGIN
  select department_name into dept_name
  from departments
  where department_id = (select department_id from employees
                         where employee_id = emp_id);
   
  return dept_name;
END;
/
 
select distinct employee_id, emp_dept_name(employee_id) from employees;





 [ 서브프로그램의 이해, 프로시저 ]


  : PL/SQL의 서브프로그램이란?
    -> <PL/SQL의 익명 블럭의 특징>
      : . 이름이 없는 PL/SQL 블럭
        . 저장해서 사용되지 않는다.
        . 사용할 때마다 컴파일이 필요하다.
        . 다른 응용프로그램에서 사용할 수 없다.
        . 값을 반환할 수 없다.
        . 매개변수를 사용할 수 없다.
        
    -> <서브프로그램의 특징>
      : . 이름이 있는 PL/SQL 블럭
        . DB에 저장해 반복적으로 사용할 수 있다.
        . 최초 실행될 때 한번만 컴파일 한다.
        . 다른 응용프로그램에서 사용할 수 있다.
        . 함수일 경우 값을 리턴(반환)할 수 있다.
        . 매개변수를 사용할 수 있다.
        
      ** "서브 프로그램"은 내장된 함수프로시저가 있다.
        - 프로시저
        - 함수
      ---------------------------------------------------------------------
      
      - 프로시저란??
        : 특정 처리를 위한 서브 프로그램의 한 유형이다.
        : 단독으로 실행 가능하며, 다른 응용 프로그램에서도 호출되어 실행 가능하다.
        
        - 프로시저 구문 형식)
          create procedure 프로시저명
            파라미터1(in,out,in out) 데이터타입, -- default는 in모드
              -- out : 값을 반환하겠다, in : 서브프로그램 내에 값 전달 in out : 서브프로그램 내에도 전달되고, 외부 응용프로그램에도 반환하기도 함
            파라미터2(in,out,in out) 데이터타입
          is
            변수 선언부;
          begin
            프로시저 본문 처리부;
          exception
            예외처리부;
          end;

EX)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
-- 프로시저 EX)
create or replace procedure update_emp -- 매개변수가 없는 프로시저
is
  -- 변수 선언부...
begin
  update employees set first_name = '마이클'
  where department_id = 20 and job_id = 'MK_MAN';
end;
/
/* 프로시저를 실행시키기 위한 구문
  exec 또는 execute
*/
execute update_emp; -- 프로시저 실행
 
select * from employees where department_id = 20 and job_id = 'MK_MAN';
 
-- 매개변수에 모드 지정안하면 default로 in모드로 지정됨.
create or replace procedure up_sal
(emp_id employees.employee_id%type)
IS
BEGIN
  update employees set salary = 3500 where employee_id = emp_id; -- 프로시저 매개변수로 전달받은 값으로 처리
END;
/
 
-- 매개변수가 있는 프로시저 호출
exec up_sal(130);
 
 
 
create or replace procedure emp_name_sal
  (emp_id employees.employee_id%type)
IS
  emp_name employees.last_name%type;
  emp_salary employees.salary%type;
BEGIN
  select last_name, salary into emp_name, emp_salary
  from employees
  where employee_id = emp_id;
   
  DBMS_OUTPUT.put_line('사원명 : '||emp_name);
  DBMS_OUTPUT.put_line('급여 : '||emp_salary);
END;
/
-- 매개변수를 전달해 프로시저 수행
exec emp_name_sal(124);
 
 
 
-- out 모드 사용의 예
      --  emp_id는 in모드로 emp_name,emp_salary는 out모드로
      -- 즉, emp_id는 값을 프로시저 내로 가져올 때 사용하는 변수고
      -- out모드 변수 두개는 리턴할 때 사용하는 변수다.
create or replace procedure emp_info
  (emp_id employees.employee_id%type,
   emp_name out employees.last_name%type,
   emp_salary out employees.salary%type)
IS
BEGIN
  select last_name, salary into emp_name, emp_salary
  from employees
  where employee_id = emp_id; -- 입력받은 사원번호와 일치하는 것 검색
END;
/
 
-- 다른 PL/SQL내에서 프로시저 사용해 보기
declare
  emp_name employees.last_name%type;
  salary employees.salary%type;
begin
  -- 위에서 작성해 컴파일된 프로시저를 다른 PL/SQL 블럭에서 사용하기
  emp_info(124, emp_name, salary); -- 124는 in으로 프로시저 내에 전달하고 out변수 값들은
  -- 각각 emp_name과 salary변수를 써주면 해당 변수에 데이터가 리턴되어 저장되게 된다.
   
  DBMS_OUTPUT.put_line(emp_name||' '||salary);
end;
/
 
 
create or replace procedure emp_info
  (emp_id employees.employee_id%type,
   emp_name out employees.last_name%type,
   emp_salary out employees.salary%type)
IS
BEGIN
  select last_name, salary into emp_name, emp_salary
  from employees
  where employee_id = emp_id; -- 입력받은 사원번호와 일치하는 것 검색
END;
 
-- 익명블럭 외에서 프로시저를 호출하는 예)
variable emp_n varchar2(10); -- 블럭 밖에서 변수 선언 방법
variable sal number;
-- 바인드 변수를 통해 프로시저 호출하는 방법
execute emp_info(124,:emp_n,:sal);
 
print emp_n sal
 
set serveroutput on;
 
declare
  emp_name employees.last_name%type;
  salary employees.salary%type;
begin
  -- 연산자 =>를 이용해서 파라미터의 값을 지정하는 방식
    -- emp_id는 124로 지정 out타입인 emp_name은 emp_name으로 지정
  emp_info(emp_id => 124,
           emp_name => emp_name,
           emp_salary => salary);
  dbms_output.put_line(emp_name ||' '||salary);
end;
/


[ 외부 변수 선언 및 전달과 out 타입을 통한 결과값 받기 예제 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 프로시저 정의
create procedure sumProcedure(numVal in number, resultVal out number)
AS
    BEGIN
        resultVal := numVal + 10;
    END;
/
 
-- 외부에서 변수를 하나 선언
variable numV number;
-- 함수를 수행하되 외부에서 선언해논 변수에 값이 저장될 수 있도록 같이 전달해 준다. 이때 외부 선언한
-- 변수를 사용할 때는 : 를 붙여주어야 함
exec sumProcedure(8, :numV);
print numV; -- 18 출력


[    ORACLE 예외처리 개념, 종류, 사용법 - (8)    ]




  - 에러 의 종류 ]
      1. 컴파일 에러 : PL/SQL 블럭이 파싱(Parsing)될 때 "사용자 오타" 등으로 인해 발생되는 에러.
        (Compile Error)
      2. 런타임 에러(=Exception)
          : PL/SQL 블럭이 실행되는 동안 발생하는 에러로 일반적으로 런타임에러를 "Exception"이라 부른다.
            종류)
              a. 오라클 예외 
                  : 오라클에서 제공되는 예외(Predefined ORACLE Exception과 Non-Predefined ORACLE Exception이 있다.)
                    ㄱ. Predefined ORACLE Exception 
                          : 사전에 정해진 예외
                          종류)
                            - ACCESS_INTO_NULL 
                                : 정의되지 않은 오브젝트 속성에 값을 할당하고자 했을 때 발생하는 예외.                      
                            - CASE_NOT_FOUND 
                                : CASE문의 when절에 해당되는 조건이 없고 else절도 없을 경우 발생
                            - COLLECTION_IS_NULL 
                                : 선언되지 않은 컬렉션(nested table, varray)에 존재하는 메서드
                                                   이외의 메서드를 사용했을 때 발생되는 예외.
                            - CURSOR_ALREADY_OPEN 
                                : 이미 열려진 커서를 열려고 시도 했을 때 발생하는 예외
                            - DUP_VAL_ON_INDEX 
                                : 유일인덱스에 중복값을 입력햇을 때 발생하는 예외.
                            - INVALID_CURSOR 
                                : 잘못된 커서 조작이 샐행될 때 발생되는 예외.
                            - INVALID_NUMBER 
                                : 문자를 숫자로의 변환 시 실패가 될 때 발생하는 예외.
                            - LOGIN_DENIED 
                                : 잘못된 사용자명이나 암호로 로그인시도시 발생하는 예외.
                            - NO_DATA_FOUND 
                                : PL/SQL Select문이 한 건도 리턴하지 못하는 경우 발생하는 예외.
                            - NOT_LOGGED ON 
                                : 접속되지 않은 상태에서 데이터베이스에 대한 요청이 PL/SQL 프로그램으로
                                  실행된 경우 발생되는 예외.
                            - PROGRAM_ERROR 
                                : PL/SQL이 내부적인 문제를 가지고 있는 경우 발생되는 예외.
                            - ROWTYPE_MISMATCH 
                                : 할당문에서 호스트 커서 변수와 PL/SQL 커서 변수의 데이터 형이 불일치할 때 발생되는 예외
                            - STORAGE_ERROR 
                                : PL/SQL이 실행될 때 메모리가 부족하거나 메모리상에 문제가 일어났을 대 발생하는 예외.
                            - SUBSCRIPT_BEYOND_COUNT 
                                : 컬렉션의 요소 갯수보다 더 큰 첨자 값으로 참조한 경우 발생
                            - SUBSCRIPT_OUTSIDE_LIMIT 
                                : 컬렉션의 첨자 한계를 벗어난 참조가 일어났을 때 발생
                            - SYS_INVALID_ROWD 
                                : 문자열을 ROWID로 변환할 때 무효한 문자열의 표현일 경우 발생되는 예외.
                            - TIMEOUT_ON_RESOURCE 
                                : 자원에 대한 대기시간이 초과했을 때 발생하는 예외.
                            - TOO_MANY_ROWS 
                                : PL/SQL select문이 두건이상의 행을 리턴햇을 때 발생되는 예외.
                            - VALUE_ERROR 
                                : 산술,변환,절삭 크기 제약에 에러가 생겼을 때 발생되는 예외.
                            - ZERO_DIVIDE
                                : 0으로 나누려 했을 때 발생하는 예외.
                  
                    ㄴ. Non-predefined ORACLE Exception
                          : 사전에 정해지지 않은 예외
              b. 사용자 정의 예외
                  : 사용자에 의해 정의되는 예외
                    사용자 정의 예외 사용 예)
                      declare (선언부에서)
                      예외명 exception;
                      begin부나 exception부에서 raise문을 이용해서 예외를 발생시킨다.
                      
                      - 예외처리부 형식)
                          -- 예외처리부 : 예외 발생시 어떻게 처리할 것인지에 예외처리 내용이 들어간다.
                        Exception
                          when 예외명 then
                            실행문...
                          when 예외명2 then
                            실행문...
                          when OTHERS then
                            실행문...

EX)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
-- 미리 정의된 예외(Predefined ORACLE Exception) EX)
declare
  emp_lastname employees.last_name%type;
begin
  select last_name into emp_lastname
  from employees
  where last_name like 'B%';
   
  DBMS_OUTPUT.put_line('사원명 : ' || emp_lastname);
 
exception
  when no_data_found then
    DBMS_OUTPUT.PUT_LINE('해당 사원이 없습니다.');
  when too_many_rows then
    DBMS_OUTPUT.PUT_LINE('사원이 두명 이상입니다.');
end;
/
 
-- 사용자 정의 예외처리 EX)
-- 1. pragma를 이용한 컴파일러에 지정하는 방법
declare
  emp_exist exception;
  pragma exception_init(emp_exist, -2292); -- emp_exist예외는 2292번 에러 코드로 처리하겠다고 컴파일러에 알려주는 부분
begin
  delete departments
  where department_id = &deptno; -- 사용자에게 detpno값 입력 받아 해당 내용을 지움
   
  commit; -- 반영함
 
  exception
    when emp_exist then
      DBMS_OUTPUT.PUT_LINE('사원이 존재하기 때문에 지울 수 없습니다.');
end;
/ -- 20번 입력
 
-- 2. raise를 이용한 예외처리 발생 방법
declare
  emp_id employees.employee_id%type;
  emp_name employees.last_name%type;
   
  no_emp exception; -- 예외명 선언
begin
  delete employees
  where employee_id = &empid;
   
  if sql%notfound then -- "묵시적커서" 속성을 이용
    raise no_emp; -- 예외를 발생시킴
  end if;
 
  exception
    when no_emp then -- raise로 no_emp가 발생할 경우 처리할 부분
      DBMS_OUTPUT.PUT_LINE('해당사원이 없습니다.');
end;
/
 
-- raise_application_error라는 프로시저를 이용하는 방법
    --  이때, 이용가능한 번호 : 20000 ~ 20999 번
declare
  emp_id employees.employee_id%type;
  emp_name employees.last_name%type;
begin
  delete employees
  where employee_id = &empid;
   
  if (sql%notfound) then
    raise_application_error(-20111,'사원이 없습니다..'); -- 에러코드와 에러메시지 지정
  end if;
end;
/
 
 
set serveroutput on;


[    ORACLE 커서(CURSOR)    ]




 [  커서  ]
  : select문을 통해 결과값들이 나올 때 이 결과들은 메모리 공간에 저장하게 되는데
  
    이때, 이 메모리 공간을 "커서"라고 한다.
    
    즉, 
      - 커서란? : 쿼리문에 의해서 반환되는 결과값들을 저장하는 메모리 공간이다.
      - Fetch란 : 커서에서 원하는 결과값을 추출하는 것.
    
    왜사용하는가???!!  
커서는 로우를 기반으로 하는 작업이 된다!!! 라는 것입니다. 저러한 로우를 하나 SELECT해서 여러가지 처리를 한후 어떠어떠한 작업을 한다~~
           
이것을 가능하게 하는 것이 바로!!! 커서 입니다.
      
     물론 SQL구문만을 가지고 어거지로 어찌어찌 한다면~~ 가능할 겁니다.
        
     하지만 커서를 이용하시면 훨씬 빠르고 간단히 저러한 처리를 가능하게 할 수 있습니다.
    
여기까진 이론적으로고... 그냥 근본적으로는

-> select의 결과가 하나의 행(단일 튜플)일 경우는 into절을 이용해 변수에 저장할 수 있지만, 결과가 복수행(복수 튜플)일      경우는 into절로 이를 처리할 수가 없죠
    이렇게 여러 복수 행의 결과를 행단위로 처리를 하기 위해서 사용하는 것이 Cursor입니다.
   

1. 커서의 정의
 

커서란 SQL Plus에서 사용자가 실행한 SQL문의 단위를 의미합니다.


오라클에서 수행한 모든 쿼리문은 커서 단위로  처리합니다.

PL/SQL의 SQL문처럼 하나의 결과를 리턴하는 경우 커서 없이도 SQL문의 실행결과가 암시적으로 커서에 저장되므로 이를 암시적 커서라고 합니다.
 

SQL문을 수행한 후에 결과로 얻어지는 행이 여러 개일 경우에는 암시적은 커서에 정보를 저장할 수 없기에 에러가 발생합니다. 이럴 경우에는 반드시 명시적인 커서를 사용해야 합니다.


명시적인 커서는 PL/SQL 레코드(RECORD)와 PL/SQL의 테이블(TABLE)을 결합한 것으로서 프로그램 언어의 구조체 배열과 유사합니다

------------------------------------------------------------------------------------------------------
(오라클에서 CURSOR란 시스템 글로벌 영역의 공유 풀 내에 저장공간을 사용하여 사용자가 SQL 문을 실행시키면 결과값을 저장공간에 가지고 있다가 원하는 시기에 순차적으로 fetch해 처리하여 해당 결과 셋을 프로그래밍적으로 

접근할수 있게 도와주는 기능이다.)

   
      [ 커서의 종류 ]
      1. 묵시적 커서(Implicit Cursor)
        : 오라클에서 자동으로 선언해주는 SQL 커서.(사용자는 생성 유무를 알 수 없다.)
        
      2. 명시적 커서(Explicit cursor)
        : 사용자가 선언해서 생성한 후에 사용하는 SQL 커서, 주로 여러개의 행을 처리하고자
          할 경우 사용한다.
        
      [ 커서의 속성 ]
      1. %Found : 가져올 레코드가 있는 경우 true를 반환
      2. %isOpen : 커서가 오픈 상태일 경우 true를 반환
      3. %NotFound : 더이상 참조할 레코드가 없을 때 true를 반환
      4. %RowCount : 카운터 역할을 한다. 처음 오픈시 0, 패치발생할 때마다 1씩 증가
      
      [ 커서의 처리 단계 ](명시적 커서 기준)
      1. 명시적 커서 선언(커서 생성)
        EX) Cursor 커서이름
      2. 명시적 커서 오픈
        EX) Open 커서이름
      3. 커서에서 데이터 추출(데이터 행 가져오기)
        EX) Fetch 커서이름
      4. 커서 종료
        EX) Close 커서이름


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/*
  [  커서  ]
  : select문을 통해 결과값들이 나올 때 이 결과들은 메모리 공간에 저장하게 되는데
   
    이때, 이 메모리 공간을 "커서"라고 한다.
     
    즉,
      - 커서란? : 쿼리문에 의해서 반환되는 결과값들을 저장하는 메모리 공간이다.
      - Fetch란 : 커서에서 원하는 결과값을 추출하는 것.
     
    왜사용하는가???!!  커서는 로우를 기반으로 하는 작업이 된다!!! 라는 것입니다.
    저러한 로우를 하나 SELECT해서 여러가지 처리를 한후 어떠어떠한 작업을 한다~~
    이것을 가능하게 하는 것이 바로!!! 커서 입니다.
    물론 SQL구문만을 가지고 어거지로 어찌어찌 한다면~~ 가능할 겁니다.
    하지만 커서를 이용하시면 훨씬 빠르고 간단히 저러한 처리를 가능하게 할 수 있습니다.
     
       
      [ 커서의 종류 ]
      1. 묵시적 커서(Implicit Cursor)
        : 오라클에서 자동으로 선언해주는 SQL 커서.(사용자는 생성 유무를 알 수 없다.)
         
      2. 명시적 커서(Explicit cursor)
        : 사용자가 선언해서 생성한 후에 사용하는 SQL 커서, 주로 여러개의 행을 처리하고자
          할 경우 사용한다.
         
      [ 커서의 속성 ]
      1. %Found : 가져올 레코드가 있는 경우 true를 반환
      2. %isOpen : 커서가 오픈 상태일 경우 true를 반환
      3. %NotFound : 더이상 참조할 레코드가 없을 때 true를 반환
      4. %RowCount : 카운터 역할을 한다. 처음 오픈시 0, 패치발생할 때마다 1씩 증가
       
      [ 커서의 처리 단계 ](명시적 커서 기준)
      1. 명시적 커서 선언(커서 생성)
        EX) Cursor 커서이름
      2. 명시적 커서 오픈
        EX) Open 커서이름
      3. 커서에서 데이터 추출(데이터 행 가져오기)
        EX) Fetch 커서이름
      4. 커서 종료
        EX) Close 커서이름
 
*/
--EX)
declare
  emp_id number(4);
  emp_name varchar2(10);
  emp_salary number(10);
   
  Cursor cu1 IS --  1. 명시적 커서 선언(커서 생성)
    select employee_id, last_name, salary
    from employees
    where department_id = 50;
begin
  open cu1; -- 2. 명시적 커서 오픈
   
  DBMS_OUTPUT.put_line('사번    이름    급여');
   
  loop
    Fetch cu1 into emp_id, emp_name, emp_salary; -- 3. 커서에서 데이터 추출(데이터 행 가져오기)
    exit when cu1%notFound; -- 더이상 할당할 레코드가 없을 때를 의미
   
    dbms_output.put_line(emp_id||'  '||emp_name||'  '||emp_salary);
  end loop;
   
  close cu1; -- 4. 커서 종료
end;
/
 
set serveroutput on;
 
  -- 커서와 반복문(FOR문) ]
      --  사용형식)
        /*
        For 레코드 네임 IN 커서이름 loop
          명시적 커서의 OPEN, FETCH가 자동으로 수행
          실행 문장들
        End loop; 루프문을 빠져나갈 때 자동적으로 커서가 종료된다.
        */
        declare
          Cursor emp_cur IS
            select employee_id, last_name
            from employees;
        begin
         
          -- FOR문과 Cursor가 같이사용될 때는
          for emp_rec in emp_cur loop -- 명시적 커서의 OPEN,FETCH가 자동으로 수행
            DBMS_OUTPUT.PUT_LINE(emp_rec.employee_id||' '||emp_rec.last_name);
          end loop; -- 루프문 빠져나갈 때 자동적으로 커서가 종료됨
         
        end;
        /
         
         
        declare
          emp employees%rowtype;
           
          Cursor cur1 IS
            select employee_id, last_name, salary
            from employees;
             
        begin
          for emp IN cur1 loop
            exit when cur1%notfound;
            dbms_output.put_line(emp.employee_id||' '||emp.last_name||' '||emp.salary);
          end loop;
        end;
        /
         
         
        -- 커서 사용하지 않고도 할 수 있는 방법
        declare
        begin
          For emp in (select employee_id, last_name, salary from employees) loop
             dbms_output.put_line(emp.employee_id||' '||emp.last_name||' '||emp.salary);
          end loop;
        end;
        /
         
         
        /* 묵시적 커서 사용 예 */
        declare
          cnt1 number;
          cnt2 number;
        begin
          select count(*) into cnt1
          from employees
          where department_id = 50;
           
          -- rowcount : 커서 오픈시 0값을 가지고 생성되며,
                --      Fetch할 때마다 값이 1씩 증가한다.
                --      묵시적 커서의 경우 속성 사용시에 sql%속성명 으로 접근한다.
                --      ex) sql%notfound, sql%rowcount, sql%found, sql%isopen
          cnt2 := sql%rowcount;
           
          DBMS_OUTPUT.PUT_LINE('cnt1의 값 : '||cnt1); -- 45(count(*)의 값)
          -- 위의 select문에서 한번 결과를 끄집어내서 cnt1에 넣을 때
          -- fetch가 발생해 rowcount값이 1증가 됨(묵시적 커서로 동작하게 된 것)
          DBMS_OUTPUT.PUT_LINE('cnt2의 값 : '||cnt2); -- 1
           
        end;


[    ORACLE 반복문(basic loop, while, for, continue)    ]




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*
  [ 반복문 ]
  - basic loop문
  - while문
  - for
*/
 
-- 1. basic loop문(조건을 나중에 검사) : DO WHILE문과 유사
      --  형식
      /*
        loop
          pl/sql문장;
          exit when(조건);
        end loop;
      */
       
    -- 1~10까지 출력하기
    declare
      num number := 1;
    begin
      loop
        dbms_output.put_line(num);
        num := num + 1;
        exit when (num > 10);
      end loop;
    end;
    /
     
-- 2. while문(조건을 먼저 검사)
    /*
      형식)
        while 조건 loop
          실행문장;
        end loop
    */
    declare
      num number := 1;
    begin
      while (num <= 10) loop
        DBMS_OUTPUT.PUT_LINE(num);
        num := num + 1;
      end loop;
    end;
    /
     
    declare
      num number := 10;
    begin
      loop
        DBMS_OUTPUT.PUT_LINE(num);
        num := num - 1;
        exit when num = -1;
      end loop;
    end;
    /
     
    declare
      num number := 10;
    begin
      while (num >= 0) loop
        DBMS_OUTPUT.PUT_LINE(num);
        num := num - 1;
      end loop;
    end;
    /
     
     
-- 3. FOR문 : 반복횟수를 지정할 수 있다.
  /*
        형식) java의 FOR EACH문과 유사! IN뒤에 나온 것이 순차적으로 i에 들어가는 개념
          FOR i IN start..end loop
            실행문장
          end loop;
  */
  declare
   
  begin
    FOR i IN 1..10 loop
      DBMS_OUTPUT.PUT_LINE(i);
    end loop;
  end;
  /
   
  -- for문 역순
      --  reverse를 붙여주자.
  begin
    FOR n IN reverse 0..10 loop
      DBMS_OUTPUT.PUT_LINE(n);
    end loop;
  end;
  /
   
  /*사원테이블에서 사원id를 입력받아서 사원이름의 문자길이만큼
    #을 찍는 PL/SQL문을 작성해보자.(employees테이블을 이용)
  */
  declare
    emp_id employees.employee_id%type := &emp_no; -- 입력받음
    emp_name employees.last_name%type;
    emp_name_length number(20);
    v_char varchar2(30);
  begin
    select last_name, length(last_name) into emp_name, emp_name_length
    from employees
    where employee_id = emp_id;
     
    for i in 1..emp_name_length loop
      v_char := v_char ||'#';
    end loop;
     
    DBMS_OUTPUT.PUT_LINE(v_char);
  end;
  /
   
   
-- continue 보조제어문(*11g부터 추가된 기능임*) ]
  declare
    tot number := 0;
  begin
    for i in 1..10 loop
      tot := tot + 1;
      DBMS_OUTPUT.put_line('tot : ' || tot);
      continue when i > 5; -- 해당 조건을 만족하면 다음 차수로 바로 가게 된다.
      -- 아래를 수행하지 않고 다음번 차수로 넘어가게 됨.
      tot := tot + i;
      DBMS_OUTPUT.PUT_LINE('tot2 :' || tot);
    end loop;
  end;
  /


[    ORACLE 조건문(IF,CASE)    ]





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
  [ 제어문 ] : 조건문, 반복문
     
      - 조건문 : if문, case문(switch문과 유사)
      - 반복문 : basic loop문, while문(반복횟수를 정하지 않을 경우)
              : for문(반복횟수 지정할 경우)
 
      // if문 : if ~ end if
            EX)
              if(조건) then
                실행명령;
              elsif(조건) then
                실행명령;
              else 실행명령;
              end if;
       
      // case
            EX)
                case 변수명
                  when 값1 then 실행명령;
                  when 값2 then 실행명령;
                  ...
                end;
*/
 
set serveroutput on;
 
-- if문 Ex)
declare
  emp_id employees.employee_id%type;
  emp_name employees.last_name%type;
  emp_dept employees.department_id%type;
  dept_name varchar2(20) := null;
begin
  select employee_id, last_name, department_id
  into emp_id,emp_name,emp_dept
  from employees
  where employee_id = 124;
   
  if(emp_dept = 50) then --if문 시작
    dept_name := 'Shipping';
  end if;
  if(emp_dept = 60) then
    dept_name := 'IT';
  end if;
  if(emp_dept = 70) then
    dept_name := 'Public Relation';
  end if;
   
  DBMS_OUTPUT.PUT_LINE(emp_id||' '||emp_name||' '||emp_dept||' '||dept_name);
end;
/
 
 
declare
  emp_id employees.employee_id%type;
  emp_name employees.last_name%type;
  emp_dept employees.department_id%type;
  dept_name varchar2(20) := null;
begin
  select employee_id, last_name, department_id
  into emp_id,emp_name,emp_dept
  from employees
  where employee_id = 103;
   
  if(emp_dept = 50) then
      dept_name := 'Shipping';
    elsif(emp_dept = 60) then -- elseif가 아니라 elsif임을 주의...
      dept_name := 'IT';
    elsif(emp_dept = 70) then
      dept_name := 'Public Relation';
    ELSE
      dept_name := 'Other';
  end if;
  DBMS_OUTPUT.PUT_LINE(emp_id||' '||emp_name||' '||emp_dept||' '||dept_name);
end;
/
 
 
declare
  emp_id employees.employee_id%type;
  emp_name employees.last_name%type;
  emp_comm employees.commission_pct%type := null;
begin
  select employee_id, last_name, commission_pct
  into emp_id, emp_name, emp_comm
  from employees
  where employee_id = 130;
   
  if (emp_comm > 0) then
    dbms_output.put_line(emp_id||' 의 보너스는 '||emp_comm);
  else
    DBMS_OUTPUT.PUT_LINE(emp_id||'의 보너스는 없습니다.');
  end if;
end;
/
 
 
-- case문 EX)
 
declare
  emp_id employees.employee_id%type;
  emp_name employees.last_name%type;
  emp_dept employees.department_id%type;
  dept_name varchar2(20) := null;
begin
  select employee_id, last_name, department_id
  into emp_id, emp_name, emp_dept
  from employees
  where employee_id = &empno; --치환변수 이용해 사용자로부터 입력받음
   
  dept_name := case emp_dept
                 when 50 then 'Shipping'
                 when 60 then 'IT'
                 when 70 then 'Public Relation'
                 when 80 then 'Sales'
               end;
  DBMS_OUTPUT.PUT_LINE(dept_name);
end;
/


[    ORACLE TABLE TYPE(컬렉션), 바인드 변수 -(4)    ]



쿼리의 일부분 예를 들어 WHERE 절의 내용만 다른 쿼리를 실행해야 하는 경우가 종종 생길 것이다. 이러한 경우에 거의 비슷한 두번의 쿼리를 실행하는 비효율성을 해소하는 방법이 바로 바인드 변수의 사용이다.
바인드 변수는 입력 내용을 넣고 SQL로부터 출력 내용을 받아내는 방법으로, " 이 부분에 들어갈 정확한 값은 이후에 알려줄테니, 일단 내가 값을 넣었을 때 어떻게 실행할 것인지에 대해서 계획만 세워둬라 "는 명령을 오라클에 내리는 것이다.

select * into temp from row_test where no = :bind_no; 이 부분이 있을 때 :가 붙은 변수가 바인드 변수인대, 실행한 뒤에 바인드 변수에 입력할 값을 넣게 된다.
그에 따라 no = 1, no = 2 등 실행할 때마다 다양한 결과를 낼 수가 있게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
  [ Table Type 변수(=컬렉션) ]
  컬렉션 : 일반 프로그래밍 언어에서 사용하는 배열 타입을
          pl/SQL에서는 "컬렉션"이라 한다.
   
    - 종류
      1. 연관배열(associative array or index-by table)
            : (KEY, VALUE) 쌍으로 저장되는 배열 - 키값을 통해 값에 접근한다.
             
            . key의 데이터 유형 - 숫자 : binary_integer, pls_integer
                                      위 두가지 데이터 타입은 number보다
                                      작은 저장 영역이 필요, 산술연산의 경우
                                      number보다 빠르다.
                              - 문자 : varchar2
            . 값(value) 유형  - 일반 데이터 타입, 레코드 데이터 타입도 값이 될 수 있다.
                                -> 레코드 타입일 경우 여러개의 값을 가질 수 있다.
   
      2. varry(variable array)
            : 고정 길이를 가진 배열(일반 프로그래밍에서 사용하는 배열과 동일)
            : 인덱스가 "0"부터 시작한다.
                ex) 3번째 v[3]
      3. 중첩테이블(nested table)
            : varry와 흡사한 구조의 배열
            : (배열의 크기를 명시하지 않음)배열의 최대값이 정해지지 않고 동적으로 자동으로 최대값이 증가됨
             
       
    - [ Table Type(컬렉션)의 선언 형식 ]
      1. 정의
        TYPE 타입명 IS TABLE OF
        employees.first_name%type
        INDEX BY BINARY_INTEGER -- binary integer로 인덱스를 사용한단 의미
      2. 선언(메모리 공간이 잡히는 부분)
        식별자 타입명;
*/
 
set SERVEROUTPUT ON;
 
declare
  tname varchar2(20);
   
  -- 정의
  TYPE t_emp_name IS TABLE OF
  employees.last_name%type
  INDEX BY BINARY_INTEGER;
   
  -- 선언
  v_name t_emp_name;
begin
  select last_name into tname
  from employees
  where employee_id = 100;
   
  -- 인덱스를 통해 접근
  v_name(0) := tname;
  DBMS_OUTPUT.PUT_LINE(v_name(0));
end;
/
 
 
declare
  TYPE tbl_type IS TABLE OF
    employees.last_name%type
  INDEX BY BINARY_INTEGER;
   
  vtbl_type tbl_type;
  a binary_integer := 0; -- 변수 a값을 0으로 초기화
begin
  for emp_name in(select last_name from employees) loop
    a := a+1;
    vtbl_type(a) := emp_name.last_name;
  end loop;
   
  for i in 1..a loop
    DBMS_OUTPUT.PUT_LINE(vtbl_type(i));
  end loop;
end;
/
 
 
/*
  [ 바인드 변수(비 PL/SQL 변수) ]
    : 호스트 환경에서 생성되어 데이터를 저장하기 때문에 호스트 변수라고 한다.
 
쿼리의 일부분 예를 들어 WHERE 절의 내용만 다른 쿼리를 실행해야 하는 경우가 종종 생길 것이다. 이러한 경우에 거의 비슷한 두번의 쿼리를 실행하는 비효율성을 해소하는 방법이 바로 바인드 변수의 사용이다.
바인드 변수는 입력 내용을 넣고 SQL로부터 출력 내용을 받아내는 방법으로, " 이 부분에 들어갈 정확한 값은 이후에 알려줄테니, 일단 내가 값을 넣었을 때 어떻게 실행할 것인지에 대해서 계획만 세워둬라 "는 명령을 오라클에 내리는 것이다.
 
select * into temp from row_test where no = :bind_no; 이 부분이 있을 때 :가 붙은 변수가 바인드 변수인대, 실행한 뒤에 바인드 변수에 입력할 값을 넣게 된다.
그에 따라 no = 1, no = 2 등 실행할 때마다 다양한 결과를 낼 수가 있게 된다.   
 
    - 키워드 VARIABLE를 이용하며, SQL문이나 PL/SQL블록에서도 사용가능
    - PL/SQL블록이 실행된 후에도 액세스가 가능하다.
    - print명령을 이용하여 출력가능
    - :을 붙여 이용한다.
*/
 
set autoprint on; -- print를 하지 않아도 자동으로 출력하도록 세팅하는 부분(default는 false로 되어있음)
 
declare
  temp row_test%ROWTYPE;
begin
  select * into temp from row_test where no = :bind_no;
  insert into row_test3 values temp;
end;
 
select * from row_test3;
 
-- no에 1을 입력시 그에 해당되는 결과가 2를 입력하면 그에 해당하는 결과가 나오게 된다.
 
 
begin
  select (salary*12+nvl(commission_pct,0)) into :vsal -- 결과값을 바인드 변수에 넣는다.
  from employees
  where employee_id = 100;
end;
/
-- print를 이용해서 PL/SQL블럭 밖에서도 출력할 수 있다.
print vsal;
 
 
create table row_test3 as select * from row_test;
truncate table row_test3;
select * from row_test3;
 
declare
  type arrRecType IS TABLE OF
  row_test%ROWTYPE
  INDEX BY binary_integer;
   
  TestValue arrRecType;
  a binary_integer := 0;
begin
  for temp_rec in (select * from row_test) loop
    a := a+1;
    TestValue(a) := temp_rec;
  end loop;
   
  for i in 1..a loop
    insert into row_test3 values TestValue(i);
  end loop;
end;
/
select * from row_test3;


[    ORACLE rowType 변수 및 복합변수 활용 예    ]


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
--[ rowType 변수 및 복합변수 활용의 예 ]
--    : rowType 변수를 활용한 데이터의 변경
 
DECLARE
  c_rec row_test%ROWTYPE;
BEGIN
  select * into c_rec
  from row_test
  where no = 3;
 
  -- 대입연산자는 := 를 사용해야 한다.(주의!) 
  c_rec.name := '강길동';
   
  -- row(행 전체)를 c_rec로 바꾸는 방법
  update row_test2 set row = c_rec
  where no = 3;
   
END;
/
 
select * from row_test2;
 
 
-- 사용자로 부터 두개의 숫자를 입력받아서 합을 구하는 예
  --  치환연산자 & 사용)
   
  set SERVEROUTPUT ON;
   
  declare
    no1 number := &no1; -- 사용자로부터 입력을 받아 값을 할당
    no2 number := &no2;
    sumV number;
  begin
    sumV := no1 + no2;
    DBMS_OUTPUT.put_line('첫번째 수'||no1||'두번째 수'||no2||'합'||sumV);
  end;
  /
   
-- 복합변수 )
-- 직접 복합 타입을 지정할 수도 있다.
  -- record Type 변수 지정 방법
      --1. type 타입명 is record();
      --2. 식별자 타입명
   
  declare
    type emp_rec is record
    (emp_id employees.employee_id%type,
     emp_name employees.first_name%type,
     emp_job employees.job_id%type
    );
     
    rec1 emp_rec; -- 위에서 정의한 타입을 사용해 변수 선언
  begin
    select employee_id, first_name, job_id into rec1
    from employees
    where department_id = 10;
     
    DBMS_OUTPUT.put_line('사번    이름    업무ID');
    DBMS_OUTPUT.PUT_LINE(rec1.emp_id||'   '||rec1.emp_name||'   '||rec1.emp_job);
  end;
  /
   
   
declare
  type emp_rec2 is record
  (
    emp_id employees.employee_id%type,
    emp_name employees.last_name%type,
    emp_email employees.email%type,
    emp_salary employees.salary%type
  );
   
  rec2 emp_rec2;
  -- 사원번호는 사용자로부터 입력받도록
  vemp_id employees.employee_id%type := '&사번';
begin
  select employee_id, last_name, NVL(email,'없음'), salary
    into rec2
  from employees
  where employee_id = vemp_id; -- 사원번호가 사용자로부터 입력받은 것과 같은 정보
   
  DBMS_OUTPUT.put_line('사번:'||rec2.emp_id);
   
  DBMS_OUTPUT.put_line('이름:'||rec2.emp_name);
   
  DBMS_OUTPUT.put_line('이메일;'||rec2.emp_email);
   
  DBMS_OUTPUT.put_line('월급:'||rec2.emp_salary);
end;
/
 
 
 
-- 지금까지 공부한 PL/SQL 부분 연습하기 --
declare
  -- 1.일반 변수 선언법
  no number(10);
  strValue varchar2(20);
  -- 2.기존 테이블의 특정 필드의 타입으로 선언하는 방법
  emp_id employees.employee_id%TYPE;
  emp_name employees.first_name%TYPE; -- employees테이블의 first_name 필드와 같은 타입으로 선언
  -- 3.특정 기존 테이블과 동일한 복합 타입으로 지정하는 방법
  emp_rec employees%ROWTYPE;
  dep_rec departments%ROWTYPE;
  -- 4.사용자에게 입력을 받아 선언한 변수에 데이터를 넣는 방법
  input_data row_test.no%TYPE := '&no번호';
  -- 5.사용자 지정 복합 타입으로 변수를 선언하는 방법
  type myType is record
  (
    my_no row_test.no%TYPE,
    my_name row_test.name%type,
    my_hdate row_test.hdate%type
  );
  myDefVal myType;
begin
  select no, name, hdate into myDefVal
  from row_test
  where no = 3;
   
  myDefVal.my_name := '수정합니다.';
  update row_test set row = myDefVal
  where no = input_data; -- 사용자가 입력한 no번호에 해당하는 필드 값을 수정하도록...
   
end;
/
select * from row_test;


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/*
  [ PL/SQL 변수 ]
  - 변수의 생성 규칙
    1. 반드시 문자로 시작해야 한다.
    2. 문자나, 숫자, 특수문자를 포함할 수 있다.
    3. 변수명은 30Byte 이하여야 한다.
    4. 예약어(키워드)를 사용하면 안된다.
     
  - 변수의 선언은 선언부(Delcare 블럭)에서 선언되어야 하고,
    값으로 초기화가 가능하다.
  - 실행부(Begin~End)에서 실행될 경우 값이 할당 된다.
  - 서브프로그램의 파라미터로 전달되기도 하며, 서브프로그램의
    출력 결과를 저장하기도 한다.
     
  - 선언의 예>
  emp_no number(6,3) : 숫자를 저장하는 변수로 총 6자리며 소수점 밑 3자리
   
  emp_name varchar2(10) : 문자를 저장하는 변수로 총 10Byte 저장가능 변수
   
  emp_date date : 날자를 저장하는 변수
   
  - 데이터 타입>
    - char : 고정길이 문자 타입, 기본최소값 1, 최대 32,767Byte를 저장 가능
    - varchar2 : 가변길이 문자 타입, 기본값은 없다.
                 최대 32,767Byte 저장 가능.
    - number(전체자리수,소수점이하 자리수) : 숫자를 저장
                                         전체자리수 범위는 1~38까지 가능하고,
                                         소수점 자리수 범위는 -84~127까지 가능                                    
    - binary_double : 부동 소수점 수를 저장하는 타입, 9Byte 필요
    - date : 날짜 및 시간을 나타내는 타입, 초단위로 저장,
             날짜의 범위는 4712B.C ~ 9999 A.D
    - timestamp : date 타입을 확장, 연도, 월, 일, 시, 분, 초 및 소수로
                  표시되는 초단위를 저장.
                   
    ** 참조 변수 **
    테이블명.필드명%Type
     
    empNo employees.employee_id%TYPE // 현재 employees테이블의 employee_id의 타입을 그대로 가져오겟다.
      : employees 테이블의 employee_id와 동일한 데이터 타입으로 선언한다는 의미.
       
    emp_name employees.frst_name%TYPE
      : employees 테이블의 first_name과 동일한 데이터 타입으로 선언
       
    empRow employees%ROWTYPE
      : employees 테이블의 모든 컬럼을 한꺼번에 저장하기 위한 변수로 선언
*/
 
-- EX)
create table employees1
as
select employee_id, salary, department_id from employees;
 
set serveroutput on;
 
 
DECLARE
  empNo employees.employee_id%TYPE;
  empSalary employees.salary%TYPE;
BEGIN
  select employee_id, salary into empNo, empSalary
  from employees1
  where department_id = 10;
   
  DBMS_OUTPUT.put_line(empNo||' '||empSalary);
END;
/
 
 
DECLARE
  emp_row employees1%ROWTYPE; -- employees1 테이블의 모든 컬럼 타입을 의미함
BEGIN
  select * into emp_row
  from employees1
  where employee_id = 100;
   
  DBMS_OUTPUT.put_line(emp_row.employee_id||' '||emp_row.salary||' '||emp_row.department_id);
END;
/
 
 
create table row_test(
  no number,
  name varchar2(20),
  hdate date
);
 
create table row_test2
as
select * from row_test;
 
insert into row_test values(1,'아무개',sysdate);
insert into row_test values(2,'홍길동',sysdate);
insert into row_test values(3,'고길동',sysdate);
 
select * from row_test;
 
commit;
 
DECLARE
  c_rec row_test%ROWTYPE;
BEGIN
  select * into c_rec
  from row_test
  where no = 3;
   
  insert into row_test2 values c_rec;
END;
/
 
select * from row_test2;
[    ORACLE PL/SQL 개념 - (1)    ]





[ PL/SQL(Procedural Language/SQL) ]

  : 오라클에서 제공하는 프로그래밍 언어
  : 일반 프로그래밍 언어적인 요소를 다 가지고 있고, 
    데이터베이스 업무를 처리하기 위한 최적화된 언어
    
    
    
   ** [ 프로시저(PL)의 기본 구조 ] **
   
     - 선언부(Declare) : 모든 변수나 상수를 선언하는 부분
     - 실행부(Executable) : BEGIN ~ END // 실재 로직이 수행되는 부분 
          ex) 제어문, 반복문, 함수정의 등의 로직을 기술하는 부분...
     - 예외처리부(Exception) : 실행도중에 에러 발생시 해결하기위한 명령들을 기술하는 부분
       (생략가능)
       
      ** Declare, begin, exception 키워드들은 ';' 을 붙이지 않는다. **
      ** 나머지 문장들은 ';'으로 처리하여 영역의 긑을 표시한다.
      
      
     - 익명 블록(Anonymous PL/SQL)
          : 주로 일회성으로 사용할 경우 많이 사용된다.
          
     - 저장 블록(Stored PL/SQL)
          : 서버에 저장해 놓고 주기적으로 반복해서 사용할 경우 사용된다.

EX)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
  [ PL/SQL(Procedural Language/SQL) ]
  : 오라클에서 제공하는 프로그래밍 언어
  : 일반 프로그래밍 언어적인 요소를 다 가지고 있고,
    데이터베이스 업무를 처리하기 위한 최적화된 언어
     
     
     
   ** [ 프로시저(PL)의 기본 구조 ] **
    
     - 선언부(Declare) : 모든 변수나 상수를 선언하는 부분
     - 실행부(Executable) : BEGIN ~ END // 실재 로직이 수행되는 부분
          ex) 제어문, 반복문, 함수정의 등의 로직을 기술하는 부분...
     - 예외처리부(Exception) : 실행도중에 에러 발생시 해결하기위한 명령들을 기술하는 부분
       (생략가능)
        
      ** Declare, begin, exception 키워드들은 ';' 을 붙이지 않는다. **
      ** 나머지 문장들은 ';'으로 처리하여 영역의 긑을 표시한다.
       
       
     - 익명 블록(Anonymous PL/SQL)
          : 주로 일회성으로 사용할 경우 많이 사용된다.
           
     - 저장 블록(Stored PL/SQL)
          : 서버에 저장해 놓고 주기적으로 반복해서 사용할 경우 사용된다.
*/
-- EX)
 
set SERVEROUTPUT OFF;
set SERVEROUTPUT ON; -- dbms_output.put_line메서드를 이용해 출력을 가능케해주는 세팅
 
-- 익명 블럭(Anonymous PL/SQL) EX
DECLARE --선언부
  cnt integer;
BEGIN --실행부
  cnt := cnt+1; /* 할당 '=' 이 아니라 ':=' */
  -- null과 연산하면 결과는 null
  if cnt is null then
    dbms_output.put_line('결과 : cnt는 NULL입니다.'); -- 괄호 안 내용을 화면에 출력하는 메서드
  end if;
END;
 
/ -- 작성한 프로시저 실행
 
/*
  PL/SQL 문장 안에 SQL 문장이 포함될 수 있는데 이 SQL 문장은 SQL 실행자에 의해 실행된다.
  PL/SQL 문장은 프로시저 실행자가 처리->(PL/SQL엔진)이 수행한다.
   
  SQL실행자가 SQL문을 실행해 프로시저 실행자에게 넘겨준다.(돌려준다)
  그러면 그걸 PL/SQL엔진이 처리하게 된다.
*/
 
DECLARE
  empNo number(20);
  empName varchar2(10);
BEGIN
  select employee_id, first_name into empNo, empName -- empNo와 empName에 각 결과 필드값을 넣겠단 의미(into)
  from Employees
  where employee_id = 124;
 
  DBMS_OUTPUT.put_line(empNo||' '||empName); -- 각 변수 값을 화면에 출력
END;
 
/


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
--[ 시퀀스(sequence) ]
--  : mysql에서 auto_increment 를 대체하는 기능으로 값들이 자동으로 순차적으로 증가하여야하는
--    데이터 등에 주로 사용된다.
 
-- 연속적인 번호를 만들어 주는 기능
 
--사용Ex)
  -- 구문 형식 ]
      /*
         create sequence (시퀀스 이름)
         increment by n -> 시퀀스 번호의 증가값을 설정한다.(default 1)
         start with n -> 시작값 설정(default 1)
         maxvalue n | nomaxvalue <- 시퀀스 최대값 설정
         minvalue n | nominvalue <- 시퀀스 최소값 설정 : cycle 옵션일 경우 시작값
         cycle | nocycle <- 최대값을 지나면 최소값으로 순환되는 옵션 설정
         cache n | nocache <- 시퀀스의 속도를 개선하기위해 캐싱 여부 지정
          
      */
       
      -- [ 시퀀스 생성 ] : 제품번호를 생성하는 시퀀스 만들기
      create SEQUENCE seq_serial_no
        INCREMENT BY 1
        START WITH 100
        MAXVALUE 110
        MINVALUE 99
        CYCLE
        cache 2;
         
         
      create table good(
        good_no number(3),
        good_name varchar2(20)
      );
       
--      시퀀스를 이용해 삽입 : 시퀀스명.nextval : 다음 시퀀스 번호를 가져옴
                    --    : 시퀀스명.currval : 현재 시퀀스 번호를 가져옴
      insert into good values(seq_serial_no.nextval, '제품1');
       
      select * from good;
       
      select seq_serial_no.CURRVAL from dual;
       
      insert into good values(seq_serial_no.nextval, '제품2');
       
      insert into good values(seq_serial_no.currval, '제품3');
       
      -- 시퀀스 삭제
          --  drop sequence 시퀀스명
      drop sequence seq_serial_no;
       
      create sequence seq_serial_no2
      INCREMENT BY 1
      start with 100
      maxvalue 105
      cache 2;
       
      insert into good values(seq_serial_no2.nextval,'제품4');
      select * from good;
[    ORACLE 뷰(View) & 인라인 뷰   ]



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
-- 뷰(VIEW)란?
    -- : 뷰는 Table과 유사하지만 테이블과 달리 물리적인 공간이 필요하지 않은 가상의 테이블이다.
    -- : 테이블과 마찬가지로 select,insert,update,delete 명령이 가능하다.
     
    -- 사용 이유)
      -- 1. [보안관리를 위한 뷰]
          --  : 보안등급에 맞추어 컬럼 및 범위를 정하여 조회할 수 있도록 제공함
            --  (내가 공개하고 싶은 데이터만 table에서 뽑아서 제공하기 위함)
          -- 연산결과만 제공하고 알고리즘을 숨기기 위해 사용
          -- select list를 함수로 가공하여 update, insert를 못하도록 함
          -- 테이블 명이나 컬럼 명을 숨기기 위함
           
      -- 2. [사용편의성을 위한 뷰]
          -- 검색 조건을 단순화하여 사용할 수 있도록 함
              -- 테이블간 복잡하게 뽑아와야할 것을 그런 결과로 뽑은 하나의 뷰로 만들어 제공하면
              -- 쉽게 사용자는 조회할 수 있다.(for End User)
          -- 조인을 단순화
          -- 사용자를 위한 컬럼명이나 테이블명 제공
           
    -- EX)
     
      -- Employees 테이블에서 필요한 몇몇 컬럼 데이터들만 "뷰"로 뽑아와 보자.
      -- 뷰 생성
      -- job_id가 ST_CLERK인 사람들의 정보를 이용한 뷰
      create view v_emp(emp_id, first_name, job_id, hiredate, dept_id) -- view검색시 사용될 컬럼명 지정
        as
        select employee_id, first_name, job_id, hire_date, department_id
        from Employees
        where job_id = 'ST_CLERK';
         
         
      -- job_id가 SH_CLERK인 사람들의 정보를 이용한 뷰
      create view v_emp1(emp_id, first_name, job_id, hiredate, dept_id) -- view검색시 사용될 컬럼명 지정
        as
        select employee_id, first_name, job_id, hire_date, department_id
        from Employees
        where job_id = 'SH_CLERK';
       
       
      -- view를 이용한 조회
      select dept_id from v_emp1;
       
       
      -- view 제거
      drop view v_emp1;
       
       
      -- view 수정
      create or replace view v_emp(emp_id, first_name, job_id, hiredate, dept_id) -- view검색시 사용될 컬럼명 지정
        as
        select employee_id, first_name, job_id, hire_date, department_id
        from Employees
        where job_id = 'SH_CLERK'; -- 조건만 수정했음
         
      select * from v_emp;
       
       
      ---- [ view의 활용 ] ----
      -- 1. 보안상 목적
        -- 뷰를 만들 때 select문에서 함수를 써서 만들면 해당 뷰에 insert와 update를 할 수 없게 된다.
        -- 따라서, 별 의미없어보이지만 nvl함수를 써서 보안상 목적을 제공할 수 있다.(꼭 nvl일 필요는 없다.)
      create or replace view v_emp3(emp_id, first_name, job_id, hiredate, dept_id) -- view검색시 사용될 컬럼명 지정
        as
        select nvl(employee_id,null), nvl(first_name,null), job_id, nvl(hire_date,null), department_id
        from Employees
        where job_id = 'SH_CLERK';
         
      desc v_emp3;
       
      -- 변경 불가
      update v_emp3 set first_name = 'modify' where first_name='Julia';
       
      -- 연봉을 구하기 위한 긴 구문을 뷰를 이용해 편리하게 조회하는 예
      create view v_emp_salary(emp_id, last_name, annual_sal)
        as
        select employee_id, last_name, (( salary + nvl(commission_pct,0) ) * 12)
        from employees;
       
      -- annual salary를 편리하게 조회할 수 잇다.
      select * from v_emp_salary;
       
       
      -- 뷰의 수정을 막는 조회용 with read only!
      -- 조회만 가능한 view 생성!(read only view)
      create view v_emp_readonly(emp_id, last_name, annual_sal)
        as
        select employee_id, last_name, (( salary + nvl(commission_pct,0) ) * 12)
        from employees
      with read only; -- with read only하면 해당 뷰를 수정할 수 없게 설정한다.
       
      desc v_emp_readonly;
       
      select * from v_emp_readonly;
      -- 변경 불가(조회만 가능한 read only view이기 때문)
      update v_emp_readonly set last_name = 'kim'
      where last_name = 'Grant';
       
       
      create view v_sample1
      as
      select employee_id, last_name, department_id, hire_date
      from employees
      where (salary + nvl(commission_pct,0))*12 > 40000
      and department_id = 50
      and job_id = 'ST_CLERK'
      and sysdate - 365 * 5 > hire_date; -- 입사한지 5년이 넘은 사람 조건
       
      select * from v_sample1;
       
       
      create view 사원 (사번, 이름, 부서번호, 입사일)
      as
      select employee_id, first_name||' '||last_name, department_id, hire_date
      from employees
      where department_id = 50;
       
      select * from 사원;
       
       
      create view v_join(사번,이름,부서번호,부서명,입사일)
      as
      select emp.employee_id, emp.first_name || ' ' || emp.last_name, dept.department_id,
             dept.department_name, emp.hire_date
      from employees emp, departments dept
      where emp.department_id = dept.department_id (+);
       
      select * from v_join;


[ 인라인(Inline) 뷰(View) 란? ]

: select의 from절에 테이블이 바로 오는 것이 아니라, select절이 오는 방식


왜 사용할까?

-> 서브쿼리를 포함해 테이블 조인이 이루어질 경우 검색하는 필드명은 테이블.필드명처럼해서 길어지게 된다. 이게 서브쿼리를 통해

메인 쿼리로 올라갈 수록 그 길이는 점점 더 길어져 불편할 수 있는데

이때, from절에서 select문을 통해 추출한 필드명에 Alias를 줘서 간단하게 만들 수 있다.

이러한 목적 이외에도 예를들어, 전체 데이터에서 비교하는 것보다 rownum을 이용해서 10번째 튜플 정보만 가져와 그 중에서 조건을 따져 데이터를

가져온다면 비교하는 횟수가 줄어들게 할 수도 있다.

이때, rownum을 통해 가져오는 갯수를 제한하는 방식을 N-TOP 질의라고 한다.

EX)     select 사원명, 부서명 from ( select e.ename AS 사원명, d.dName AS 부서명 from emp e, dept d where e.deptno = d.deptno

where rownum <= 10

order by e.sal

);





'스터디 > DB(ORACLE)' 카테고리의 다른 글

ORACLE PL/SQL 개념 - (1)  (0) 2017.09.11
ORACLE 시퀀스(sequence)  (0) 2017.09.08
ORACLE 무결성 제약조건  (0) 2017.09.07
ORACLE DML( insert, update, delete, select )  (0) 2017.09.06
ORACLE DDL( create, drop, alter, truncate )  (0) 2017.09.06
[    ORACLE 무결성 제약조건    ]




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
--무결성 제약 조건1
    --  데이터가 올바르게 저장될 수 있도록 제약 조건을 거는 것
     
  -- 객체 무결성
      -- : 기본키 필드의 데이터는 null값을 가질 수 없으며, 중복된 값이 올 수 없다.
  -- 참조 무결성
      -- : 기본키를 참조하는 외래키(Foreign key)는 null은 허용하되, 참조하는 기본키
        -- 값들로 사용되는 값들만 올 수 있다.
        -- ex) 기본키값이 (A,B,C,D)고 외래키가 이 기본키를 참조한다면
        --     외래키값은 A,B,C,D,null 값들 중에 하나의 값만 입력될 수 있다.
   
  -------- [ 제약 조건 ] --------
  -- not null
      -- : NULL값이 입력되지 못하게하는 조건
   
  -- unique : 중복된 값이 입력되지 못하게 하는 조건
   
  -- primary key(PK,기본키) : not null + unique 조건
      --  = '객체 무결성'
  -- foreign key(FK,외래키) : 다른 테이블의 필드(컬럼)를 참조해서 무결성을 검사하는 조건
      --  = '참조 무결성'
   
  -- check : 주어진 값만 허용하는 조건
   
  -- EX)
   
  -- not null )
  create table null_test(
    col1 varchar2(20) not null, -- null 입력 불가
    col2 varchar2(20) null, -- null 입력 가능
    col3 varchar2(20) -- null 입력 가능
  );
   
  insert into null_test(col1,col2) values('aa','bb'); -- col3에 null들어감
  select * from null_test;   
   
  -- col1은 null이 불가하기 때문에 에러가 발생하게 됨
  insert into null_test(col2,col30) values('cc','dd');
   
  -- unique )
  create table unique_test(
    col1 varchar2(20) unique not null, -- unique + not null = primary key(PK)
    col2 varchar2(20) unique,
    col3 varchar2(20) not null,
    col4 varchar2(20) not null,
    constraint temp_unique unique(col3,col4) -- col3과 col4를 "조합"했을 때 중복되지 않도록
  );
   
  insert into unique_test(col1,col2,col3,col4)
  values('aa','bb','cc','dd');
   
  insert into unique_test(col1,col2,col3,col4)
  values('a2','b2','c2','d2');
   
  select * from UNIQUE_TEST;
   
  -- ERROR 발생 : unique로 중복을 허용하지 않게 해났기 때문에...
  update unique_test set col1 = 'aa' where col2 = 'b2';
   
  insert into unique_test(col1,col2,col3,col4)
  values('a3','','c3','d3'); -- col2에 null값이 가능함으로 들어간다.(스페이스없으면 null)
  select * from unique_test; -- unique는 null값은 가능함(중복 여부 비교 대상에서 제외함)
   
  insert into unique_test(col1,col2,col3,col4)
  values('a4','','c4','d4');
   
  -- Primary Key(PK,기본키) = UNIQUE + NOT NULL
    -- 1. 테이블 생성시 기본키 생성 방법
  create table primary_test(
    student_id number(20) primary key, -- 인라인 방식
    name varchar2(20)
  );
   
  create table primary_test2(
    student_id number(20),
    name varchar2(20),
    constraint student_id_pk primary key(student_id) -- 아웃라인 방식
    -- 인라인 방식에서는 아웃라인에서처럼 기본키의 이름을 지을 수 없다.
  );
   
    -- 2. 테이블 생성하고 나서 이후에 기본키를 생성하는 방법
    alter table primary_test drop primary key; -- 기존 기본키 지우기
    alter table primary_test add constraint student_id_pk2 primary key (student_id); -- 기본키 추가
     
   
  -- Foreign Key(FK,외래키)
  create table foreign_key(
    department_id constraint dept_fk references departments(department_id) -- 인라인 방식
  );
   
  create table foreign_key(
    department_id number(20),
    -- 아웃라인 방식
    constraint dept_fk2 foreign key (department_id) references departments(department_id)
  );
   
  -- 테이블 생성 뒤 fk 만들기
      --  ALTER TABLE 테이블명 DROP CONSTRAINT fk_table_id;
  alter table foreign_key drop constraint dept_fk2; -- fk 삭제
   
      --  ALTER TABLE 테이블명 ADD CONSTRAINT fk_table_id FOREIGN KEY(id) REFERENCES 참조테이블명(id);
  alter table foreign_key add constraints dept_fk3 foreign key(department_id) -- fk 추가
  references departments(department_id);
   
    -- 참조되고 있는 부모테이블의 데이터를 지우개되면??
        -- 그 없어질 데이터를 참조하고 있는 자식 테이블 때문에 에러가 발생함
        -- 따라서 지울 수가 없는대, 지울려면 2가지 방법이 있다.
        -- 1. 자식 테이블에서 해당 부모 테이블을 참조하는 데이터를 먼저 삭제한 뒤 부모테이블 데이터를 삭제한다.
        -- 2. casecade 키워드를 붙여 삭제한다.
         
         
         
  -- check )
  create table check_test(
    gender varchar2(10) not null constraint check_sex check(gender IN('M','F')) -- M,F만 입력 가능해짐
  );
   
  create table check_test2(
    gender varchar2(10) not null constraint check_sex check(gender IN('남성','여성')) -- 남성,여성만 입력 가능해짐
  );
   
  create table check_test3(
    score number(20) constraint score_check check(score between 0 and 100), -- 0~ 100 값만 입력 가능
    kor varchar2(10)
  );


[   ORACLE DML( insert, update, delete, select )   ]




--DML : Data Manipulation Language : ( select, insert, update, delete )

-- 1. select 문 : 데이터 검색
      -- 형식) select 컬럼 from 테이블 where 조건들.. group by having order by ...

-- 2. insert 문 : 데이터 삽입(튜플 삽입)
      -- 형식)

 - 단일행을 삽입하는 경우 : insert into 테이블(컬럼1,컬럼2,...) values(값1,값2,...)
 - 복수행을 삽입하는 경우 : insert into 테이블(컬럼1,컬럼2...) (select 컬럼1,컬럼2,... from ~ where ~);
      
-- 3. update 문 : 데이터 수정 
      -- 형식) update 테이블 set 컬럼1=값1 where 조건들...      

-- 4. delete 문 : 데이터 삭제(튜플 삭제)
      -- 형식) delete from 테이블 where 조건
      
delete * from 테이블명; 과 Truncate Table 테이블명과의 차이점?

: delete를 수행시 자동으로 commit이 이루어 지지 않기 때문에 rollback을 할 수 있지만
  
  truncate의 경우 데이터를 삭제 후 자동으로 commit까지 진행하기 때문에 rollback을 할 수 없다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
--DML : Data Manipulation Language : ( select, insert, update, delete )
 
-- 1. select 문 : 데이터 검색
      -- 형식) select 컬럼 from 테이블 where 조건들.. group by having order by ...
 
-- 2. insert 문 : 데이터 삽입(튜플 삽입)
      -- 형식) insert into 테이블(컬럼1,컬럼2,...) values(값1,값2,...)
       
-- 3. update 문 : 데이터 수정
      -- 형식) update 테이블 set 컬럼1=값1 where 조건들...     
 
-- 4. delete 문 : 데이터 삭제(튜플 삭제)
      -- 형식) delete from 테이블 where 조건
       
--ex)
create table sample(
  deptNo number(20),
  deptName varchar2(15),
  deptLoc varchar2(15),
  deptManager varchar2(10)
);
 
-- 데이터 튜플 삽입
insert into sample(deptNo,deptName,deptLoc,deptManager)
       values(1,'인사부','강남','홍길동');
 
select * from sample;
 
-- 필드명을 생략하고 추가할 수도 잇다. 생략하면 전 필드의 데이터를 다 넣어줘야함
insert into sample values(2,'개발부','군산','홍길순');
 
-- deptManager 값을 빼고 튜플을 삽입
insert into sample(deptNo,deptName,deptLoc) values(3,'관리부','광주');
select * from sample; -- deptManager값이 null로 들어감을 알 수 있다.
 
-- deptManager 값이 null인 것을 수정하자.
update sample set deptManager = '홍삼순' where deptManager IS NULL;
select * from sample;
 
update sample set deptLoc = '일산' where deptName = '인사부';
select * from sample;
 
-- 튜플 삭제
delete from sample where deptName = '관리부';
select * from sample;
 
-- commit 과 rollback
 
  -- commit : 최종적으로 명령 처리를 반영하는 명령어
  -- rollback : 직전에 수행한 명령을 수행하지 않은 상태로 돌리는 명령어
        -- transaction처리와 관련있다.
            -- 트랜잭션이란? 완전히 다 처리하거나 아예 처리되지 않은 상태로 돌리는 것으로
                -- 일부만 수행됨으로써 발생할 수 있는 심각한 오류를 방지하기 위함
 
-- rollback
commit;        


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
--[ DDL(Data Definition Language) : create, alter, drop, truncate를 이용한 테이블 관리 ]
 
-- desc 테이블명
    --  테이블 구조 보기(describe)
desc Employees;
desc member;
 
create table employees2(
  employee_id number(10) primary key,
  name varchar2(20),
  salary number(7,2) -- 7자리를 할당하고 소수점은 2자리란 의미
);
 
 
create table employees4(
  employee_id number(10), --constraint employees4_pk primary key,
  name varchar2(20),
  salary number(7,2), -- 7자리를 할당하고 소수점은 2자리란 의미
  --foreign key (employee_id) references employees2.employee_id
  constraint employees4_pk primary key(employee_id), -- 각 키 이름을 지정할 수 있는데
  -- 이름을 지정할 때는 constraint 라는 키워드를 붙여준다.
  constraint employees4_fk FOREIGN key(employee_id) REFERENCES employees2(employee_id) --  외래키 지정
);
 
 
-- 기존 테이블 항목을 그대로 만들고 싶을 때
create table employees3
as
select * from employees;
 
desc employees3;
 
-- 테이블 수정 : alter
-------------------------------------------------------------------------------
 
-- alter문)
-- employees2 테이블에 manager_id 항목 추가
alter table employees2 add (
    manager_id varchar2(10)
    );
 
desc employees2;
 
-- manager_id의 varchar2(20)을 varchar2(10)으로 변경해보자.
alter table employees2 modify(
  manager_id varchar2(20)
);
 
-- manager_id "컬럼"을 삭제하자.
alter table employees2 drop column manager_id;
desc employees2;
 
 
-- Primary Key 변경 방법
  -- 1. 먼저 primary key를 drop한다.
  -- 2. alter table 테이블명 add constraint 인덱스명 primary key(기존컬럼,추가컬럼)
alter table employees2 drop primary key; -- primary key 지우기
alter table employees2 add primary key(name); -- primary key 추가하기
desc employees2;
 
-- 현재 name이 기본키로 되어 있다. name뿐만아니라 employee_id + name 두개를 복합키로 지정해 보자.
alter table employees2 drop primary key;
alter table employees2 add primary key(name,employee_id);
----------------------------------------------------------------
 
-- drop 문)
 
-- employees2 테이블 삭제하기
drop table employees2;
----------------------------------------------------------------
 
-- rename문)
 
  --  테이블 이름 변경
-- member 테이블명을 member1으로 변경
rename member to member1;
desc member1;
--------------------------------------------------------------------
 
-- truncate 문)
    --  테이블 내에 있는 모든 레코드를 삭제하는 구문(drop은 테이블 자체를 지우고
        --  truncate는 레코드들만 비우는 것
-- employees3 테이블의 모든 레코드를 지워라.
truncate table employees3;
select * from employees3;
 
 
/* 문자형 데이터 타입
  char : 고정길이 문자형 타입(지정한 사이즈만큼,남아도 고정적으로 용량 차지),
  varchar2 : 가변길이(최대사이즈는 지정하지만, 입력한게 여분이 남으면 입력한대까지만 용량차지),
  nchar : 고정길이 유니코드 문자형 타입,
  nvarchar : 가변길이 유니코드 문자형 타입,
  long(2GB) : 가변길이형 문자 데이터 타입(2GB까지 가능)
*/
[   ORACLE 서브쿼리ANY,ALL,IN에 대하여...  ]




메인 쿼리 안에서 메인 쿼리의 중간 결과로 사용되는 쿼리문을 "서브쿼리"라고 하는데,

서브 쿼리를 작성할 때 고려해야할 점은, 서브쿼리의 결과값이 단일 튜플이 나올 수도 있지만 다중 튜플일 경우이다.

일반적으로 salary(급여)가 (13000,6000,7000) 이 중 하나인 경우( salary = 13000 or salary = 6000, salary = 7000 )는

간단하게 salary IN (서브 쿼리) 로 "IN"을 사용해 작성하면 되지만

">"와 같은 연산자는 IN을 사용할 수가 없는 단일 비교 연산자이다. 이러한 경우에는 ANY ALL을 사용하게 된다.

ANY는 해당 비교 연산의 or결과로 ALL은 and 결과로 연산이 되는데 예를들자면

salary > ANY(다중 튜플을 결과로하는 서브쿼리) 는 

salary > 13000 or salary > 6000 or salary > 7000 의 결과가 된다. 즉, salary > 6000을 만족하는 결과들이 될 것이고,

salary > ALL(다중 튜플을 결과로하는 서브쿼리) 는

salary > 13000 and salary > 6000 and salary > 7000의 결과임으로, salary > 13000 을 만족하는 결과가 나올 것이다.



다음 아래는 해당 내용에 대한 예제 코드와 주석문이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
--[ 서브 쿼리 ]
    -- 메인 쿼리 안에 또 다른 쿼리문이 들어가는 것!
    -- 메인쿼리를 구성하는 소단위 쿼리다.
    -- DML(select,insert,delete,update)에서 모두 사용가능하다.
    -- 서브쿼리의 결과 집합을 메인 쿼리가 중간 결과값으로 사용된다.
    -- 서브 쿼리 자체는 일반 쿼리와 다를 바가 없다.
 
     
     
-- 평균 급여보다 급여가 작은 사람들의 정보를 구할 때
select employee_id, first_name, last_name, salary
from employees
where salary < 6462; -- 이렇게하면 가변적으로 변하는 값에 대응할 수가 없음
 
 
 
-- 따라서, 서브쿼리를 이용해 평균을 구한 값을 중간 결과값으로 써서 구할 수 있다.
select employee_id, first_name, last_name, salary
from employees
where salary < (select round(avg(salary)) from Employees); -- where절에선 집계함수
-- 사용이 불가능하기 때문에 서브쿼리를 이용해 중간 결과값을 가지고 와야 한다.
 
 
 
--COUNTRY_ID값이 US인 location_id를 가지고와서 departments 테이블에 있는 부서명을 구해보자
select department_name from departments
-- 조건절에서 서브쿼리 결과가 여러 튜플이 나올 수 있을 땐 IN을 사용해야 한다.
where location_id IN (select location_id from locations where COUNTRY_ID = 'US');
 
 
 
--EX)Employees 테이블에서 월급이 가장 적은 사용자 정보(직급포함)를 가져와라
select emp.first_name, emp.last_name, job.JOB_TITLE
from Employees emp, jobs job
where salary = (select min(salary) from Employees) -- 서브쿼리
and emp.job_id = job.job_id; -- join(JOB_TITLE을 구하기 위함)
 
 
 
--평균 급여보다 많은 급여를 받는 사원 정보를 출력(단, 급여가 높은순에서 낮은순으로)
select emp.first_name || emp.last_name 이름, emp.salary 급여, j.JOB_TITLE 직급
from Employees emp, Jobs j
where salary > (select avg(salary) from Employees) and emp.job_id = j.job_id
order by emp.salary desc; -- 급여가 높은순으로 정렬
 
 
 
-- [ any, all 연산자 ] 를 활용한 서브쿼리
select salary
from Employees
where Department_id = 20;
 
--department_id가 20인 부서에서 받는 급여보다 많은 급여를 받는 사람
  -- 위의 서브쿼리 결과는 salary가 6000, 13000이 나옴
  -- 따라서, 6000보다 많이받아도되고, 13000보다 많이 받아도 되는 사람들을 출력하도록 하자.
select Employee_id, department_id, salary
from Employees
where salary > (select salary
                from Employees
                where Department_id = 20);
-- 이 경우는 오류가 날 것이다. > 연산자나 = 연산자는 하나의 결과값과 비교하는 것인데
-- 위의 경우 서브쿼리의 결과가 여러 튜플이 나오게 된다. 따라서 그것보다 크다는 비교를 할수가 없게된다.
-- in을 사용하려해도 in은 그 돈과 같은 돈을 받는 사람을 구하는 것이기 때문에 그것도 불가능함
-- 따라서, 아래처럼 해 주어야 한다.
 
 
-- ANY를 사용하자. (다중값이 나올 때 각 비교의 하위 고려 대상을 기준으로 된다.)
  -- ex) 6000,13000 두개의 값이 나왔을 때 salary < ANY(6000,13000)일 경우 13000보다 작은 값들
    --    salary > ANY(6000,13000)일 경우 6000보다 큰 값들이 나오게 됨
    -- 더 쉽게 말해, salary < 6000 or salary < 13000일 경우 출력되는 상황과 같음
    -- salary > 6000 or salary > 13000
     
    -- SUMMARY) 즉, ANY는 서브쿼리의 결과가 여러개 나올 때, 그 튜플 결과의 둘중 하나만이라도 만족시키는
              -- 값들은 다 나올 수 있도록 하는 "OR"와 같은 개념이고...
              -- ALL은 여러 튜플의 결과에 따른 비교 결과를 모두 만족시킬 수 있도록 나오게하는 "AND"와 같다.
select Employee_id, department_id, salary
from Employees
where salary < ANY(select salary
                   from Employees
                   where Department_id = 20);
 
-- ALL의 경우]
      --  따라서 다 만족하게 되는 결과가 나오게 됨으로 6000보다 작은 얘들만 나오게 됨
select Employee_id, department_id, salary
from Employees
where salary < ALL(select salary
                   from Employees
                   where Department_id = 20);


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
--조인(JOIN)
  -- : n개의 테이블간에 필요한 컬럼 정보를 하나로 출력하기 위해 사용됨
 
-- [ Inner JOIN ] : 완전히 일치하는 데이터만 나오는 방식(일반적인 조인 방식)
 
select e.First_name, e.Last_name, e.Email 이메일, d.DEPARTMENT_NAME 부서명
from Employees e, Departments d
where e.DEPARTMENT_ID = d.DEPARTMENT_ID; -- 두 정보가 완전히 일치하는 정보들만 출력됨
-- 즉, 이 경우는 값이 다른 값들은 출력되지 않음
-- 단순히 부서명 컬럼이 하나 더 필요해서 조인을 하는 경우는 이렇게하면 출력되지 않는 값들이 생겨
-- 원하는 데이터들을 뽑아내지 못할 수 있다.
 
select emp.first_name || emp.last_name 이름, emp.email 이메일, emp.department_id,
       dep.department_name 부서명, j.job_title 직급, l.CITY 지역
from Employees emp,
     Departments dep,
     jobs j,
     locations l
where emp.department_id = dep.department_id
      and emp.job_id = j.job_id
      and dep.location_id = l.location_id
      -- 여기까지 조인조건
      and l.city = 'Seattle'; -- 일반조건
           
 
--[ Self JOIN ]
    --  자신의 테이블에서 자신의 테이블을 조인해서 사용하는 방식
    --  왜 필요??
      -- ex) Employees 테이블에서 한 튜플의 직원 정보에서 그 사람의 상급자인 manager_id값이 있다.
          -- 이 manager_id는 상급자의 사원번호인데 이 사원번호 정보는 동일 테이블인 Employees에 있다.
          -- 왜? 상급자도 직원이기 때문에 같은 테이블에 정보가 있고 따라서 같은 테이블을 참고할 일이 생김
          -- 아래의 경우, 셀프조인을 하지 않으면 emp2.FIRST_NAME인 상사의 이름에 해당하는 필드는 가져올 방법이 없다.
 
select emp1.EMPLOYEE_ID, emp1.FIRST_NAME,
       emp2.EMPLOYEE_ID 상사ID,
       emp2.FIRST_NAME 상사이름
from Employees emp1, Employees emp2
where emp1.manager_id = emp2.employee_id;
 
------------------------------------------------------------------------------------------
 
 
--[ Outer JOIN(외부 조인) ]
  -- left outer join, right outer join, full outer join이 있다.
  -- full outer join은 ANSI 표준 문법으로만 작성이 가능하고 ORACLE 전용 문법으로는 불가능하다.
 
select emp.employee_id,
       emp.first_name,
       emp.department_id,
       dep.department_name
from Employees emp, departments dep
where emp.department_id = dep.department_id;
-- 106명 출력
 
select * from Employees; -- 107명 
-- 왜 106명 107명 다를까? 둘다 완전히 일치한 경우만 출력되기 때문(inner join), null값인 경우 출력안됐을 것...
-- 따라서, 이런 경우 Outer Join 방식을 사용해야 함
 
select emp.employee_id,
       emp.first_name,
       emp.department_id,
       dep.department_name,
       loc.CITY
from Employees emp, departments dep, locations loc
where emp.department_id = dep.department_id (+)
      and dep.location_id = loc.location_id (+);
-- 즉, 이러한 방식은 Oracle 전용 문법임, ANSI 표준은 from에 left outer join혹은 right outer join식으로 써야함
-- 즉, 오라클 문법에서 outer join은 (+)로 나타내며, 전부 출력되어야 하는 테이블의 반대편에 붙여주게 된다.
 
 
-- ANSI 표준 방식으로 작성한 3중 left outer join
select emp.employee_id,
       emp.first_name,
       emp.department_id,
       dep.department_name,
       loc.city
from Employees emp left outer join departments dep
  on emp.department_id = dep.department_id
  left outer join locations loc
  on dep.location_id = loc.location_id;
 
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
--Group by 절
      --  distinct처럼 중복되지 않도록 나오지만 각 튜플마다 집계함수와 연계되어
      --  각 튜플별로 그룹핑해 그 결과를 알고싶을 때 주로 사용된다.
      --  예를들어, 여러 학생들의 성적이 들어 있는 Student테이블이 있을 때
      --  A반,B반,C반... 반마다 평균 국어 성적을 구한다고 했을 때 반으로 그룹핑을 하고 avg로 평균을 구할 수 있다.
select distinct department_id from EMPLOYEES;
 
select department_id from Employees GROUP BY DEPARTMENT_ID;
 
--부서별 급여 합계 : 그룹별(부서별) 급여 총액 구하기
select DEPARTMENT_ID, sum(SALARY)
from Employees GROUP BY DEPARTMENT_ID;
 
--부서별,직급별 사원수와 평균 급여를 구해보자.
    -- job_id 도 같이 출력할 경우 DEPARTMENT_ID 때문에 일일이 하나하나 다 표시될 수 없기 때문에
    -- 이 job_id또한 GROUP BY를 걸어주어야 한다.
select DEPARTMENT_ID, job_id, sum(SALARY), count(*), round(avg(salary),2) from Employees
GROUP BY DEPARTMENT_ID, job_id;
 
select DEPARTMENT_ID, job_id, sum(SALARY), count(*), round(avg(salary),2) from Employees
GROUP BY DEPARTMENT_ID, job_id
ORDER BY DEPARTMENT_ID, job_id;
 
select DEPARTMENT_ID, job_id,
to_char(sum(salary),'999,999') 총급여,
to_char(avg(salary),'999,999') 평균급여
from Employees
where department_id = 80
GROUP BY DEPARTMENT_ID,job_id
ORDER BY DEPARTMENT_ID, job_id;
 
 
--having 절
  -- GROUP BY의 조건절
 
--현재 부서별 사원수가 10명 이상인 직원수 구하기
    --  집계 그룹과 관련된 조건(사원수 10명이상)이기 때문에 having절이 필요
    --  집계함수에 대한 조건을 사용할 때(sum,count,avg 등...) having절을 사용하고
    --  부서id가 80이고 null이 아닌...등의 조건은 where절을 사용하면 된다.
SELECT department_id 부서ID, count(*) 직원수
FROM Employees
where department_id is not null -- null이 아닌 부서 id만 출력할 것(<>사용하면 안됨)
GROUP BY department_id
HAVING count(*) > 10
ORDER BY department_id;
 
 
--rollup
 
--[ ROLLUP operator ]
-- ROLLUP구문은 GROUP BY 절과 같이 사용 되며, GROUP BY절에 의해서 그룹 지어진 집합 결과에 대해서 좀 더 상세한 정보를 반환하는 기능을 수행 한다.
-- SELECT절에 ROLLUP을 사용함으로써 보통의 SELECT된 데이터와 그 데이터의 총계를 구할 수 있다.
 
    -- : 그룹별 합계 정보를 추가해서 보여주는 함수
    -- Employees 테이블에는 department_id정보에 대한 부서Name 정보가 없는데
    -- Employees 테이블에 대해 GROUP BY를 해 출력할 경우 부서 테이블에 있는 부서명도 같이 출력하고 싶을 때
    -- rollup 함수를 이용하면 효과적으로 해당정보를 가져올 수 있다.(그룹별에서 조인을 쉽게할 수 있는 함수)
select l.CITY, d.DEPARTMENT_NAME, e.JOB_ID,
       count(*) 사원수, sum(e.salary) 총급여
from Employees e, departments d, locations l
where e.DEPARTMENT_ID = d.DEPARTMENT_ID
      and d.LOCATION_ID = l.LOCATION_ID
group by rollup(l.CITY, d.DEPARTMENT_NAME, e.JOB_ID) -- 그룹별 나뉘어진것 결과가 나오고 그 서브 합계도 추가로 출력되게된다.(rollup)
order by l.CITY, d.DEPARTMENT_NAME, e.JOB_ID;
-- 따라서 rollup을 쓰면 좀 더 세밀한 결과값들을 얻어올 수 있다.
[    ORACLE 날짜함수, 변환함수, decode(), case()  ]  





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
--[ ** 날짜 함수 ** ]
 
--sysdate : 현재 시스템 날짜를 구해옴
select sysdate AS 현재날짜 from dual;
 
--두 날짜 사이의 개월수를 출력하는 함수  : months_between(date1,date2) 
select ENAME,months_between(sysdate,hiredate) as 근무개월수 from EMP;
 
--개월수를 더하는 메서드 : add_months()
select add_months(sysdate,4) from dual; -- 현재 날짜에서 4개월을 더해줌
 
--다가올 특정일 날짜 가져오기 : next_day
select next_day(sysdate,'일요일') from dual; -- 현재날짜 기준 다가올 일요일의 날짜
 
--해당달의 마지막 일 수 구하기 : last_day()
select LAST_DAY(SYSDATE) from dual;
 
--형식을 지정해 날짜를 문자열로 변환시킨다. : to_char()
select to_char(sysdate,'yyyy-MM-dd') from dual;
 
--문자열을 날짜형으로 바까주는 메서드 : to_DATE()
select to_DATE('2017/10/27','yyyy/mm/dd') from dual;
 
--NULL일 경우 다른 값으로 치환하는 메서드(중요!! NULL과 산술연산시 결과는 무조건 NULL이기 때문에 적당한 처리 필요)   : NVL
select (NVL(sal,0) * 0.09) 세금 from EMP; -- sal값이 NULL인경우 0으로 계산되도록...
 
--switch문과 같은 역할을 하는 sql 메서드 : decode()
    -- deptno가 20일경우 '리서치부서'로 30일경우 '영업부'로 40일경우 '운영부'로 출력하는 코드
select DEPTNO, decode(deptno,20,'리서치부서',30,'영업부',40,'운영부') from DEPT;
 
--elseif문과 유사 : case()
select ENAME,DEPTNO,
  case when DEPTNO = 20 then '리서치부'
       when DEPTNO = 30 then '영업부'
       when DEPTNO = 40 then '운영부'
       else ' '
       end AS "부서명"
from EMP;


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
--[ 문자형 함수 ]
 
--문자열과 문자열을 연결하는 함수 : CONCAT(char1,char2) or || 를 이용
select CONCAT('HELLO','_World'), CONCAT('good','_bad') from dual;
select 'Hello' || 'World' from dual; -- ||연산자를 이용한 문자열 합치기
select sal || '원' from EMP;
 
--단어의 첫 문자를 대문자로 변환 : INITCAP(char)
select INITCAP('the soap') from dual; -- The Soap
select INITCAP('good/bad morning') from dual; -- Good/Bad Morning
 
--LOWER(char) / UPPER(char)
select LOWER('ABC') from dual;
select UPPER('abc') from dual;
 
--LPAD()
select LPAD('good',6) from dual; -- good이란 글자를 6자리로 늘림
select lpad('good',6,'@') "LPAD1" from dual; -- @@good 6자리로 확대하고 왼족에 3번째
-- 인자 문자를 채움
select lpad('good',8,'L') from dual; -- LLLLgood
 
-- RPAD()
select RPAD('good',6,'#') from dual; -- good##
-- 단, 한글은 2바이트니까 주의해야한다.
select RPAD('good','10','홍길동') from dual; -- good(4byte) + '홍길동'(6byte) 총 10byte필요.
 
-- trim() : LTRIM(), RTRIM(), trim()
select LTrim('goodbye','g') from dual; -- oodbye 왼쪽에 있는 g값을 지움
select lTrim('goodbye','o') from dual; -- goodbye 중간에 기여잇는 경우는 지울 수 없음 아래처럼 해야함
select lTrim('goodbye','go') from dual; -- dbye
 
select RTrim('goodbye','go') from dual; -- goodbye 오른쪽에서 go를 찾으니까 없음으로 안지워짐
select RTrim('goodbye','e') from dual;
 
-- 왼쪽 삭제 'leading'
select trim(leading from ' good ') from dual; -- good
select length(trim(leading from ' good ')) from dual; -- 5 : leading은 왼쪽기준 공백 제거함으로 뒤에 5개 길이가 나옴
-- 오른쪽 삭제 'trailing'
select trim(trailing from ' good ') from dual;
-- 양쪽 : 'both'
select trim(both from ' good ') from dual; -- 양쪽 공백을 지움
-- 왼쪽기준에서 g문자를 지움
select trim(leading 'g' from 'good') from dual; -- ood
 
--substr('문자열',시작인덱스,문자열개수(길이)) : 부분적인 문자열을 뽑아내는 함수(시작 인덱스가 1임!!)
select substr('good morning john',8,4) from dual; -- rnin
select substr('good morning john',6) from dual; -- morning john 길이를 생략하면 그 뒤로 쭉 다 출력된다.
select substr('good morning john',-4) from dual; -- john 오른쪽 끝에서부터 거꾸로...
select substr('good morning john',-4,2) from dual; -- jo
select substr('good morning john',instr('good morning john','morning',1),length('morning')) from dual;
 
--replace('원문자열','바꿀문자열','교체할문자열') : 문자열 교체
select replace('good morning john','good','switch') from dual; -- good문자열을 switch로 변경 : switch morning john
 
--translate() : replace와 약간 다른데
  -- translate('문자열','You','We')라하면 1:1로 대응해서 바끼게된다. Y는 W로 o는 e로
select translate('You are not alone','You','We') from dual; -- We are net alene
 
-- ASCII() : 각 문자열의 아스키값을 구함
select ascii('a') from dual; -- 97
 
-- 문자열 길이 : length()
select length('안녕') from dual; -- 2
 
-- instr('문자열','찾고자하는문자',어느인덱스부터찾을지,몇번째꺼찾을지) : 찾고자하는 문자열의 시작 위치를 반환(시작인덱스는 1부터...)
select instr('good morning john','mor',1) from dual; -- 6
select instr('good morning john','n',1,2) from dual; -- 11 두번째 n시작위치를 구한다.
-- 1번째 인덱스부터 찾기 시작해서 2번째 n의 위치를 가져오는 것
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
--[ 기본함수 : 집계함수, 숫자함수 ]
 
--sum(): 합계
select sum(salary) AS '임금합계' from employees;
 
--count() : 튜플(레코드) 수 출력
select count(*) from employees;
--전체 개수(중복포함), 중복을 배제한 개수 출력
select count(all First_name), count(distinct First_name) from Employees;
 
--avg() : 평균
select avg(salary) as '임금평균' from Employees;
WHERE department_id = 80;
 
--max() : 최대값
select max(salary) as '가장높은임금' from Employees;
--고용일이 가장 최근인 직원
select max(hire_date) from Employees;
--주의!! : 집계함수는 조건절에서는 아래와같이 사용할 수 없음!!
--select first_name,last_name from Employees where hire_date = max(hire_date);
--따라서 서브쿼리를 이용해서 해야한다.!!
    -- 이런식으로...
select First_name,Last_name from Emplyees where hire_date = (select max(hire_date) from Employees);
 
--min() : 최소값
select min(salary) from Employees;
 
--[ number function ]
 
--abs() : 절대값
--    이런 경우는 테이블을 쓸 필요 없음으로 오라클이 제공해주는 더미(dummy)테이블인 dual을 써서하면 된다.
select ABS(-23) from dual;
 
--sign() : 양수(1) 음수(-1) 0(0)
select sign(23), sign(-23), sign(0) from dual;
 
--round() : 반올림
select round(0.123), round(2.543) from dual;
--반올림할 자리수를 지정할 수도 있다.
select round(0.12345678,6), round(2.345678,4) from dual;
 
--trunc(n1,n2) : 잘라내기, n2생략시 0이 default
select trunc(0.12345,2) from dual;
select trunc(1234.1234,-1) from dual; -- 1230 출력
 
--ceil() : 무조건 올림 함수(큰 정수 값)
select ceil(32.8) from dual; -- 33
 
--floor() : 무조건 내림 함수(작은 정수 값)
select floor(32.8) from dual;
 
--power(n1,n2) : 제곱을 구해주는 함수
select power(4,2) from dual; -- 4의 2승
 
--mod(n1,n2) : 나머지 구하기 : n1 % n2
select mod(777,4) from dual;
 
--sqrt() : 제곱근 구하기
select sqrt(16) from dual;
select sqrt(2) from dual;

+ Recent posts