{*}1)급여체계(SAL)(b)*
직급(JOB_GRD/PK),호봉(SAL_CLASS/PK),기본급(BASE_SAL)
{*}2)사원(EMP)(a)*
사번(EMPNO/PK),성명(NAME),주소(ADDR),직급(JOB_GRD/FK),호봉(SAL_CLASS),직무(JOB_KIND),퇴직일(RES_DATE)
{*}3)가족(FAM)(d)*
일련번호(SEQ/PK),관계(REL),성명(NAME),사번(EMPNO/FK)
{*}4)근태(WORK)©*
일자(WORK_DATE/PK),사번(EMPNO/PK/FK),근태코드(WORK_CD),근무시간(WORK_TIM)
SQL>
CREATE TABLE ORA9.SAL (
2 JOB_GRD VARCHAR2(10) NOT NULL
3 , SAL_CLASS VARCHAR2(2) NOT NULL
4 , BASE_SAL NUMBER(10) NOT NULL
5 )
6 TABLESPACE ORA9;
테이블이 생성되었습니다.
SQL>
CREATE TABLE ORA9.EMP (
2 EMPNO VARCHAR2(10) NOT NULL
3 , NAME VARCHAR2(10) NOT NULL
4 , ADDR VARCHAR2(30) NOT NULL
5 , JOB_GRD VARCHAR2(10) NOT NULL
6 , SAL_CLASS VARCHAR2(2) NOT NULL
7 , JOB_KIND VARCHAR2(10) NOT NULL
8 , RES_DATE VARCHAR2(8) NULL
9 )
10 TABLESPACE ORA9;
테이블이 생성되었습니다.
SQL>
CREATE TABLE ORA9.FAM (
2 SEQ VARCHAR2(10) NOT NULL
3 , REL VARCHAR2(10) NOT NULL
4 , NAME VARCHAR2(10) NOT NULL
5 , EMPNO VARCHAR2(10) NOT NULL
6 )
7 TABLESPACE ORA9;
테이블이 생성되었습니다.
SQL>
CREATE TABLE ORA9.WORK (
2 WORK_DATE VARCHAR2(8) NOT NULL
3 , EMPNO VARCHAR2(10) NOT NULL
4 , WORK_CD VARCHAR2(10) NOT NULL
5 , WORK_TIM VARCHAR2(10) NOT NULL
6 )
7 TABLESPACE ORA9;
테이블이 생성되었습니다.
SQL>
ALTER TABLE ORA9.SAL ADD (
2 CONSTRAINT SAL_PK PRIMARY KEY ( JOB_GRD,SAL_CLASS )
3 USING INDEX TABLESPACE ORA9 );
테이블이 변경되었습니다.
SQL>
ALTER TABLE ORA9.EMP ADD (
2 CONSTRAINT EMP_PK PRIMARY KEY ( EMPNO )
3 USING INDEX TABLESPACE ORA9 );
테이블이 변경되었습니다.
SQL>
ALTER TABLE ORA9.FAM ADD (
2 CONSTRAINT FAM_PK PRIMARY KEY ( SEQ )
3 USING INDEX TABLESPACE ORA9 );
테이블이 변경되었습니다.
SQL>
ALTER TABLE ORA9.WORK ADD (
2 CONSTRAINT WORK_PK PRIMARY KEY ( WORK_DATE,EMPNO )
3 USING INDEX TABLESPACE ORA9 );
테이블이 변경되었습니다.
SQL>
ALTER TABLE ORA9.SAL ADD (
2 CONSTRAINT SAL_PK PRIMARY KEY ( JOB_GRD,SAL_CLASS )
3 USING INDEX TABLESPACE ORA9 );
테이블이 변경되었습니다.
SQL>
ALTER TABLE ORA9.EMP ADD (
2 CONSTRAINT EMP_PK PRIMARY KEY ( EMPNO )
3 USING INDEX TABLESPACE ORA9 );
테이블이 변경되었습니다.
SQL>
ALTER TABLE ORA9.FAM ADD (
2 CONSTRAINT FAM_PK PRIMARY KEY ( SEQ )
3 USING INDEX TABLESPACE ORA9 );
테이블이 변경되었습니다.
SQL>
ALTER TABLE ORA9.WORK ADD (
2 CONSTRAINT WORK_PK PRIMARY KEY ( WORK_DATE,EMPNO )
3 USING INDEX TABLESPACE ORA9 );
테이블이 변경되었습니다.
SQL>
SELECT COUNT(ALL)
2 FROM ORA9.SAL;
COUNT(ALL)
=========
10
SQL>
SELECT COUNT(ALL)
2 FROM ORA9.EMP;
COUNT(ALL)
=========
20
SQL>
SELECT COUNT(ALL)
2 FROM ORA9.FAM;
COUNT(ALL)
=========
40
SQL>
SELECT COUNT(ALL)
2 FROM ORA9.WORK;
COUNT(ALL)
=========
60
{*}1)조인은 연결 조건을 만족하는 다른 집합의 데이터를 찾아오는 것이 아니다.*
{*}2)결과{*}
SQL>
SELECT a.JOB_GRD AS EMP_FK1
2 ,a.SAL_CLASS AS EMP_FK2
3 ,b.JOB_GRD AS SAL_PK1
4 ,b.SAL_CLASS AS SAL_PK2
5 ,b.BASE_SAL
6 FROM ORA9.EMP a, ORA9.SAL b
7 WHERE a.JOB_GRD = b.JOB_GRD
8 AND a.SAL_CLASS = b.SAL_CLASS;
EMP_FK1 EMP_FK2 SAL_PK1 SAL_PK2 BASE_SAL
======= ======= ======= ======= ========
사원 1 사원 1 800000
사원 1 사원 1 800000
사원 1 사원 1 800000
사원 2 사원 2 1000000
사원 2 사원 2 1000000
대리 1 대리 1 1200000
대리 1 대리 1 1200000
대리 2 대리 2 1400000
대리 2 대리 2 1400000
과장 1 과장 1 1600000
과장 1 과장 1 1600000
과장 2 과장 2 1800000
과장 2 과장 2 1800000
과장 2 과장 2 1800000
차장 1 차장 1 2000000
차장 1 차장 1 2000000
차장 1 차장 1 2000000
차장 2 차장{color:#ff0000} 2 2200000{color}
부장 1 부장 1 2400000
부장 2 부장 2 2600000
20 개의 행이 선택되었습니다.
{*}1)조인한 집합에서 1쪽의 집합에 있던 컬럼을 SUM한다면 잘못된 결과를 얻게 된다.*
{*}2)결과{*}
SQL>
SELECT (800000 + 1000000 + 1200000 + 1400000 + 1600000
2 + 1800000 + 2000000 + 2200000 + 2400000 + 2600000) 원하는값
3 FROM SYS.DUAL;
원하는값
=======
17000000
SQL>
SELECT SUM(b.BASE_SAL) 잘못된값
2 FROM ORA9.EMP a, ORA9.SAL b
3 WHERE a.JOB_GRD = b.JOB_GRD
4 AND a.SAL_CLASS = b.SAL_CLASS;
잘못된값
=======
31400000
SQL>
SELECT SUM(원하는값.정상값) 정상값
2 FROM ( SELECT a.JOB_GRD
3 ,a.SAL_CLASS
4 ,MAX(b.BASE_SAL) 정상값
5 ,SUM(b.BASE_SAL)
6 FROM ORA9.EMP a, ORA9.SAL b
7 WHERE a.JOB_GRD = b.JOB_GRD
8 AND a.SAL_CLASS = b.SAL_CLASS
9 GROUP BY a.JOB_GRD,a.SAL_CLASS,b.BASE_SAL) 원하는값;
정상값
=======
17000000
{*}1)카테시안 곱과 GROUP BY시 MAX/MIN 함수 사용{*}
{*}2)결과{*}
SQL>
SELECT a.EMPNO, b.WORK_DATE
2 FROM ORA9.EMP a, ORA9.WORK b
3 WHERE a.EMPNO = b.EMPNO;
EMPNO WORK_DATE
====== ==========
0001 20090401
0001 20090402
0001 20090403
0002 20090401
0002 20090402
0002 20090403
0003 20090401
0003 20090402
0003 20090403
0004 20090402
0004 20090403
0004 20090401
0005 20090402
0005 20090403
0005 20090401
0006 20090402
0006 20090403
0006 20090401
0007 20090402
0007 20090403
0007 20090401
0008 20090402
0008 20090403
0008 20090401
0009 20090402
0009 20090403
0009 20090401
0010 20090402
0010 20090403
0010 20090401
0011 20090402
0011 20090403
0011 20090401
0012 20090402
0012 20090403
0012 20090401
0013 20090402
0013 20090403
0013 20090401
0014 20090402
0014 20090403
0014 20090401
0015 20090402
0015 20090403
0015 20090401
0016 20090402
0016 20090403
0016 20090401
0017 20090402
0017 20090403
0017 20090401
0018 20090402
0018 20090403
0018 20090401
0019 20090402
0019 20090403
0019 20090401
0020 20090402
0020 20090403
0020 20090401
60 개의 행이 선택되었습니다.
SQL>
SELECT a.EMPNO, b.MAX_WORKDT
2 FROM ORA9.EMP a,
3 ( SELECT EMPNO,MAX(WORK_DATE) MAX_WORKDT
4 FROM ORA9.WORK
5 GROUP BY EMPNO) b
6 WHERE a.EMPNO = b.EMPNO;
EMPNO MAX_WORKDT
====== ===========
0001 20090403
0002 20090403
0003 20090403
0004 20090403
0005 20090403
0006 20090403
0007 20090403
0008 20090403
0009 20090403
0010 20090403
0011 20090403
0012 20090403
0013 20090403
0014 20090403
0015 20090403
0016 20090403
0017 20090403
0018 20090403
0019 20090403
0020 20090403
20 개의 행이 선택되었습니다.
{*}1)급여체계,사원,가족,근태 네개의 엔터티를 하나의 SQL로 조인하여 구현{*}
{*}2)실행 과정{*}
SQL1>
SELECT a.EMPNO 사원번호
2 ,COUNT(DISTINCT a.NAME) * 10000 가족수당
3 ,SUM(DECODE(c.WORK_CD,'결근',0,10000)) "만근수당(잘못된값)"
4 ,COUNT(DECODE(c.WORK_CD, '결근', NULL, '출장', NULL, 1)) * 3500 "중식비(잘못된값)"
5 FROM ORA9.EMP a, ORA9.SAL b, ORA9.WORK c, ORA9.FAM d
6 WHERE b.JOB_GRD = a.JOB_GRD
7 AND b.SAL_CLASS = a.SAL_CLASS
8 AND c.EMPNO = a.EMPNO
9 AND c.WORK_DATE between '20090401' and '20090402'
10 AND d.EMPNO = c.EMPNO
11 AND a.RES_DATE IS NULL
12 GROUP BY a.EMPNO;
사원번호 가족수당 만근수당(잘못된값) 중식비(잘못된값)
====== ====== ============== ============
0001 10000 40000 14000
0002 10000 40000 14000
0003 10000 40000 14000
0004 10000 40000 14000
0005 10000 40000 14000
0006 10000 40000 14000
0007 10000 40000 14000
0008 10000 40000 14000
0009 10000 40000 14000 \--> 2배 증가된 잘못된 값
0010 10000 40000 14000
0011 10000 40000 14000
0012 10000 40000 14000
0013 10000 40000 7000
0014 10000 40000 14000
0015 10000 40000 14000
0016 10000 40000 14000
0017 10000 40000 14000
0018 10000 40000 14000
0019 10000 40000 14000
0020 10000 20000 0
20 개의 행이 선택되었습니다.
SQL2>
SELECT COUNT(ALL)
2 FROM ORA9.EMP a, ORA9.SAL b
3 WHERE a.RES_DATE IS NULL
4 AND b.JOB_GRD = a.JOB_GRD
5 AND b.SAL_CLASS = a.SAL_CLASS;
COUNT(ALL)
==========
20
SQL3>
SELECT COUNT(ALL)
2 FROM ORA9.EMP a, ORA9.SAL b, ORA9.WORK c
3 WHERE a.RES_DATE IS NULL
4 AND b.JOB_GRD = a.JOB_GRD
5 AND b.SAL_CLASS = a.SAL_CLASS
6 AND c.EMPNO = a.EMPNO
7 AND c.WORK_DATE between '20090401' and '20090402';
COUNT(ALL)
==========
40
SQL4>
SELECT COUNT(ALL)
2 FROM ORA9.EMP a, ORA9.SAL b, ORA9.WORK c, ORA9.FAM d
3 WHERE a.RES_DATE IS NULL
4 AND b.JOB_GRD = a.JOB_GRD
5 AND b.SAL_CLASS = a.SAL_CLASS
6 AND c.EMPNO = a.EMPNO
7 AND c.WORK_DATE between '20090401' and '20090402'
8 AND d.EMPNO = c.EMPNO;
COUNT(ALL)
==========
80
SQL5>
SELECT SUM(DECODE(WORK_CD,'결근',0,10000)) "만근수당(정상값)"
2 ,COUNT(DECODE(WORK_CD, '결근', NULL, '출장', NULL, 1)) * 3500 "중식비(정상값)"
3 FROM ORA9.WORK
4 WHERE WORK_DATE between '20090401' and '20090402'
5 AND EMPNO = '0001';
만근수당(정상값) 중식비(정상값)
============ ==========
20000 7000
SQL6>
SELECT *
2 FROM ORA9.WORK
3 WHERE WORK_DATE between '20090401' and '20090402'
4 AND EMPNO = '0001';
WORK_DATE EMPNO WORK_CD WORK_TIM
========== ====== ======== =========
20090401 0001 출근 16
20090402 0001 출근 18
SQL7>
SELECT SUM(DECODE(c.WORK_CD,'결근',0,10000)) "만근수당(정상값)"
2 ,COUNT(DECODE(c.WORK_CD, '결근', NULL, '출장', NULL, 1)) * 3500 "중식비(정상값)"
3 FROM ORA9.EMP a, ORA9.SAL b, ORA9.WORK c
4 WHERE b.JOB_GRD = a.JOB_GRD
5 AND b.SAL_CLASS = a.SAL_CLASS
6 AND c.EMPNO = a.EMPNO
7 AND c.WORK_DATE between '20090401' and '20090402'
8 AND a.RES_DATE IS NULL
9 AND a.EMPNO = '0001';
만근수당(정상값) 중식비(정상값)
============ ==========
20000 7000
SQL8>
SELECT *
2 FROM ORA9.EMP a, ORA9.SAL b, ORA9.WORK c
3 WHERE b.JOB_GRD = a.JOB_GRD
4 AND b.SAL_CLASS = a.SAL_CLASS
5 AND c.EMPNO = a.EMPNO
6 AND c.WORK_DATE between '20090401' and '20090402'
7 AND a.RES_DATE IS NULL
8 AND a.EMPNO = '0001';
EMPNO NAME ADDR JOB_GRD SAL_CLASS JOB_KIND RES_DATE JOB_GRD SAL_CLASS BASE_SAL WORK_DATE EMPNO WORK_CD WORK_TIM
====== ===== ===== ======== ========= ======== ======== ======= ========= ======== ========== ====== ======== ========
0001 오라구 신림동 사원 1 개발 사원 1 800000 20090401 0001 출근 16
0001 오라구 신림동 사원 1 개발 사원 1 800000 20090402 0001 출근 18
SQL9>
SELECT COUNT(DISTINCT a.NAME) * 10000 가족수당
2 ,SUM(DECODE(c.WORK_CD,'결근',0,10000)) "만근수당(잘못된값)"
3 ,COUNT(DECODE(c.WORK_CD, '결근', NULL, '출장', NULL, 1)) * 3500 "중식비(잘못된값)"
4 FROM ORA9.EMP a, ORA9.SAL b, ORA9.WORK c, ORA9.FAM d
5 WHERE b.JOB_GRD = a.JOB_GRD
6 AND b.SAL_CLASS = a.SAL_CLASS
7 AND c.EMPNO = a.EMPNO
8 AND c.WORK_DATE between '20090401' and '20090402'
9 AND d.EMPNO = c.EMPNO
10 AND a.RES_DATE IS NULL
11 AND a.EMPNO = '0001';
가족수당 만근수당(잘못된값) 중식비(잘못된값)
====== ============== ============
10000 40000 14000
SQL10>
SELECT *
2 FROM ORA9.EMP a, ORA9.SAL b, ORA9.WORK c, ORA9.FAM d
3 WHERE b.JOB_GRD = a.JOB_GRD
4 AND b.SAL_CLASS = a.SAL_CLASS
5 AND c.EMPNO = a.EMPNO
6 AND c.WORK_DATE between '20090401' and '20090402'
7 AND d.EMPNO = c.EMPNO
8 AND a.RES_DATE IS NULL
9 AND a.EMPNO = '0001';
EMPNO NAME ADDR JOB_GRD SAL_CLASS JOB_KIND RES_DATE JOB_GRD SAL_CLASS BASE_SAL WORK_DATE EMPNO WORK_CD WORK_TIM SEQ REL NAME EMPNO
====== ===== ===== ======== ========= ======== ======== ======= ========= ======== ========== ====== ======== ======== === === ===== ======
0001 오라구 신림동 사원 1 개발 사원 1 800000 20090401 0001 출근 16 000101 부 오라구F 0001
0001 오라구 신림동 사원 1 개발 사원 1 800000 20090402 0001 출근 18 000101 부 오라구F 0001
0001 오라구 신림동 사원 1 개발 사원 1 800000 20090401 0001 출근 16 000102 모 오라구M 0001
0001 오라구 신림동 사원 1 개발 사원 1 800000 20090402 0001 출근 18 000102 모 오라구M 0001
SQL11>
SELECT a.EMPNO 사원번호
2 ,COUNT(DISTINCT a.NAME) * 10000 가족수당
3 ,SUM(DECODE(c.WORK_CD,'결근',0,10000)) "만근수당(정상값)"
4 ,COUNT(DECODE(c.WORK_CD, '결근', NULL, '출장', NULL, 1)) * 3500 "중식비(정상값)"
5 FROM ORA9.EMP a, ORA9.SAL b, ORA9.WORK c,
6 ( SELECT *
7 FROM ORA9.FAM
8 GROUP BY EMPNO) d
9 WHERE b.JOB_GRD = a.JOB_GRD
10 AND b.SAL_CLASS = a.SAL_CLASS
11 AND c.EMPNO = a.EMPNO
12 AND c.WORK_DATE between '20090401' and '20090402'
13 AND d.EMPNO = c.EMPNO
14 AND a.RES_DATE IS NULL
15 GROUP BY a.EMPNO;
사원번호 가족수당 만근수당(정상값) 중식비(정상값)
====== ====== ============ ==========
0001 10000 20000 7000
0002 10000 20000 7000
0003 10000 20000 7000
0004 10000 20000 7000
0005 10000 20000 7000
0006 10000 20000 7000
0007 10000 20000 7000
0008 10000 20000 7000
0009 10000 20000 7000
0010 10000 20000 7000
0011 10000 20000 7000
0012 10000 20000 7000
0013 10000 20000 3500
0014 10000 20000 7000
0015 10000 20000 7000
0016 10000 20000 7000
0017 10000 20000 7000
0018 10000 20000 7000
0019 10000 20000 7000
0020 10000 10000 0
20 개의 행이 선택되었습니다.
3)주석(NESTED LOOPS 조인시)
\* 사원 테이블(a)에서 근무중인 사원 체크(11 행)
\* 사원 테이블(a)의 직급/호봉(PK)으로 급여체계 테이블(b)에 연결 : SQL2의 결과(20 건)
\* 사원 테이블(a)의 사번(PK)으로 근태 테이블©의 근무 일자 범위만큼 연결(8,9 행) : SQL3의 결과(40) 건
\* 2배 증가된 로우마다 읽어둔 근태 테이블©의 사번(FK)으로 가족 테이블(d)과 연결(10 행) : SQL4의 결과(80) 건
\* 반복 수행
\*전체 집합에 대한 GROUP BY/필요한 연산(2,3,4,12) : SQL1의 결과(20 건)
4)수행결과 검증
\*근무 테이블에서 사원번호가 '0001'인 사원에 대해서만 만근수당과 중식비를 계산 : SQL5/SQL6의 결과(정상값)
\*사원/급여체계/근무 테이블을 조인하면서 사원번호가 '0001'인 사원에 대해서만 만근수당과 중식비를 계산 : SQL7/SQL8의 결과(정상값)
\*사원/급여체계/근무/가족 테이블을 조인하면서 사원번호가 '0001'인 사원에 대해서만 만근수당과 중식비를 계산 : SQL9/SQL10의 결과(가족의 수에 따라 증가된 잘못된 값)
\*이 SQL에서는 가족수당을 계산할때 가족의 수는 관계없이 가족의 유무만을 고려하므로 사원번호별로 중복제거를 한 후, 만근수당과 중식비를 계산 : SQL11의 결과(정상값)
5)결론
문제점 : 하나 이상의 로우를 가지는 집합을 방사형으로 여러 개를 조인시 카테시안 곱만큼의 집합이 생긴다.
수행속도 측면에서 매우 비효율적이고 수행결과도 잘못 되었음.
해결방안 : SQL을 분리하여 절차형 처리 \-> 처리를 복잡하게 만들고 수행속도를 저하시킴
모든 M을 1로 만든 후에 조인 \-> 효율적임
\* SQL 분리시 수행속도의 저하
1)SQL 통합(12 시간 소요)
2)SQL 분리(4 시간 소요)
(1)조인시 결과만 유일하다면 어떤 비교 연산자도 사용 가능하다.
SELECT ..............
FROM 어음수불 x, 지급어음 y
WHERE y.어음번호 BETWEEN x.어음시작번호 and x.어음종료번호;
(2)정보통신회사의 사례
SELECT 국번호, 시작번호, 종료번호, 개수, 골든번호등급
FROM 전화번호
WHERE 사용상태 = '미사용'
AND 개수 >= 50
AND 시작번호 >= 2000
AND 종료번호 <= 2999
AND ROWNUM = 1;
(3)조인이란 로우를 다루는 것이 아니라 집합을 다루는 것{}