티스토리 뷰

It

2-6 INDEX 인덱스 정리 2

IT eoeo25 2023. 3. 27. 15:43

★★★★★

인덱스 매칭률

특정 테이블에 대해서 SQL상의 주어진 조건으로 인해 사용될 수

있는 인덱스가 두 개 이상이라 할 때 Optimizer는 조건에 가장

적합한 인덱스를 선택해서 사용해야만 한다.

이와같이 주어진 조건에 가장 적합한 인덱스를 선택해서 사용하

고자 할 때 이용하는 인덱스 선택기준을 "인덱스 매칭률"이라고

한다.

* Optimizer의 인덱스 선택시 판단 절차

주어진 조건에 대한 각 인덱스별 매칭률을 계산해서 매칭률이

높은 것을 우선적으로 선택

인덱스별 매칭률이 같을 경우에는 인덱스를 구성하는 칼럼의

개수가 많은 것을 우선적으로 선택

인덱스별 매칭률과 인덱스구성하는 칼럼의 개수가 같을 경우에는

가장 최근에 생성된 것을 우선적으로 선택

㈜ 위의 절차를 RBO CBO 모두 사용하지만, 특히 CBO

이외에도 Cost를 고려해서 최종 판단을 하게 된다는

점에서 RBO와 차이점이 있다.

*

Where절에 첫 번째 칼럼부터 연속된 칼럼에 대해서

'='에 상수조건이 사용된 인덱스 칼럼의 개수

인덱스 매칭률 = -----------------------------------------------

인덱스 칼럼의 총 개수

* 범위 제한 조건이 되는 '='을 의미함.

eg. 매칭률 계산에 대한 예제 :

1st 2nd 3rd

() 결합인덱스 : ( + + )

1) 매칭률 = 1/3 2) 매칭률 = 2/3 3) 매칭률 = 3/3

WHERE = '서울시'; WHERE = '서울시' WHERE = '서울시'

AND = '강남구'; AND = '강남구'

AND = '역삼동';

1st 2nd 3rd

() 결합인덱스 : ( + + )

매칭률 = 1/3

① WHERE = '서울시'; ② WHERE = '서울시'

AND = '역삼동';

③ WHERE = '서울시'

AND LIKE '%'

AND = '역삼동';

매칭률 = 2/3

① WHERE = '서울시' ② WHERE = '서울시'

AND = '강남구'; AND LIKE '%'

AND = '강남구';

매칭률 = 3/3 "교환 법칙"

① WHERE = '서울시' ② WHERE = '서울시'

AND = '강남구' AND = '역삼동'

AND = '역삼동'; AND = '강남구';

③ WHERE = '강남구' ④ WHERE = '강남구'

AND = '역삼동' AND = '서울시'

AND = '서울시'; AND = '역삼동';

⑤ WHERE = '역삼동' ⑥ WHERE = '역삼동'

AND = '서울시' AND = '강남구'

AND = '강남구'; AND = '서울시';

기타 인덱스

===========

① Function based Index

Function-based index, 함수(function)나 수식(expression)

으로 계산된 결과에 대해 생성해 놓은 인덱스이며, 따라서 해당

함수나 수식을 처리하여 결과를 가져 오는 것이 아니라, 인덱스

형태로 존재하는 미리 계산되어 있는 결과를 가지고 처리하므로

성능 향상을 기할 수 있다.

- Cost-base optimizer에 의해 사용됨.

- SELECT/DELETE를 할때 마다를 계산하는 것이 아니라

INSERT/UPDATE시 계산된 값을 인덱스에 저장.

* Dictionary : USER_IND_EXPRESSIONS

eg. SELECT INDEX_NAME, COLUMN_EXPRESSION

FROM USER_IND_EXPRESSIONS

WHERE TABLE_NAME = 'EMP';

② Cluster Index

클러스터는 클러스터 키라고 부르는 공통된 칼럼을 기준으로 하나

이상의 테이블의 데이터를 같은 데이터 블록에 모아서 저장할 수

있게 한다.

이러한 클러스터가 갖는 인덱스를 클러스터 인덱스라고 한다.

- 클러스터에 대하여 SELECT, INSERT를 포함한 모든 DML문을 실행

하기 전에 반드시 클러스터 인덱스는 생성되어 있어야 한다.

- 클러스터 인덱스는 분포도가 나쁠수록 좋다.

- 클러스터 키 이외의 칼럼들에 대해서 인덱스를 만들 수 있다.

- 클러스터 인덱스의 경우는 클러스터 키 값에 의하여 해당 데이터가

저장된 첫 번째 블록의 위치를 찾게하며, 이 때 해당 데이터 블록에

는 주어진 클러스터 키 값과 일치하는 자료가 모두 모여 저장되어

있으므로 다른 데이터 블록을 범위 제한할 필요가 없다.

Partitioned Table

==================

Partition 종류

- RANGE partition

- LIST partition

- HASH partition

eg. 생성 예제

SQL> CREATE TABLE EC_COURSE_PLAN_PARTED_YEAR

PARTITION BY RANGE(YEAR)

(PARTITION EC_COURSE_PLAN_1997 VALUES LESS THAN ('1998')

TABLESPACE TS_EC_DATA,

PARTITION EC_COURSE_PLAN_1998 VALUES LESS THAN ('1999')

TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_1999 VALUES LESS THAN ('2000')

TABLESPACE TS_EC_DATA,

PARTITION EC_COURSE_PLAN_2000 VALUES LESS THAN ('2001')

TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_2001 VALUES LESS THAN ('2002')

TABLESPACE TS_EC_DATA,

PARTITION EC_COURSE_PLAN_2002 VALUES LESS THAN ('2003')

TABLESPACE TS_EC_IDX

PARTITION EC_COURSE_PLAN_GITA VALUES LESS THAN (MAXVALUE))

AS SELECT * FROM EC_COURSE_PLAN;

RANGE Partition 이라고 함.

○ Partition에 대한 정보 확인

SQL> SELECT PARTITION_NAME, PARTITION_POSITION, NUM_ROWS, LAST_ANALYZED

FROM USER_TAB_PARTITIONS

WHERE TABLE_NAME = 'EC_COURSE_PLAN_PARTED_YEAR'

ORDER BY 2;

PARTITION_NAME PARTITION_POSITION NUM_ROWS LAST_ANAL

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

EC_COURSE_PLAN_1997 1

EC_COURSE_PLAN_1998 2

EC_COURSE_PLAN_1999 3

EC_COURSE_PLAN_2000 4

EC_COURSE_PLAN_2001 5

EC_COURSE_PLAN_2002 6

EC_COURSE_PLAN_GITA 7

* Partition 간 데이터 이동 (Parition key값 수정시)

SQL> ALTER TABLE EC_COURSE_PLAN_PARTED_YEAR ENABLE ROW MOVEMENT;

* Partition 별로 ANALYZE 작업 수행이 가능함

SQL> ANALYZE TABLE EC_COURSE_PLAN_PARTED_YEAR

PARTITION(EC_COURSE_PLAN_2002) COMPUTE STATISTICS;

* Partition 병합

SQL> ALTER TABLE EC_COURSE_PLAN_PARTED_YEAR

MERGE PARTITIONS EC_COURSE_PLAN_1997, EC_COURSE_PLAN_1998

INTO PARTITION EC_COURSE_PLAN_1998

TABLESPACE TS_EC_DATA;

* Partition 분할

SQL> ALTER TABLE EC_COURSE_PLAN_PARTED_YEAR

SPLIT PARTITION EC_COURSE_PLAN_1998 AT ('1998')

INTO (PARTITION EC_COURSE_PLAN_1997 TABLESPACE TS_EC_DATA,

PARTITION EC_COURSE_PLAN_1998 TABLESPACE TS_EC_IDX);

* Partition 제거

SQL> ALTER TABLE EC_COURSE_PLAN_PARTED_YEAR

DROP PARTITION EC_COURSE_PLAN_GITA;

* Partition 추가

SQL> ALTER TABLE EC_COURSE_PLAN_PARTED_YEAR

ADD PARTITION EC_COURSE_PLAN_2003 VALUE ('2003');

* Partition tablespace 변경

SQL> ALTER TABLE EC_COURSE_PLAN_PARTED_YEAR

MOVE PARTITION EC_COURSE_PLAN_1999

TABLESPACE TS_EC_IDX;

* Partition의 데이터 제거

SQL> ALTER TABLE EC_COURSE_PLAN_PARTED_YEAR

TRUNCATE PARTITION EC_COURSE_PLAN_1999;

* Partition의 이름 변경

SQL> ALTER TABLE EC_COURSE_PLAN_PARTED_YEAR

RENAME PARTITION EC_COURSE_PLAN_1999 TO EC_COURSE_PLAN_1999_1;

SQL> CREATE INDEX EC_COURSE_PLAN_PARTED_YEAR_IDX

ON EC_COURSE_PLAN_PARTED_YEAR(YEAR)

LOCAL

(PARTITION EC_COURSE_PLAN_1997_IDX TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_1998_IDX TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_1999_IDX TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_2000_IDX TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_2001_IDX TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_2002_IDX TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_GITA_IDX TABLESPACE TS_EC_IDX);

또는

SQL> CREATE INDEX EC_COURSE_PLAN_PARTED_YEAR_IDX

ON EC_COURSE_PLAN_PARTED_YEAR(YEAR)

LOCAL;

* Index rebuiding

SQL> ALTER INDEX EC_COURSE_PLAN_PARTED_YEAR_IDX

REBUILD PARTITION EC_COURSE_PLAN_1999_IDX;

SQL> CREATE TABLE EC_COURSE_PLAN_PARTED_YEAR

PARTITION BY LIST (YEAR)

(PARTITION EC_COURSE_PLAN_1997 VALUES ('1997')

TABLESPACE TS_EC_DATA,

PARTITION EC_COURSE_PLAN_1998 VALUES ('1998')

TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_1999 VALUES ('1999')

TABLESPACE TS_EC_DATA,,

PARTITION EC_COURSE_PLAN_2000 VALUES ('2000')

TABLESPACE TS_EC_IDX,

PARTITION EC_COURSE_PLAN_2001 VALUES ('2001')

TABLESPACE TS_EC_DATA,

PARTITION EC_COURSE_PLAN_2002 VALUES ('2002')

TABLESPACE TS_EC_IDX)

AS SELECT * FROM EC_COURSE_PLAN;

LIST Partition 이라고 함.

SQL> CREATE TABLE EC_COURSE_PLAN_HASH_PART

PARTITION BY HASH(YEAR)

PARTITIONS 4

STSORE IN (TS_EC_DATA, TS_EC_IDX)

AS SELECT * FROM EC_COURSE_PLAN;

HASH Partition 이라고 함.

(참고) 다음 명령을 지원하지 않는다.

ALTER TABLE EC_COURSE_PLANHASH_PART SPLIT PARTITION ...

ALTER TABLE EC_COURSE_PLANHASH_PART DROP PARTITION ...

ALTER TABLE EC_COURSE_PLANHASH_PART MERGE PARTITIONS ...

반면에

ALTER TABLE EC_COURSE_PLAN_HASH_PART COALESCE PARTITION;

ALTER TABLE EC_COURSE_PLAN_HASH_PART ADD PARTITION;

혹은

ALTER TABLE EC_COURSE_PLAN_HASH_PART ADD PARTITION;

3] Join

============

조인 방식으로는 Nested Loops, Sort Merge, Hash 등이 있으며,

이들 조인 방식은 나름대로의 장점과 단점이 각각 있다.

그러므로 조인을 튜닝하고자 할 때, 무엇보다도 각 조인 방식 별로

튜닝 포인트를 구분하는 가운데 튜닝이 진행될 수 있어야 한다.

★★★★★

Index Scan vs. Full Table Scan

Nested Sort/Merge

Loops Hash

|-------------------------------------|

(0%) (100%)

(10~15%)

일반적으로, 0% 에서 많게는 15% 까지 데이터를 처리하게 될 때는

인덱스를 사용하는 것이 좋고, 그 이상의 데이터를 처리하게 될 때

는 인덱스를 사용하지 않는 것이 훨씬 좋다.

Index Scan을 해야 할 상황에서 조인을 해야한다면 Nested Loops 조인을,

Full Scan을 해야 할 상황에서 조인을 해야한다면 Sort/Merge, Hash 조인을

하는 것이 바람직하다. 특히, Sort/Merge 조인을 하고자 할 때 비대칭 데이터

로 인해 sort에 의한 대기시간이 발생될 가능성이 있는 경우는 Hash 조인이

적절하다.

1) Nested Loops 조인

* 조인 방식으로, 가장 널리 사용되면서도 중요한 조인방식

* 하나의 테이블을 기준으로 순차적으로 상대방 테이블의 row를 조인

하여 원하는 결과를 추출하는 테이블 연결 방식.

기준되는 테이블 = Driving (또는 Outer 테이블)

조인되는 테이블 = Driven (또는 Inner 테이블)

* 특징 : 대부분이 인덱스 사용에 의한 데이터 처리를 전제로 함

, 인덱스 사용에 의한 랜덤 액세스에 기반을 둔다.

★★★★★

○ Tuning points:

① Driving 테이블 결정 (, 조인 순서 결정)

② Driven 테이블은 연결고리 칼럼에 대한 인덱스를 반드시 사용

○ Driving table 찾기

㈜ 조인 할 테이블의 개수에 따른 예측 가능한 조인순서

- 테이블 3 : A, B, C

① A → B → C

② A → C → B

③ B → A → C

④ B → C → A

⑤ C → A → B

⑥ C → B → A

첫 번째 테이블의 선택이 가장 중요하며, 두 번째 테이블의

선택도 중요하다. (Driving 결정이 중요)

조인 순서 제어 방법

힌트 사용 : /*+ ORDERED */, /*+ LEADING(t) */

② Suppressing 활용

[Example]

SELECT A.RESNO, A.GRENTDT, B.WEDDT

FROM EMP_MASTER A, EMP_DETAIL B

WHERE A.RESNO = B.RESNO

AND RTRIM(A.GRENTDT) LIKE '1990%'

AND B.WEDDT LIKE '200210%';

③ FROM절의 테이블 순서 변경(, Rule Base에서만)

, 각 테이블이 갖는 규칙이 모두 같은 경우에만 적용 가능함.

규칙이 같은 테이블에 대해서는 FROM절에서 볼 때 더 멀리 있는

테이블로부터 먼저 처리하게 된다.

그러나 Cost Base일 때는 적용되질 않는다.

④ View 사용

테이블 간 연결고리에 대한 인덱스 구성 상태

두 쪽 모두 정상

~ 최악의 상황을 발생시키지 않는다.

이러한 경우에는 드라이빙 테이블만 결정하면 됨.

(, 조인 순서) 결정만 생각하면 된다.)

한 쪽만 정상

~ 연결고리에 인덱스가 없는 테이블을 먼저 처리함.

, 조인 순서가 고정되어 있음

두 쪽 모두 비정상 (Sort Merge 또는 Hash 조인을 해야함)

) SELECT *

FROM TAB_A A, TAB_B B

WHERE A.NAME = B.NAME;

A B B A

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

(FULL) (FULL) (FULL) (FULL)

㈜ 특히, driven 테이블에 대한 FULL table scan 횟수는

driving 테이블로부터 읽어 들이는 row수 만큼이 된다!!!

이러한 상황에서 할 수 있는 조인이 Sort Merge Hash 조인이다.

, Rule base에서는 Sort Merge만 할 수 있다.

2) Sort Merge 조인

* 조인 방식으로, 인덱스를 사용하기엔 너무 큰 데이터이거나

연결고리에 전혀 인덱스가 존재하지 않을 때 사용할 수 있는 조인임.

* 각 테이블에 대하여 동시에 독립적으로 데이터를 읽어들인 후,

연결고리 칼럼에 대하여 Sort를 수행하며, Sort가 모두 완료 된

후에 비로소 조인을 하게 됨.

* Tuning points:

각 테이블별로 독립적으로 데이터를 읽어들일 때, 빨리 읽어들이

도록 함. 특히, Full Scan을 하는 경우 DB_FILE_MULTIBLOCK_READ_COUNT

의 개수를 늘리는 것과 Parallel Processing의 적용을 검토함.

SQL> ALTER SESSION SET DB_FILE_MULTIBLOCK_READ_COUNT = 16;

읽어들인 데이터를 Sort하게 될 때, 이를 위한 메모리를 최적화

해야 함. (Parameter : SORT_AREA_SIZE)

SQL> ALTER SESSION SET SORT_AREA_SIZE = 2097152;

㈜ 각 테이블에서 읽어 들인 데이터에 상당한 차이가 생길 경우

대기시간이 발생하게 됨. → Hash 조인을 해야 함.

* Hint : /*+ USE_MERGE(t1 t2 t3 ... ) */

* Sort Merge 조인 관련 parameters

① DB_FILE_MULTIBLOCK_READ_COUNT

~ 한 번의 I/O에 의해서 동시에 읽어 들일 수 있는 블럭 개수

주로, OLTP 시스템에서는 4 ~ 16개 정도로 설정하고, DW

시스템에서는 이 보다 크게(32 ~ 64개 정도) 설정한다.

너무 크게 설정하게 되면, Index Scan을 통한 데이터 처리 보다

Full Scan을 통한 데이터 처리를 많이 하게 된다.

② SORT_AREA_SIZE, SORT_AREA_RETAINED_SIZE

~ Sort를 수행하는데 필요한 메모리로서, 너무 작게 설정하면

Disk I/O가 많이 발생하게 되면, 반대로 너무 크게 설정하면

OS 상의 메모리가 부족하게 된다.

3) Hash 조인

* 조인 방식으로, 인덱스를 사용하기엔 너무 큰 데이터이거나

연결고리에 전혀 인덱스가 존재하지 않을 때 사용할 수 있는 조인

이면서, 비대칭형 데이터에 의한 Sort Merge 조인에서 대기시간이

있을 경우 선택해야 할 조인임.

* 각 테이블에 대하여 독립적으로 데이터를 읽어들이는데,

HASH_AREA_SIZE의 크기에 의해서 결정된 driving table로부터

먼저 데이터를 읽어들여서 연결고리 칼럼에 대하여 hashing

사용하여 조인에서 사용할 값들을 생성한다. 그 다음으로 driven

table로 부터 데이터를 읽어서 연결고리 칼럼에 대해서 hashing

사용하여 조인에서 사용할 값들을 생성하여 조인을 수행함.

* Tuning points:

① Driving 테이블의 결정

각 테이블별로 독립적으로 데이터를 읽어들일 때, 빨리 읽어들이

도록 함. 특히, Full Scan을 하는 경우 Parallel Processing

할 수 있도록 함.

읽어들인 데이터를 Hashing , 조인에서 사용할 메모리를 최적화

해야 함. (Parameter : HASH_AREA_SIZE)

* 조인 방식 중에서 가장 많이 메모리와 CPU를 사용하게 됨

따라서 H/W가 넉넉한 상황에서는 다른 조인에 비해 효율적이 처리가

가능하겠지만 부족한 상황에서는 다른 조인 보다 오히려 느리다.

* Hint : /*+ USE_HASH(t1 t2 t3 ... ) */

Set Operators

=============

○ UNION, UNION ALL, INTERSECT, MINUS

* UNION ALL Sort가 발생하지 않는다.

* 중복된 데이터(교집합)가 없는 경우에 있어, SQL의 결과를

합치고자 할 때 UNION ALL을 사용해야 한다.

* 특히, MINUS INTERSECT Sort의 부하가 문제될 수 있다.

이러한 문제를 해결하기 위해서 Subquery를 사용하면 해결 할 수 있다.

- INTERSECT → EXISTS 또는 IN 사용에 의한 서브쿼리로 전환

) INTERSECT

SELECT EMPNO, ENAME, JOB, SAL FROM EMP

WHERE JOB = 'MANAGER'

INTERSECT

SELECT EMPNO, ENAME, JOB, SAL FROM EMP

WHERE SAL BETWEEN 2500 AND 3000;

1) SQL을 하나로 통합(경우에 따라 가능할 경우)

SELECT A.EMPNO, A.ENAME, A.JOB, A.SAL FROM EMP A

WHERE A.JOB = 'MANAGER'

AND A.SAL BETWEEN 2500 AND 3000;

2) IN에 의한 서브쿼리 (Nested subquery)

SELECT A.EMPNO, A.ENAME, A.JOB, A.SAL FROM EMP A

WHERE A.JOB = 'MANAGER'

AND A.EMPNO IN (SELECT B.EMPNO FROM EMP B

WHERE B.SAL BETWEEN 2500 AND 3000);

3) EXISTS에 의한 서브쿼리 (Correlated subquery)

SELECT A.EMPNO, A.ENAME, A.JOB, A.SAL FROM EMP A

WHERE A.JOB = 'MANAGER'

AND EXISTS (SELECT 'X' FROM EMP B

WHERE B.SAL BETWEEN 2500 AND 3000

AND B.EMPNO = A.EMPNO);

4) Join 활용

SELECT A.EMPNO, A.ENAME, A.JOB, A.SAL

FROM EMP A, EMP B

WHERE A.EMPNO = B.EMPNO

AND A.JOB = 'MANAGER'

AND B.SAL BETWEEN 2500 AND 3000;

- MINUS → NOT EXISTS 또는 NOT IN 사용에 의한 서브쿼리로 전환

) MINUS

SELECT EMPNO, ENAME, JOB, SAL FROM EMP

WHERE JOB = 'MANAGER'

MINUS

SELECT EMPNO, ENAME, JOB, SAL FROM EMP

WHERE SAL BETWEEN 2500 AND 3000;

1) SQL을 하나로 통합(경우에 따라 가능할 경우)

SELECT A.EMPNO, A.ENAME, A.JOB, A.SAL FROM EMP A

WHERE A.JOB = 'MANAGER'

AND A.SAL NOT BETWEEN 2500 AND 3000;

2) NOT EXISTS에 의한 서브쿼리 (Correlated subquery)

SELECT A.EMPNO, A.ENAME, A.JOB, A.SAL FROM EMP A

WHERE A.JOB = 'MANAGER'

AND NOT EXISTS (SELECT 'X' FROM EMP B

WHERE B.SAL BETWEEN 2500 AND 3000

AND B.EMPNO = A.EMPNO);

3) NOT IN에 의한 서브쿼리 (Nested subquery)

SELECT A.EMPNO, A.ENAME, A.JOB, A.SAL FROM EMP A

WHERE A.JOB = 'MANAGER'

AND A.EMPNO NOT IN (SELECT B.EMPNO FROM EMP B

WHERE B.SAL BETWEEN 2500 AND 3000);

4) Join 활용

SELECT A.EMPNO, A.ENAME, A.JOB, A.SAL

FROM EMP A, EMP B

WHERE A.EMPNO = B.EMPNO

AND A.JOB = 'MANAGER'

AND B.SAL NOT BETWEEN 2500 AND 3000;

Subquery

========

* Join Sub-query는 성능상 비교 대상이 될 수 있다.

기본적으로 테이블에 대한 데이터 처리를 join에서 할 때와

sub-query에서 할 때를 서로 비교함으로써 상대적으로 적게

데이터를 처리할 수 있도록 하는 방법을 선택할 수 있어야

한다.

* Subquery의 종류

① Nested subquery

일반적으로, Subquery Main query와는 별개로 독립적

으로 실행되고 그 다음으로 Main query가 실행되는 유형임.

Sub-query의 수행 결과에 비교되는 Main-query칼럼에

에 의한 인덱스 사용이 불가능할 경우엔 먼저 수행되지

않는다. 그리고 Correlated subquery와 함께 OR조건에

의해 동시에 사용되는 경우에도 먼저 수행되지 않는다.

② Correlated subquery

항상 Main query가 먼저 실행되며, Subquery가 나중에 동작됨

따라서 Loops query와 수행원리가 유사해짐

③ Scalar subquery

항상 Main query가 먼저 실행되며, Subquery가 나중에 동작됨

따라서 Loops query와 수행원리가 유사해짐

④ Inline view

eg. Nested Subquery Correlated Subquery를 함께 사용한 경우

SELECT A.COURSE_CODE, A.COURSE_NAME

FROM EC_COURSE A

WHERE A.COURSE_CODE IN (SELECT COURSE_CODE

FROM EC_APPLY

WHERE YEAR = '2000'

GROUP BY COURSE_CODE

HAVING COUNT(COURSE_CODE) < 500)

OR NOT EXISTS (SELECT 'X'

FROM EC_APPLY C

WHERE C.COURSE_CODE = A.COURSE_CODE

AND C.YEAR = '2000');

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

Id | Operation | Name |

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

0 | SELECT STATEMENT | |

* 1 | FILTER | |

2 | TABLE ACCESS FULL | EC_COURSE |

* 3 | FILTER | |

4 | HASH GROUP BY | |

* 5 | INDEX FAST FULL SCAN| EC_APPLY_PK |

* 6 | INDEX RANGE SCAN | EC_APPLY_PK |

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

0

|

1

/ | \

2 3 6

|

4

|

5

'It' 카테고리의 다른 글

5단계 발상법  (0) 2023.03.29
2-5 INDEX 인덱스 정리  (0) 2023.03.28
3-1. JOIN - NESTED LOOPS JOIN  (0) 2023.03.26
파이썬 변수 , 사용자로부터 문자열 입력받기  (0) 2023.03.25
비주얼베이직 변수, 데이터형  (0) 2023.03.24