TABLE에 대한 사소한 몇 가지의 의문 해결

2015. 10. 25. 21:26

-- 첫번째 의문 : NOT NULL과 PRIMARY KEY

궁금증 1) NOT NULL 제약조건과 PRIMARY KEY 제약조건을 같이 쓸수 있을까? --> YES


아래 DDL문은 이채남 저 오라클 실습 1장에 나오는 예제 테이블 생성 쿼리이다. EMP_ID에 NOT NULL과 PRIMARY KEY 제약조건을 같이 줬는데, PK 자체가 NOT NULL에 Unique니까 이렇게 잡아주는게 의미가 있나? 또는 틀린 구문은 아닐까? 라는 의문이 들었다.


CREATE TABLE TEMP2 (

 EMP_ID      NUMBER NOT NULL PRIMARY KEY,

 EMP_NAME    VARCHAR2(10) NOT NULL,

 BIRTH_DATE  DATE,

 DEPT_CODE   VARCHAR2(06) NOT NULL,

 EMP_TYPE    VARCHAR2(04),

 USE_YN      VARCHAR2(01) NOT NULL,

 TEL         VARCHAR2(15),

 HOBBY       VARCHAR2(30),

 SALARY      NUMBER,

 LEV         VARCHAR2(04)

);


아래 쿼리를 수행시켜서 확인해봤다.

select a.owner, a.constraint_name, a.constraint_type, a.table_name, b.column_name, a.search_condition from user_constraints a, user_cons_columns b

where a.owner=b.owner

and a.constraint_name=b.constraint_name

and a.table_name=b.table_name

and a.table_name='TEMP2';


OWNER   CONS_NAME CONS_TYPE TAB_NAME COL_NAME SEARCH_CONDITION

PRAC01 SYS_C0011311 C TEMP2 EMP_ID "EMP_ID" IS NOT NULL

PRAC01 SYS_C0011312 C TEMP2 EMP_NAME "EMP_NAME" IS NOT NULL

PRAC01 SYS_C0011313 C TEMP2 DEPT_CODE "DEPT_CODE" IS NOT NULL

PRAC01 SYS_C0011314 C TEMP2 USE_YN "USE_YN" IS NOT NULL

PRAC01 SYS_C0011315 P TEMP2 EMP_ID


위의 결과에서 EMP_ID 컬럼에 PK constraint 와 NOT NULL Constraint 조건이 함께 생성된걸 확인 할 수 있다.

만약 첫번째 DDL문에서 not null 조건을 뺀 채 생성한다면 어떻게 될까?


OWNER   CONS_NAME CONS_TYPE TAB_NAME COL_NAME SEARCH_CONDITION

PRAC01 SYS_C0011328 C TEMP2 EMP_NAME "EMP_NAME" IS NOT NULL
PRAC01 SYS_C0011329 C TEMP2 DEPT_CODE "DEPT_CODE" IS NOT NULL
PRAC01 SYS_C0011330 C TEMP2 USE_YN "USE_YN" IS NOT NULL
PRAC01 SYS_C0011331 P TEMP2 EMP_ID

위의 결과처럼 EMP_NAME에 대해 NOT NULL constraint는 생성되지 않는다.

궁금증 2) PRIMARY KEY  컬럼에 Null을 명시해도 오류가 나지 않는가? 테이블이 생성되는가? --> YES

CREATE TABLE TEMP2 (

 EMP_ID      NUMBER NULL PRIMARY KEY,

 EMP_NAME    VARCHAR2(10) NOT NULL,

 BIRTH_DATE  DATE,

 DEPT_CODE   VARCHAR2(06) NOT NULL,

 EMP_TYPE    VARCHAR2(04),

 USE_YN      VARCHAR2(01) NOT NULL,

 TEL         VARCHAR2(15),

 HOBBY       VARCHAR2(30),

 SALARY      NUMBER,

 LEV         VARCHAR2(04)

);


결과적으로 위와 같이 생성해도 아~무 문제도 없다. 언뜻 생각하면 헷갈릴 수 있지만 각 컬럼값에 대해 NULL이 default 값이란걸 생각해보면 당연한 결과이다. 즉 위의 쿼리는 아래의 쿼리와 동일하다.


CREATE TABLE TEMP2 (

 EMP_ID      NUMBER PRIMARY KEY,

 EMP_NAME    VARCHAR2(10) NOT NULL,

 BIRTH_DATE  DATE,

 DEPT_CODE   VARCHAR2(06) NOT NULL,

 EMP_TYPE    VARCHAR2(04),

 USE_YN      VARCHAR2(01) NOT NULL,

 TEL         VARCHAR2(15),

 HOBBY       VARCHAR2(30),

 SALARY      NUMBER,

 LEV         VARCHAR2(04)

);


그리고 당연하게도 PK가 해당 컬럼에 있기 때문에, 테이블 생성 시 NULL을 명시했다고 해서 해당 컬럼이 NULL인 레코드가 테이블에 insert되는 경우는 발생하지 않기 때문에 안심하시라.


궁금증 3)  NOT NULL(NULL), PRIMARY KEY 구문의 순서가 바뀌어도 되는가? --> YES

실험으로 확인했으나, 간단한 부분이니 따로 결과를 올리지는 않았다.


-- 두번째 의문 : DROP TABLE 구문

DROP TABLE구문은 다음과 같다.

DROP TABLE schema_name.table_name [cascade constraints] [purge];


궁금증 4) cascade constraints 구문

일단 헷갈리는 부분인 Foreign Key에 대해 정리하고 넘어가자. Foreign Key 생성 예제는 다음과 같다.

ALTER TABLE employees

ADD CONSTRAINTS fk_deptno FOREIGN KEY (department_id)

REFERENCES departments (department_id);


여기서 자식 테이블은 employees, 부모 테이블은 departments가 된다. 즉, Foreign Key constraint(이하 Foreign Key)는 자식테이블에 생성되는 것이다. 또한 Foreign Key는 referential integrity constraint(참조 무결성 제약조건)과 같은 말이다.

도표로 표현하면 다음과 같다.


자식테이블                                     부모테이블

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

Foreign Key  --> refers to (참조)  -->   Primary Key or Unique Key

                                           *referential integrity relationship (참조 무결성 관계) 


다시 DROP TABLE 구문으로 돌아가서, cascade constraints구는 만약 drop될 테이블이 부모테이블로서 참조되고 있다면, 자식테이블의 참조하는 Foreign Key 제약조건을 함께 삭제하라는 명령이다. 왜냐하면 자식 테이블의 외래키에 의해 참조되고 있는 테이블은 Drop 될 수 없기 때문이다.

결국 drop할 테이블이 부모 테이블이 아니라면 cascade constraints 구문은 불필요하다.


사족) Foreign Key 제약조건과 관련하여 끊임없이 헷갈리는 이유는 짧은 경험 때문이기도 하지만, 자식테이블에 FK를 생성할 때 부모테이블에 대응되는 무언가(?)가 없기 때문이지 않나... 라는 생각이 든다. 따라서 "자식테이블에 FK를 생성하면, 부모 테이블엔 referential integrity relationship(참조 무결성 관계)가 생긴다." 정도로 정리를 해두려고 한다. 물론 엄밀히 따지면 부-자 테이블 간에 해당 관계가 성립되는 것이겠지만...



궁금증 5) Purge 구문과 Constraints

아시다시피 purge 구문은 oracle 10g의 신 기능인 recycle bin과 관련된 구문이다. 즉 테이블을 drop 했을 때 purge하기 전까지는 해당 테이블은 recycle bin에 place되어 복구가 가능하다. 그런데 constraints와 관련하여..

일단 테이블 드랍

drop table temp2;


그리고 다음 구문으로 조회를 한다.

select a.owner, a.constraint_name, a.constraint_type, a.table_name, b.column_name, a.search_condition from user_constraints a, user_cons_columns b

where a.owner=b.owner

and a.constraint_name=b.constraint_name

and a.table_name=b.table_name

and a.table_name like 'BIN%';


OWNER   CONS_NAME CONS_TYPE TAB_NAME COL_NAME SEARCH_CONDITION

PRAC01 BIN$IFoPPCQFRgiZ37xFqNreYA==$0 C BIN$rcwgjLNxTnyUVLV7CgSwgw==$0 EMP_ID "EMP_ID" IS NOT NULL

PRAC01 BIN$VQtSPYN1SoWRJruYXqAABg==$0 C BIN$rcwgjLNxTnyUVLV7CgSwgw==$0 EMP_NAME "EMP_NAME" IS NOT NULL

PRAC01 BIN$za5xbIlVT9u3ju49LJEoDA==$0 C BIN$rcwgjLNxTnyUVLV7CgSwgw==$0 DEPT_CODE "DEPT_CODE" IS NOT NULL

PRAC01 BIN$JRARQl33RA2Yup/70nI8eQ==$0 C BIN$rcwgjLNxTnyUVLV7CgSwgw==$0 USE_YN "USE_YN" IS NOT NULL

PRAC01 BIN$33sqjnJjRJqWelc3yo2tkg==$0 P BIN$rcwgjLNxTnyUVLV7CgSwgw==$0 EMP_ID


다음과 같이 constraints도 완전히 삭제 되지 않고 replace 되었음을 확인할 수 있다.

그리고 recycle bin을 purge 한다.

purge recyclebin;


다시 조회하면 recycle bin에 replaced되었던 constraints도 삭제 되었음을 확인할 수 있다.

만약 해당 테이블을 복원할 필요가 없거나, 백업이 잘 되어 있어서 recycle bin 보관이 필요치 않은 경우라면, 다음 구문으로 깔끔하게 constraints 까지 정리하는 편이 좋겠다.


drop table temp2 purge;


곰돌곰둘 Oracle_DB_Unix admin/미분류

실습 56. 리스트 파티션 테이블 생성과 관리

2015. 10. 13. 01:08

※ 책에서 제시하는 실습용 스키마를 생성하지 않았기 때문에 다소 예제가 다르나, 내용은 대동소이합니다. SCOTT 유저의 기본 스키마 (EMP, departments) 등을 이용합니다.


(1)파티션 테이블 생성

create table scott.emp_list_part (

EMPNO NUMBER(22),

ENAME VARCHAR2(10),

JOB VARCHAR2(9),

MGR NUMBER(22),

HIREDATE DATE,

SAL NUMBER(22),

COMM NUMBER(22),

DEPTNO NUMBER(22),

constraint pk_list_empno primary key(empno)

using index tablespace indx

)

partition by list (job)

(

partition job_entry values ('CLERK','ANALYST') tablespace t1,

partition job_middle values ('MANAGER','SALESMAN') tablespace t2,

partition job_high values ('PRESIDENT') tablespace t3);


(2)데이터 삽입

insert into emp_list_part

select * from emp;


commit;


(3)파티션 등 확인

select * from user_tab_partitions

where table_name = 'EMP_LIST_PART';


select table_name, partitioned from user_tables;


select name, column_name

from user_part_key_columns

where name = 'EMP_LIST_PART';


(4)파티션 드랍 (alter table을 사용)

alter table emp_list_part

drop partition job_entry;


(5)데이터 재 삽입 --> 오류가 발생한다. partition key가 list에 존재하지 않기 때문!

insert into emp_list_part

select * from emp

where job in ('CLERK','ANALYST');

ORA-14400: inserted partition key does not map to any partition


(6)파티션 추가 (역시 alter table을 사용)

alter table emp_list_part

add partition job_entry values ('CLERK','ANALYST') tablespace t1;


(7)데이터 재 삽입 --> 어랏? 에러 다시 발생

insert into emp_list_part

select * from emp

where job in ('CLERK','ANALYST');


SQL 오류: ORA-01502: 인덱스 'SCOTT.PK_LIST_EMPNO'또는 인덱스 분할영역은 사용할 수 없은 상태입니다

01502. 00000 -  "index '%s.%s' or partition of such index is in unusable state"

*Cause:    An attempt has been made to access an index or index partition

           that has been marked unusable by a direct load or by a DDL

           operation

*Action:   DROP the specified index, or REBUILD the specified index, or

           REBUILD the unusable index partition


무엇이 문제인지 확인해본다.


dba_objects 조회 --> 정상


select owner, object_name, object_type, status from dba_objects

where owner = 'SCOTT'

and object_type= 'INDEX';


SCOTT PK_LIST_EMPNO INDEX VALID

SCOTT PK_EMP INDEX VALID

SCOTT PK_DEPT INDEX VALID


dba_indexes 조회 --> UNUSABLE로 문제가 생긴걸 알 수 있다.


select owner, index_name, status from dba_indexes

where owner = 'SCOTT';


SCOTT PK_DEPT VALID

SCOTT PK_EMP VALID

SCOTT PK_LIST_EMPNO UNUSABLE


-- 그럼 일단 REBUILD를 해준다.

alter index PK_LIST_EMPNO rebuild;


(8)데이터 재 삽입

insert into emp_list_part

select * from emp

where job in ('CLERK','ANALYST');


commit;

--> 인덱스 REBUILD 후에는 정상적으로 데이터가 삽입됨을 알 수 있다.


-- 이상의 실습에서 알 수 있는점

1. 새로운 레코드가 파티션 테이블에 insert 될 때, 오라클이 자동적으로 key column에 따라 각 파티션으로 배분해준다. 이때 key column 값이 list 에 없는 값이라면 ORA-14400 에러가 발생하며 값이 insert 되지 않는다.


2. (1)번 과정에서 인덱스를 비파티션 인덱스로 만들었다. 그 후 각 테이블 파티션(이 예제에서는 job_entry의 drop)에 변경이 발생하자 index가 unusable 상태로 변경된걸 확인할 수 있었다. (PK 제약조건이 딸린 INDEX가 unusable 상태로 변경되었기 때문에 새 레코드의 insert가 불가능) --> 이후 Rebuild를 통해 인덱스를 valid 상태로 만든 후에야 레코드의 insert가 가능했다.


즉, 파티션 테이블에 비파티션 인덱스가 구성되어 있다면, 특정 테이블 파티션에 변경(split, exchange, drop 등)을 가하고 난 후에는 반드시 INDEX를 REBUILD 해 주어야 한다.


--> 이런 불편함을 감소시키기 위해서 로컬 파티션 인덱스를 사용할 수 있다.


실습 재개

(9) 인덱스 삭제 (PK_LIST_EMPNO)

alter table emp_list_part

drop constraint pk_list_empno;

(* PK Constraint에 해당하는 index는 drop 할 수 없으므로, 대신 PK Constraint를 삭제 해준다. 그럼 자동적으로 해당 INDEX도 DROP 된다. 


참고

drop index PK_LIST_EMPNO

오류 보고 -

SQL 오류: ORA-02429: 고유/기본 키 적용을 위한 인덱스를 삭제할 수 없습니다.

02429. 00000 -  "cannot drop index used for enforcement of unique/primary key"

*Cause:    user attempted to drop an index that is being used as the

           enforcement mechanism for unique or primary key.

*Action:   drop the constraint instead of the index.)


(10) 로컬 파티션 인덱스 생성 --> 에러발생!


alter table emp_list_part add constraint pk_list_empno primary key(empno)

using index local tablespace indx;


SQL 오류: ORA-14039: 열을 분할영역한 것은 UNIQUE 인덱스로 키 열의 부분 집합을 폼 합니다

14039. 00000 -  "partitioning columns must form a subset of key columns of a UNIQUE index"

*Cause:    User attempted to create a UNIQUE partitioned index whose

           partitioning columns do not form a subset of its key columns

           which is illegal

*Action:   If the user, indeed, desired to create an index whose

           partitioning columns do not form a subset of its key columns,

           it must be created as non-UNIQUE; otherwise, correct the

           list of key and/or partitioning columns to ensure that the index'

           partitioning columns form a subset of its key columns


구문은 위와 같으나 에러가 발생했다. 그 이유는 local partition index의 대상 컬럼은 파티션 키 컬럼(여기서는 job)과 같아야 하기 때문이다.

empno를 조회 조건으로 많이 사용하기 때문에(가정) empno를 버릴 수는 없다. 따라서 다음과 같이 수정하여 인덱스를 생성하였다.


(11) 로컬 파티션 인덱스 생성 (local non-prefixed index) --> 성공

* 첫번째 컬럼이 empno, 즉 list partitioning key column이 아니기에 non-prefixed가 된다.


alter table emp_list_part add constraint pk_list_empno primary key(empno, job)

using index local tablespace indx;


생성 확인) DBA_OBJESTS에서는 생성이 된걸 확인할 수 있으나, DBA_SEGMENTS에서는 확인되지 않는다.

이 때 SEGMENT_TYPE을 INDEX PARTITION으로 해야 조회가 가능하다.

*SEGMENT_TYPE이 INDEX가 아닌 INDEX PARTITION인 까닭 : LOCAL PARTITION INDEX라는 개념 자체가 인덱스 파티션과 테이블 파티션이 1:1 매칭이 되는 걸 전제로 하고 있기 때문이다.


추가 확인) INDEX 파티션 생성여부, 구성 컬럼, Constraint 조건 확인


select * from dba_ind_partitions --> 인덱스 파티션은 여기서 확인할 수 있다.

where index_owner = 'SCOTT';


select * from dba_ind_columns

where index_owner = 'SCOTT';


select * from dba_constraints

where constraint_name = 'PK_LIST_EMPNO';


(12)테스트: 테이블 파티션 DROP

실제로 local index를 사용한 경우, 테이블 파티션 변경시에도 INDEX가 VALID로 유효한지 확인해보자.


alter table emp_list_part

drop partition job_entry; --> 테이블 파티션 DROP


인덱스 상태확인)

select owner, object_name, object_type, status from dba_objects

where owner = 'SCOTT'

and object_type= 'INDEX';


SCOTT PK_LIST_EMPNO INDEX VALID

SCOTT PK_EMP INDEX VALID

SCOTT PK_DEPT INDEX VALID

--> dba_objects 확인. 일단 정상이다


select owner, index_name, status from dba_indexes

where owner = 'SCOTT';


SCOTT PK_DEPT VALID

SCOTT PK_EMP VALID

SCOTT PK_LIST_EMPNO N/A

--> dba_indexes 확인. 상태가 N/A이다. (아까전 비 파티션 인덱스 때 UNUSABLE이었던것과 비교해보자)


select index_name, partition_name, status from dba_ind_partitions

where index_owner = 'SCOTT';


PK_LIST_EMPNO JOB_MIDDLE USABLE

PK_LIST_EMPNO JOB_HIGH USABLE

--> 나머지 인덱스 파티션의 경우 상태가 USABLE임을 확인 할 수 있다.


우선 PK Constraints가 정상인지 먼저 확인 해보자.


insert into emp_list_part

select * from emp

where job in ('MANAGER'); --> 일부러 중복되는 값을 insert

SQL 오류: ORA-00001: 무결성 제약 조건(SCOTT.PK_LIST_EMPNO)에 위배됩니다


--> PK Constraints가 정상적으로 작동함을 확인 할 수 있다.


delete emp_list_part

where empno='7499'

and job='SALESMAN';

commit; --> INSERT가 정상적으로 되는지 확인하기 위해 일부러 값을 삭제함.


insert into emp_list_part

select * from emp

where empno='7499'

and job='SALESMAN';

commit; --> INSERT가 정상적으로 되며, 따라서 PK 제약조건이 딸린 INDEX도 문제 없이 사용가능함을 확인하였다.

최종적으로 DROP 했던 테이블 파티션을 다시 추가해주고, 해당 파티션에 속했던 record들이 정상적으로 INSERT되는지 확인해보자.


(13) 파티션 추가 및 레코드 INSERT

alter table emp_list_part

add partition job_entry values ('CLERK','ANALYST') tablespace t1; --> 파티션 추가 완료


*추가 후 dba_objects, dba_ind_partitions에서 STATUS가 각각 valid, usable로 조회가 되나, 여전히 DBA_INDEXES에서는 N/A로 조회됨을 알수 있다.


insert into emp_list_part

select * from emp

where job in ('CLERK','ANALYST');

commit; --> 레코드 추가 완료


이로써 로컬 파티션 인덱스 생성시에는 특정 테이블 파티션에 대한 변경(drop, split, exchange 등)이 발생하더라도 해당 INDEX가 unusable 되지 않음을 확인하였다.


(참고) 

참고1.DBA_INDEXES의 STATUS에서 N/A는 무엇을 말하는 것일까? oracle reference를 읽어보자

--> Indicates whether a nonpartitioned index is VALID or UNUSABLE

nonpartition index일 경우에 대해서만 valid/unusable을 말해주고, partition index일 경우 N/A (해당 없음)이라는 것. partition index를 생성하면 초기값 부터가 N/A이다.


한편 ALL_IND_PARTITIONS의 STATUS 설명을 보면,

--> Indicates whether the index partition is usable (USABLE) or not (UNUSABLE)

즉 파티션 인덱스(=인덱스 파티션)은 dba_ind_partitions에서 조회하는 것이 맞다는 것이다.


참고2. 하지만 여전히 DBA_INDEXES 조회시 N/A로 나타나는게 찜찜하다... Rebuild를 하면 어떨까?

alter index PK_LIST_EMPNO rebuild;

오류 보고 -

SQL 오류: ORA-14086: 분할영역된 인덱스는 전체를 다시 만들 수 없습니다

14086. 00000 -  "a partitioned index may not be rebuilt as a whole"

*Cause:    User attempted to rebuild a partitioned index using

           ALTER INDEX REBUILD statement, which is illegal

*Action:   Rebuild the index a partition at a time (using

           ALTER INDEX REBUILD PARTITION) or drop and recreate the

           entire index


설명 처럼 파티션 인덱스는 개별적으로 (한번에 하나) rebuild 하여야 하며, 구문도 다르다. 예제 -->

alter index PK_LIST_EMPNO rebuild partition job_entry;


참고3. 파티션 인덱스 생성 구문

create index emp_list_part_idx1 on emp_list_part (job) local;

생각외로 간단하며 뒤에 local만 붙여주면 된다. 단 파티션 인덱스는 tablespace 미 지정시 해당 USER의 Default tablespace가 아니라 '짝지' 테이블 파티션과 동일한 tablespace에 위치하게 되므로, 이것이 싫을 경우 tablespace를 지정해주자. 구문은 다음과 같다.


create index emp_list_part_idx1 on emp_list_part (job) local

(partition list_p_idx1_entry tablespace indx,

partition list_p_idx1_middle tablespace indx,

partition list_p_idx1_high tablespace indx);


테이블 파티션과 인덱스 파티션의 매칭 결과는 어디서 볼 수 있는가? 역시 dba_ind_partitions에서 확인 가능하다.

SCOTT EMP_LIST_PART_IDX1 NO LIST_P_IDX1_HIGH 0 'CLERK', 'ANALYST'

SCOTT EMP_LIST_PART_IDX1 NO LIST_P_IDX1_ENTRY 0 'MANAGER', 'SALESMAN'

SCOTT EMP_LIST_PART_IDX1 NO LIST_P_IDX1_MIDDLE 0 'PRESIDENT'

SCOTT PK_LIST_EMPNO NO JOB_HIGH 0 'PRESIDENT'

SCOTT PK_LIST_EMPNO NO JOB_MIDDLE 0 'MANAGER', 'SALESMAN'

SCOTT PK_LIST_EMPNO NO JOB_ENTRY 0 'CLERK', 'ANALYST'


위에서부터 3번째까지 레코드를 보면 알겠지만, 초기 설계의도와는 다르게 매칭이 뒤죽박죽이 되었다... 인덱스 생성시 테이블 파티션을 지정해 줄수 없는가? --> 방법을 못 찾음... 찾으면 알려주시길 ㅠㅜ


내가 찾아낸 방법은 rename이 최선이었다. 구문은 다음과 같다 -->

alter index EMP_LIST_PART_IDX1 rename partition LIST_P_IDX1_HIGH to LIST_P_IDX1_ENTRY;

곰돌곰둘 Oracle_DB_Unix admin/(5)Self Training_오라클 관리

DATA TYPE - DATE 옆에는 Length를 쓰면 안돼요!

2015. 10. 12. 22:36

30분 동안 끙끙거린 문제...

왜 다음 문장이 오류가 발생하는가?


create table scott.emp_list_part (

EMPNO NUMBER(22),

ENAME VARCHAR2(10),

JOB VARCHAR2(9),

MGR NUMBER(22),

HIREDATE DATE(7),

SAL NUMBER(22),

COMM NUMBER(22),

DEPTNO NUMBER(22),

constraint pk_list_empno primary key(empno)

using index tablespace indx

)

partition by list (job)

(

partition job_entry values ('CLERK','ANALYST') tablespace t1,

partition job_middle values ('MANAGER','SALESMAN') tablespace t2,

partition job_high values ('PRESIDENT') tablespace t3);


--> 답은 DATE 때문이다. DATE TYPE 옆에는 LENGTH를 기재하지 않는다.


create table scott.emp_list_part (

EMPNO NUMBER(22),

ENAME VARCHAR2(10),

JOB VARCHAR2(9),

MGR NUMBER(22),

HIREDATE DATE,

SAL NUMBER(22),

COMM NUMBER(22),

DEPTNO NUMBER(22),

constraint pk_list_empno primary key(empno)

using index tablespace indx

)

partition by list (job)

(

partition job_entry values ('CLERK','ANALYST') tablespace t1,

partition job_middle values ('MANAGER','SALESMAN') tablespace t2,

partition job_high values ('PRESIDENT') tablespace t3);


수정완료.

곰돌곰둘 Oracle_DB_Unix admin/미분류

Oracle Database Administrator's Guide (1)

2015. 10. 11. 20:27

<Getting Started with Database Administration>


- SQL Plus를 사용하는 법 (connect remotely)

(1) Oracle DB 설치 (엔진 + Client)

(2) Oracle Client 설치

(3) Instant Client 설치


- Instant ID = System ID = SID


For a local connection, you identify the instance by setting operating system environment variables.

For a remote connection, you identify the instance by specifying a network address and a database 

service name. For both local and remote connections, you must set environment 

variables to help the operating system find the SQL*Plus executable and to provide the 

executable with a path to its support files and scripts. 


- environment variables

1)ORACLE_HOME

2)ORACLE_SID

3)ORACLE_HOME/bin

곰돌곰둘 Oracle_DB_Unix admin/(4)Oracle Manual

Oracle Manual 진도표

2015. 10. 11. 18:37

(1)Oracle Database Administrator's Guide

10/11(일) 47P ~ 51P

곰돌곰둘 Oracle_DB_Unix admin/미분류

[TIP] SELECT 절 서브쿼리 사용시 주의점

2015. 10. 8. 16:07

이렇게 수행 시 에러 발생함.


select a.employee_id, a.first_name||' '||a.last_name names, (select b.department_name

from employees a, departments b

where a.department_id = b.department_id) dep_names

from employees a

where a.department_id = 100;


ORA-01427: single-row subquery returns more than one row

01427. 00000 -  "single-row subquery returns more than one row"

*Cause:    

*Action:


--> 요렇게 고쳐서 수행해야 함


select a.employee_id, a.first_name||' '||a.last_name names, (select b.department_name

from departments b

where a.department_id = b.department_id) dep_names

from employees a

where a.department_id = 100;

곰돌곰둘 Oracle_DB_Unix admin/미분류

PL/SQL 서브프로그램

2015. 10. 8. 16:05

- 묻지마 블록(Anonymous block)의 경우 실행 시 마다 항상 컴파일을 수행해야 함. 이런 불편함을 줄이기 위해 1회 컴파일 후 반복해서 사용할 수 있는 블록을 오라클 객체로 제공함. --> 이것이 PL/SQL 서브 프로그램임.


- PL/SQL 서브프로그램 (PL/SQL subprogram)

  * 파리미터 고유의 이름을 가진 PL/SQL 블록

  * 내장 프로시져 및 함수를 통칭

  * 함수는 결과값을 반환하므로 SELECT나 WHERE 절 등에 위치 할 수 있음.


-- 함수 (내장 함수, 사용자 정의 함수, User Defined Function)


(예제1)

CREATE OR REPLACE FUNCTION hr.emp_salaries (emp_id number)

           RETURN NUMBER IS

    nSalaries NUMBER(9);        -- 여기까지 함수 명세부(specification)

BEGIN                           -- 여기서부터 구현부(body)


  nSalaries := 0;

   SELECT salary

     INTO nSalaries

     FROM hr.employees

    WHERE employee_id = emp_id;

    RETURN nSalaries;

  END;


(예제2)

SELECT 절에 함수 사용하기


select employee_id, first_name, emp_salaries(employee_id)

  from employees

 where department_id = 100;


-- 함수 활용하기

employees 테이블에서 각 사원의 부서명을 알고 싶을 경우

1) employees 테이블, departments 테이블 조인

2) select 절에 서브쿼리 사용 (서브쿼리 내에서는 조인이 사용됨)

3) FUNCTION 생성 후 사용


1. 함수만들기


create or replace function get_dep_name (dep_id number)

                 return varchar2 is

                 

               sDepName varchar2(30);

          begin

              select department_name

                into sDepName

                from departments

               where DEPARTMENT_ID = dep_id;

          

          RETURN sDepName;

END;


2. 사용하기


select employee_id, first_name||' '||last_name names, get_dep_name(department_id) dep_names

from employees

where department_id = 100;


--> 함수를 사용할 경우 조인이 발생하지 않는다!

이처럼 함수를 사용할 경우 성능상 이점이 발생하나, 함수 개수가 많아지는 등 관리상의 문제가 발생할 수 있으므로 경우에 따라 조인, 서브쿼리, 함수중에서 적절한 것을 택해 사용한다.


참고. 서브쿼리 사용시


select a.employee_id, a.first_name||' '||a.last_name names, (select b.department_name

from departments b

where a.department_id = b.department_id) dep_names

from employees a

where a.department_id = 100;



-- 프로시져 (내장 프로시져, Stored procedure)


(예제) 신입사원 등록 프로시져 만들기


create or replace procedure register_emp (

                                         f_name VARCHAR2, -- *자리수 명시 X

                                         l_name VARCHAR2,

                                         e_acct VARCHAR2,

                                         j_id   VARCHAR2)  IS

begin

  insert_into employees (employee_id, first_name, last_name, email, hire_date, job_id)

                 values (employee_seq.nextval, f_name, l_name, e_acct, sysdate, j_id);

commit;

  

exception when others then  

  dbms_output.put_line(f_name||' '||l_name||' register is failed!');

rollback;

end;


* 프로시저의 파라미터로 명시할 때는 VARCHAR2만 명시한다. 구체적인 자리수(BYTES)까지 명시하게 되면 오류가 발생한다.


create or replace procedure register_emp (

                                         f_name VARCHAR2,

                                         l_name VARCHAR2,

                                         e_acct VARCHAR2,

                                         j_id   VARCHAR2)  IS

begin

  insert into employees (employee_id, first_name, last_name, email, hire_date, job_id)

                 values (employees_seq.nextval, f_name, l_name, e_acct, sysdate, j_id);

commit;

  

exception when others then  

  dbms_output.put_line(f_name||' '||l_name||' register is failed!');

rollback;

end;


(예제) 프로시져 사용하기

exec register_emp ('조영', '대', 'djoyoung', 'IT_PROG');

곰돌곰둘 Oracle_DB_Unix admin/(3)뇌자극 오라클 PL/SQL

PL/SQL 문장과 커서

2015. 10. 8. 15:24

*홍형경 저/ 뇌를 자극하는 오라클 PL/SQL 프로그래밍


-- IF문

set serveroutput on


declare

  grade char(1);

begin

  grade := 'B';

  

  if grade = 'A' THEN

     dbms_output.put_line('Excellent');

  ELSIF grade = 'B' THEN

     dbms_output.put_line('Good');

  ELSIF grade = 'C' THEN

     dbms_output.put_line('Fair');

  ELSIF grade = 'D' THEN

     dbms_output.put_line('Poor');

  END IF;

END;


-- CASE문

declare

  grade char(1);

begin

  grade := 'B';

  

CASE grade

  WHEN 'A' THEN

     dbms_output.put_line('Excellent');

  WHEN 'B' THEN

     dbms_output.put_line('Good');

  WHEN 'C' THEN

     dbms_output.put_line('Fair');

  WHEN 'D' THEN

     dbms_output.put_line('Poor');

  ELSE

     dbms_output.put_line('Not Found');

  END CASE;

END;


-- LOOP문 (1)

declare

  test_number integer;

  result_num  integer;

begin

  test_number := 1;

  

  loop

    result_num := 2 * test_number;

    if result_num > 20 then

      exit;

    ELSE

      dbms_output.put_line(result_num);

    end if;

    test_number := test_number + 1;

  end loop;

end;


-- LOOP문(2)

declare

  test_number integer;

  result_num  integer;

begin

  test_number := 1;

  

  loop

  result_num := 2* test_number;

  

  exit when result_num > 20;

  

  dbms_output.put_line(result_num);

  test_number := test_number +1;

  end loop;

end;


※ (2)번 예제에서처럼 EXIT WHEN 문을 쓰면 IF 구문을 추가로 사용하지 않아도 되기 때문에 구문이 깔끔해진다.


-- WHILE LOOP 문

declare

  test_number integer;

  result_num  integer;

begin

  test_number := 1;

  result_num := 0;

  

while result_num <  20 loop

  

  result_num := 2 * test_number;

  dbms_output.put_line(result_num);

  test_number := test_number + 1;

  

  end loop;

end;


-- FOR ... LOOP 문

declare

  test_number integer;

  result_num  integer;

begin

  test_number := 1;

  result_num := 0;


  dbms_output.put_line('<<first>>');

  <<first>>

  for test_number in 1..10 loop

  

    result_num := 2 * test_number;

    dbms_output.put_line(result_num);

  end loop;

  

  dbms_output.put_line('<<second>>');

    

  result_num := 0;

  <<second>>

  for test_number in reverse 1..10 loop

   

    result_num := 2 * test_number;

    dbms_output.put_line(result_num);

  end loop;

  

  end;


-- GOTO문

declare

  test_number integer;

  result_num  integer;

begin

  test_number := 1;

  result_num := 0;

  

  GOTO second;

  dbms_output.put_line('<<first>>');

  <<first>>

  FOR test_number IN 1..10 LOOP

  

    result_num := 2 * test_number;

    dbms_output.put_line(result_num);

  end loop;

  

  <<second>>

  result_num := 0;

  dbms_output.put_line('<<second>>');

  FOR test_number IN REVERSE 1..10 LOOP

  

    result_num := 2 * test_number;

    dbms_output.put_line(result_num);

  end loop;

END;ㅈ


-- NULL 문

NULL 문이 사용되는 목적 : 보통 NULL 문장은 예외처리시 자주 사용된다. 즉 사용자가 정의한 특정 예외가 발생하였을 경우나 EXCEPTION WHEN OTHERS, 즉 EXCEPTION 절에서 명시한 예외 이외의 오류가 발생하였을 경우에 NULL 문을 명시한다.


예제(1) IF문을 사용했을 경우

  --> IF문 사용시 예외값에 대한 처리(ELSE절)이 없더라도 오류를 발생시키지 않는다.


declare

  grade char(1);

begin

  grade := 'Z';

  

  if grade = 'A' THEN

     dbms_output.put_line('Excellent');

  ELSIF grade = 'B' THEN

     dbms_output.put_line('Good');

  ELSIF grade = 'C' THEN

     dbms_output.put_line('Fair');

  ELSIF grade = 'D' THEN

     dbms_output.put_line('Poor');

  END IF;

END;


예제(2) CASE문을 사용했을 경우

  --> CASE문 사용시 1. WHEN절이 모든 가능한 경우를 포함하거나 

                         2. 예외값에 대한 처리(ELSE절)가 반드시 포함되어야 한다.

                         그렇지 않을 경우 에러가 발생하게 된다.


declare

  grade char(1);

begin

  grade := 'Z';

  

CASE grade

  WHEN 'A' THEN

     dbms_output.put_line('Excellent');

  WHEN 'B' THEN

     dbms_output.put_line('Good');

  WHEN 'C' THEN

     dbms_output.put_line('Fair');

  WHEN 'D' THEN

     dbms_output.put_line('Poor');

  END CASE;

END; 


오류 보고 -

ORA-06592: CASE not found while executing CASE statement

ORA-06512: at line 6

06592. 00000 -  "CASE not found while executing CASE statement"

*Cause:    A CASE statement must either list all possible cases or have an

           else clause.

*Action:   Add all missing cases or an else clause.


이 경우 NULL 절을 이용하면 간단히 처리할 수 있다.


declare

  grade char(1);

begin

  grade := 'Z';

  

CASE grade

  WHEN 'A' THEN

     dbms_output.put_line('Excellent');

  WHEN 'B' THEN

     dbms_output.put_line('Good');

  WHEN 'C' THEN

     dbms_output.put_line('Fair');

  WHEN 'D' THEN

     dbms_output.put_line('Poor');

  ELSE NULL; --> 아무것도 처리하지 않음

  END CASE;

END;


-- 커서

 정의: 쿼리에 의해 반환되는 결과셋(*메모리 상에 위치)에 접근하기 위해 사용되는 Cursor.

  * 명시적 커서(explicit cursor)란 사용자가 직접 쿼리의 결과에 접근해서 이를 사용하기 위해 명시적으로 선언한 커서를 말한다.

   커서 선언 --> 커서 오픈 --> 패치 --> 커서 닫기


  * 묵시적 커서(implicit cursor)란 오라클 내부에서 각각의 쿼리 결과에 접근하여 사용하기 위한 내부적 커서라 할 수 있다.


(예제) 명시적 커서


DECLARE                                 -- 커서 선언부

  CURSOR emp_csr IS

  SELECT employee_id

    FROM hr.employees

   WHERE department_id = 100;

   

   emp_id hr.employees.employee_id%TYPE;

BEGIN

    OPEN emp_csr;                        -- 커서 오픈

    

 LOOP

    FETCH emp_csr INTO emp_id;           -- 커서 패치

EXIT WHEN emp_csr%NOTFOUND;

          dbms_output.put_line(emp_id);

      END LOOP;

      

      CLOSE emp_csr;                     -- 커서 닫기

  END;


(참고) 명시적 커서
  이런식으로 쉽게 활용이 가능하다


  DECLARE                                 -- 커서 선언부

  CURSOR emp_csr IS

  SELECT employee_id, first_name, last_name

    FROM hr.employees

   WHERE department_id = 100;

   

   emp_id hr.employees.employee_id%TYPE;

   emp_f_name hr.employees.first_name%TYPE;

   emp_l_name hr.employees.last_name%TYPE;

BEGIN

    OPEN emp_csr;                        -- 커서 오픈

    

 LOOP

    FETCH emp_csr INTO emp_id, emp_f_name, emp_l_name;           -- 커서 패치

EXIT WHEN emp_csr%NOTFOUND;

          dbms_output.put_line(emp_id);

          dbms_output.put_line(emp_f_name);

          dbms_output.put_line(emp_l_name);

      END LOOP;

      

      CLOSE emp_csr;                     -- 커서 닫기

  END;


--명시적 커서의 속성

%FOUND : 커서 오픈시 NULL, 패치 시 TRUE, 패치 완료시 FALSE 반환

%ISOPEN : 커서 오픈시 TRUE 반환

%NOTFOUND : 더 이상 패치할 로우가 없음을 의미

%ROWCOUNT: 커서 오픈시 0, 패치 1건당 1씩 증가


-- 묵시적 커서(Implicit Cursor)

  선언, 오픈 등의 작업을 할 필요가 없음

  명시적 커서의 속성을 묵시적 커서에도 동일하게 사용할 수 있음

  가장 최근에 실행된 SQL문장에 대한 커서 = SQL커서


(예제1)

  DECLARE

    count1 NUMBER;

    count2 NUMBER;

  BEGIN

    SELECT count(*)

      INTO count1

      FROM hr.employees

     WHERE department_id = 100;

     

     count2 := SQL%ROWCOUNT;

     dbms_output.put_line('SELECT COUNT IS '||count1);

     dbms_output.put_line('ROW COUNT IS '||count2);

     

  END;

곰돌곰둘 Oracle_DB_Unix admin/(3)뇌자극 오라클 PL/SQL

PL/SQL 객체

2015. 10. 4. 08:26

*뇌를 자극하는 오라클 프로그래밍 (홍형경 저)의 정리입니다.

(블럭1: Counter Is Null)

declare

  counter integer;

begin

  counter := counter +1;

  if counter is null then

    dbms_output.put_line('Result : COUNTER IS Null');

end if;

end;

/


set serveroutput on;


(블럭2: 구구단 출력)

declare

  counter integer;

  i integer;

begin

  for i in 1..10 loop

  counter := (2 * i);

  dbms_output.put_line(' 2 * '||i||' = '||counter);

  end loop;

end;

/


(블럭3: 예외출력하기)

- 수정 전

declare

  counter integer;

begin

  counter := 10;

  counter := counter / 0;

  dbms_output.put_line(counter);

end;


- 수정 후

declare

  counter integer;

begin

  counter := 10;

  counter := counter / 0;

  dbms_output.put_line(counter);

exception when others then

  dbms_output.put_line('ERRORS');

end;


- 수정 후 (2) - 에러 내용 명시

declare

  counter integer;

begin

  counter := 10;

  counter := counter / 0;

  dbms_output.put_line(counter);

exception when zero_divide then

  dbms_output.put_line('ERRORS');

end;

/


- 수정 후 (3) - 0으로 나누기 시도시 1로 나누도록


declare

  counter integer;

begin

  counter := 10;

  counter := counter / 0;

  dbms_output.put_line(counter);

exception when zero_divide then

  counter := counter / 1;

  dbms_output.put_line(counter);

end;


<콜렉션의 사용>


(예제)

declare

  type varray_test is varray(3) of integer;

    -- integer 형 요소 3개로 구성된 varray 타입 선언

  type nested_test is table of varchar2(10);

    -- varchar2(10)형 요소로 구성된 nested table 타입 선언 (최대값이 없다)

  type assoc_array_num_type is table of number index by pls_integer;

    -- 키는 PLS_INTEGER 형이며 값은 number형인 요소들로 구성된 associative array 타입1

  type assoc_array_str_type is table of varchar2(32) index by pls_integer;

    -- 키는 PLS_INTEGER 형이며 값은 varchar2(32)형인 요소들로 구성된 associative array 타입2

  type assoc_array_str_type2 is table of varchar2(32) index by varchar2(64);

  -- 키는 VARCHAR2(64) 형이며 값은 varchar2(32)형인 요소들로 구성된 associative array 타입3

  

  varray1 varray_test; -- varray1 : 변수 / varray_test : 타입(헷갈리면 안됨)

  nested1 nested_test; 

  

  assoc1 assoc_array_num_type; 

  assoc2 assoc_array_str_type;

  assoc3 assoc_array_str_type2;

  

  begin

  varray1 := varray_test(1,2,3);

  nested1 := nested_test('A','B','C','D');

  

  assoc1(3) := 33; -- 키는 3, 값은 33

  assoc2(2) := 'TT'; -- 키는 2 값은 TT

  assoc3('O') := 'ORACLE'; -- 키는 O, 값은 ORACLE

  assoc3('K') := 'KOREA'; -- 키는 K, 값은 KOREA

  

  dbms_output.put_line(varray1(1)); -- varray_test의 첫번째 요소값을 출력, 결과는 1

  dbms_output.put_line(nested1(2)); -- nested_test의 두번째 요소값을 출력, 결과는 B

  

  dbms_output.put_line(assoc1(3)); -- 키 값이 3인 요소값을 출력, 결과는 33

  dbms_output.put_line(assoc2(2)); -- 키 값이 2인 요소값을 출력, 결과는 TT

  dbms_output.put_line(assoc3('O')); -- 키 값이 O인 요소값을 출력, 결과는 ORACLE

  dbms_output.put_line(assoc3('K')); -- 키 값이 K인 요소값을 출력, 결과는 KOREA

  end;

  /


<사용자 정의 데이터 타입(User Defined Data Type, TYPE 객체>


콜렉션을 필요할 때마다 다시 정의해서 사용하는 것은 번거롭기 때문에, 자주 사용되는 콜렉션은 데이터베이스 객체 (TYPE)으로 지정하여 사용할 수 있다. 이것을 사용자 정의 데이터 타입이라고 한다.


  create type alphabet_typ as varray(26) of varchar(2);

  

  declare

    test_alph alphabet_typ;

  begin

    test_alph := alphabet_typ('A','B','C','D');

    

  dbms_output.put_line(test_alph(2));

  

  end;

  /


<콜렉션과 레코드>

콜렉션과 레코드: PL/SQL에서 제공하는 데이터 타입.


(1)콜렉션 : PL/SQL에서 사용되는 배열(Array)형태의 데이터 타입을 통칭하여 콜렉션이라고 한다.

  *배열(Array)이란? : 같은 데이터타입으로 구성된 요소(element)들의 집합.

  +콜렉션의 종류 : 총 3가지

    1. varray(variable array) : 고정 길이를 가진 배열. 선언시 전체 크기가 명시되어야 하며, 각 요소가 순서대로 참조(정의)되어야 한다. 각 요소는 인덱스(=순서번호)를 통해 접근된다.

    2. 중첩테이블(nested table) : varray와 흡사하나 전체 크기를 명시할 필요가 없다는 점, 각 요소가 순서대로 참조(정의) 될 필요가 없다는 점에서 차이점을 갖는다.

    3. Associative array(연관배열, index-by table) : 키와 값의 쌍으로 구성된 콜렉션. 각 요소는 키에 의해 접근된다.

  +참고: 콜렉션은 다른 프로그래밍 언어에서 사용하는 1차원 배열형태의 구조를 갖고 있음.


(2)레코드 : 테이블 형태(2차원)의 데이터 타입. 여러 개의 필드(=컬럼)으로 구성되어 있으며, 해당 필드는 각기 다른 데이터 타입을 가질 수 있다.

  +쓰임 : 레코드가 테이블 형태의 구조를 갖고 있으므로, 실제 대부분의 경우 테이블의 데이터를 읽어오거나 조작하기 위해 PL/SQL 블록 내에서 임시적인 데이터 저장소 역할을 수행한다.

   +참고: 레코드는 다른 프로그래밍 언어에서 사용하는 구조체(Structure) 형태를 갖고 있음.


<콜렉션과 레코드 - 구문형식>

  1. VARRAY

      TYPE 타입명 IS {VARRAY | VARYING ARRAY } (크기) OF 요소데이터 타입 [NOT NULL];

  2. Nested Table

      TYPE 타입명 IS TABLE OF 요소데이터타입 [NOT NULL];

  3. associative array

      TYPE 타입명 IS TABLE OF 요소데이터타입 [NOT NULL] INDEX BY [PLS_INTEGER | BINARY_INTEGER | VARCHAR2(크기)]


  4. 레코드

      TYPE 레코드이름 IS RECORD (필드1 데이터타입1, 필드2 데이터타입2, ...);

 

  참고1. %TYPE

      변수명 스키마명.테이블명.컬럼명%TYPE;

  참고2. %ROWTYPE

      레코드이름 스키마명.테이블명%ROWTYPE;

  참고3. 커서%ROWTYPE을 이용한 레코드 선언

      레코드이름 커서명%ROWTYPE;


예제- 레코드

declare

    -- type으로 선언한 레코드

    type record1 is record (dep_id number not null := 300,

                             dep_name varchar2(30),

                             man_id number, loc_id number);

    -- 위에서 선언한 record1을 받는 변수 선언

    rec1 record1;

    

    -- 테이블명%ROWTYPE을 이용한 레코드 선언

    rec2 hr.departments%rowtype;

    

    cursor c1 is

       select department_id, department_name, location_id

       from hr.departments

       where location_id = 1700;

       

    -- 커서명%rowtype을 이용한 레코드 선언

    rec3 c1%rowtype;

    

    begin

     -- record1 레코드 변수rec1의 dep_name 필드에 값 할당.

     rec1.dep_name := '레코드부서1';

     

     -- rec2 변수에 값 할당

     rec2.department_id := 400;

     rec2.department_name := '레코드부서2';

     rec2.location_id := 2700;

     

     -- rec1의 레코드 값을 departments 테이블에 INSERT

     INSERT INTO hr.departments values rec1;

     

     -- rec2의 레코드 값을 departments 테이블에 INSERT

     INSERT INTO hr.departments values rec2;

     

     -- 커서 오픈

     OPEN c1;

     

     loop

     -- 커서값을 rec3에 할당한다. 개별 값이 아닌 department_id, department_name, location_id 값이 레코드 단위로 할당된다.

     fetch c1 into rec3;

     dbms_output.put_line(rec3.department_id);

     exit when c1%notfound;

     end loop

     

     commit;

     exception when others then

     rollback;

     end;

     /

곰돌곰둘 Oracle_DB_Unix admin/(3)뇌자극 오라클 PL/SQL

[Oracle] 인라인뷰

2015. 10. 3. 23:48

(정의) 인라인뷰: SQL문장에서 FROM 절에 사용된 서브쿼리


데이터베이스 객체인 뷰와 비슷하나, 인라인 뷰는 서브쿼리라는 특성 때문에 하나의 SQL 문장에서만 사용가능하며 데이터베이스 객체가 아니다. 임시성 뷰라고 생각할 수 있다.


인라인 뷰의 필요성(예제)


select a.employee_id, a.first_name||' '||a.last_name, a.salary

from employees a

where a.salary >= (select avg(salary) from employees)

and a.salary <= (select max(salary) from employees)

order by a.salary desc;


--> 만약 평균 급여, 최대 급여까지 함께 보여 주고 싶다면?


select a.employee_id, a.first_name||' '||a.last_name names, a.salary, round(b.avgs), b.maxs

from employees a, (select avg(salary) avgs, max(salary) maxs from employees) b

where a.salary between b.avgs and b.maxs

order by a.salary desc;

* from 절에 2개의 table이 명시되었으나, Join 구문이 포함되지 않았으므로 양 테이블의 catenation 곱으로 표현되었다. (총 레코드 개수(51) = a레코드 개수(51) * b레코드 개수(1))


인라인 뷰의 필요성(예제2)


월별 입사 현황 구하기


*레코드 개수 12개 결과 만들기

select 

decode(to_char(hire_date,'mm'), '01', count(*), 0) "1월",

decode(to_char(hire_date,'mm'), '02', count(*), 0) "2월",

decode(to_char(hire_date,'mm'), '03', count(*), 0) "3월",

decode(to_char(hire_date,'mm'), '04', count(*), 0) "4월",

decode(to_char(hire_date,'mm'), '05', count(*), 0) "5월",

decode(to_char(hire_date,'mm'), '06', count(*), 0) "6월",

decode(to_char(hire_date,'mm'), '07', count(*), 0) "7월",

decode(to_char(hire_date,'mm'), '08', count(*), 0) "8월",

decode(to_char(hire_date,'mm'), '09', count(*), 0) "9월",

decode(to_char(hire_date,'mm'), '10', count(*), 0) "10월",

decode(to_char(hire_date,'mm'), '11', count(*), 0) "11월",

decode(to_char(hire_date,'mm'), '12', count(*), 0) "12월"

from employees

group by to_char(hire_date,'mm')

order by to_char(hire_date,'mm');


*레코드 개수 1개 결과 만들기


select sum(m1) "1월", sum(m2) "2월", sum(m3) "3월", sum(m4) "4월", sum(m5) "5월", sum(m6) "6월", sum(m7) "7월", sum(m8) "8월", sum(m9) "9월", sum(m10) "10월", sum(m11) "11월", sum(m12) "12월"

from (

select decode(to_char(hire_date,'mm'), '01', count(*), 0) m1,

decode(to_char(hire_date,'mm'), '02', count(*), 0) m2,

decode(to_char(hire_date,'mm'), '03', count(*), 0) m3,

decode(to_char(hire_date,'mm'), '04', count(*), 0) m4,

decode(to_char(hire_date,'mm'), '05', count(*), 0) m5,

decode(to_char(hire_date,'mm'), '06', count(*), 0) m6,

decode(to_char(hire_date,'mm'), '07', count(*), 0) m7,

decode(to_char(hire_date,'mm'), '08', count(*), 0) m8,

decode(to_char(hire_date,'mm'), '09', count(*), 0) m9,

decode(to_char(hire_date,'mm'), '10', count(*), 0) m10,

decode(to_char(hire_date,'mm'), '11', count(*), 0) m11,

decode(to_char(hire_date,'mm'), '12', count(*), 0) m12

from employees

group by to_char(hire_date,'mm')

);


인라인 뷰의 필요성(예제3)


연봉 순위 상위 10명 조회하기

select *

from (

select employee_id, first_name, last_name, salary

from employees

order by salary desc

)

where rownum <= 10;


*뇌를 자극하는 오라클 프로그래밍(홍형경 저) 참조함

곰돌곰둘 Oracle_DB_Unix admin/(3)뇌자극 오라클 PL/SQL