{CODE:SQL}
CREATE SEQUENCE SEQ;
CREATE TABLE MEMBER( MEM_ID NUMBER, AGE NUMBER( 2 ) );
EXEC DBMS_RANDOM.SEED(0);
INSERT INTO MEMBER
SELECT SEQ.NEXTVAL, DBMS_RANDOM.VALUE( 1,19 ) FROM DUAL CONNECT BY LEVEL <= 50;
INSERT INTO MEMBER
SELECT SEQ.NEXTVAL, DBMS_RANDOM.VALUE( 20,29 ) FROM DUAL CONNECT BY LEVEL <= 270;
INSERT INTO MEMBER
SELECT SEQ.NEXTVAL, DBMS_RANDOM.VALUE( 30,39 ) FROM DUAL CONNECT BY LEVEL <= 330;
INSERT INTO MEMBER
SELECT SEQ.NEXTVAL, DBMS_RANDOM.VALUE( 30,39 ) FROM DUAL CONNECT BY LEVEL <= 330;
INSERT INTO MEMBER
SELECT SEQ.NEXTVAL, 40 FROM DUAL CONNECT BY LEVEL <= 1000; --> POPULAR VALUE
INSERT INTO MEMBER
SELECT SEQ.NEXTVAL, DBMS_RANDOM.VALUE( 41,49 ) FROM DUAL CONNECT BY LEVEL <= 200;
INSERT INTO MEMBER
SELECT SEQ.NEXTVAL, DBMS_RANDOM.VALUE( 50,59 ) FROM DUAL CONNECT BY LEVEL <= 100;
INSERT INTO MEMBER
SELECT SEQ.NEXTVAL, DBMS_RANDOM.VALUE( 60,99 ) FROM DUAL CONNECT BY LEVEL <= 50;
SQL> SELECT CASE WHEN AGE <= 19 THEN '10'
2 WHEN AGE >=20 AND AGE < 30 THEN '20'
3 WHEN AGE >=30 AND AGE < 40 THEN '30'
4 WHEN AGE >=40 AND AGE < 50 THEN '40'
5 WHEN AGE >=50 AND AGE < 60 THEN '50'
6 WHEN AGE >=60 THEN '60'
7 END AGE_GRP, COUNT(*)
8 FROM MEMBER
9 GROUP BY CASE WHEN AGE <= 19 THEN '10'
10 WHEN AGE >=20 AND AGE < 30 THEN '20'
11 WHEN AGE >=30 AND AGE < 40 THEN '30'
12 WHEN AGE >=40 AND AGE < 50 THEN '40'
13 WHEN AGE >=50 AND AGE < 60 THEN '50'
14 WHEN AGE >=60 THEN '60'
15 END
16 ORDER BY AGE_GRP;
AG COUNT(*)
--
{CODE}
SQL> SELECT COUNT(*), COUNT( DISTINCT AGE ) FROM MEMBER;
COUNT(*) COUNT(DISTINCTAGE)
---------- ------------------
2130 79
SQL> begin
2 dbms_stats.gather_table_stats( user, 'MEMBER', method_opt=> 'for all columns size 100' ); --히스토그램 생성
3 end;
4 /
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> SELECT NUM_DISTINCT, NUM_BUCKETS, HISTOGRAM
2 FROM USER_TAB_COL_STATISTICS
3 WHERE TABLE_NAME = 'MEMBER'
4 AND COLUMN_NAME = 'AGE'
5 ;
NUM_DISTINCT NUM_BUCKETS HISTOGRAM
------------ ----------- ---------------
79 79 FREQUENCY
연령 인원수(명) FREQUENCY RUNNING_TOTAL
연령 인원수(명) FREQUENCY RUNNING_TOTAL
연령 인원수(명) FREQUENCY RUNNING_TOTAL
연령 인원수(명) FREQUENCY RUNNING_TOTAL
연령 인원수(명) FREQUENCY RUNNING_TOTAL
연령 인원수(명) FREQUENCY RUNNING_TOTAL
연령 인원수(명) FREQUENCY RUNNING_TOTAL
연령 인원수(명) FREQUENCY RUNNING_TOTAL
79 개의 행이 선택되었습니다.
{CODE}
begin
dbms_stats.gather_table_stats( user, 'MEMBER', method_opt=> 'for all columns size 20' ); --히스토그램 생성
end;
/
SQL> SELECT NUM_DISTINCT, NUM_BUCKETS, HISTOGRAM
2 FROM USER_TAB_COL_STATISTICS
3 WHERE TABLE_NAME = 'MEMBER'
4 AND COLUMN_NAME = 'AGE';
NUM_DISTINCT NUM_BUCKETS HISTOGRAM
– ENDPOINT_NUMBER = 1 버킷은 1~22 연령대 구간을, 20은 55~99연령대 구간을 대표한다.
SQL> SELECT ENDPOINT_NUMBER, ENDPOINT_VALUE
2 FROM USER_HISTOGRAMS
3 WHERE TABLE_NAME= 'MEMBER'
4 AND COLUMN_NAME = 'AGE';
ENDPOINT_NUMBER ENDPOINT_VALUE
ENDPOINT_NUMBER ENDPOINT_VALUE
13 개의 행이 선택되었습니다.
SQL> SELECT '~' || AGE "연령대", ENDPOINT_NUMBER, DIFF
2 , ROUND( 100 * diff / sum( diff) over() ) "RATION(%)"
3 , ROUND( T.NUM_ROWS * DIFF / SUM( DIFF ) OVER()) "인원수(명)"
4 FROM (SELECT TABLE_NAME
5 , ENDPOINT_VALUE AGE, ENDPOINT_NUMBER
6 , ENDPOINT_NUMBER - LAG( ENDPOINT_NUMBER ) OVER (ORDER BY ENDPOINT_VALUE) DIFF
7 , LAG(ENDPOINT_NUMBER) OVER( ORDER BY ENDPOINT_VALUE) PREV
8 FROM USER_HISTOGRAMS
9 WHERE TABLE_NAME = 'MEMBER'
10 AND COLUMN_NAME = 'AGE' ) H, USER_TABLES T
11 WHERE H.ENDPOINT_NUMBER > 0
12 AND T.TABLE_NAME = H.TABLE_NAME
13 ORDER BY 1;
연령대 ENDPOINT_NUMBER DIFF RATION(%) 인원수(명)
-- 오라클은 popular value( 40 )에 대한 카디널리티를 구할 때만 버킷에 의한 계산식을 사용하고
나머지는 미리 구해놓은 density 값을 이용한다.
12 개의 행이 선택되었습니다.
{CODE}
--조건절 값이 두 개 이상 버킷을 가진 popular value이면 아래 공식을 따른다.
선택도 = ( 조건절 값의 버킷 개수 ) / ( 총 버킷 개수 )
카디널리티 = 총 로우 수 * 선택도
= (총 로우 수 ) * ( 조건절 값의 버킷 개수 ) / ( 총 버킷 개수 )
= 2130 * 9 / 20 = 958.5
SQL> SELECT COUNT(*) FROM MEMBER ;
COUNT(*)
----------
2130
SQL> SELECT COUNT(*) FROM MEMBER WHERE AGE = 40;
COUNT(*)
----------
1000
SQL> @XPLAN
---------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 5 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 5 |
|* 2 | TABLE ACCESS FULL| MEMBER | 1 | 959 | 1000 |00:00:00.01 | 5 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("AGE"=40)
카디널리티 = 총 로우 수 * 선택도 = 총 로우수 * density
SQL> SELECT 1/NUM_DISTINCT, TO_CHAR( density, 'FM999.999999999999999999999' ) AS density
2 , round( TO_CHAR( density, 'FM999.999999999999999999999' ) * 2130 ) AS CD
3 FROM USER_TAB_COL_STATISTICS
4 WHERE TABLE_NAME = 'MEMBER'
5 AND COLUMN_NAME = 'AGE'
6 ;
1/NUM_DISTINCT DENSITY CD
-------------- -------------------------- ----------
.012658228 .0234101956873987 50
SQL> SELECT COUNT(*) FROM MEMBER WHERE AGE = 39;
COUNT(*)
----------
25
---------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 5 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 5 |
|* 2 | TABLE ACCESS FULL| MEMBER | 1 | 15 | 25 |00:00:00.01 | 5 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("AGE"=39)
{CODE:SQL}
DROP TABLE T PURGE;
CREATE TABLE T AS
SELECT ROWNUM NO FROM DUAL CONNECT BY LEVEL <= 1000;
begin
dbms_stats.gather_table_stats( user, 'T', method_opt=> 'for all columns size 254' );
end;
/
SQL> SELECT COUNT(*) FROM T WHERE NO <= :NO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time |
0 | SELECT STATEMENT | 1 | 1 | 00:00:00.01 | ||
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.01 | |
| TABLE ACCESS FULL | T | 1 | 50 | 0 | 00:00:00.01 |
SQL> VAR NO1 NUMBER;
SQL> VAR NO2 NUMBER;
SQL> SELECT COUNT(*) FROM T WHERE NO BETWEEN :NO1 AND :NO2;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time |
0 | SELECT STATEMENT | 1 | 1 | 00:00:00.01 | ||
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.01 | |
| FILTER | 1 | 0 | 00:00:00.01 | ||
| TABLE ACCESS FULL | T | 0 | 3 | 0 | 00:00:00.01 |
-- 상수일경우
SQL> SELECT COUNT(*) FROM T WHERE NO <= 100;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
0 | SELECT STATEMENT | 1 | 1 | 00:00:00.01 | 3 | ||
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.01 | 3 | |
| TABLE ACCESS FULL | T | 1 | 98 | 100 | 00:00:00.01 | 3 |
Predicate Information (identified by operation id):
2 - filter("NO"<=100)
SQL> SELECT COUNT(*) FROM T WHERE NO BETWEEN 500 AND 600;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
0 | SELECT STATEMENT | 1 | 1 | 00:00:00.01 | 3 | ||
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.01 | 3 | |
| TABLE ACCESS FULL | T | 1 | 98 | 101 | 00:00:00.01 | 3 |
Predicate Information (identified by operation id):
2 - filter(("NO">=500 AND "NO"<=600))
{CODE}
{CODE:SQL}
DROP TABLE T PURGE
CREATE TABLE T AS
SELECT EMPNO , ENAME , SAL , SAL * 0.1 SAL_BO FROM SCOTT.EMP;
begin
dbms_stats.gather_table_stats( user, 'T', method_opt=> 'for columns SAL size 254 SAL_BO SIZE 254' );
end;
/
SQL> SELECT * FROM T
2 WHERE SAL >= 2000
3 AND SAL_BO >= 200;
EMPNO ENAME SAL SAL_BO
6 개의 행이 선택되었습니다.
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
0 | SELECT STATEMENT | 1 | 6 | 00:00:00.01 | 3 | ||
| TABLE ACCESS FULL | T | 1 | 2 | 6 | 00:00:00.01 | 3 |
Predicate Information (identified by operation id):
1 - filter(("SAL">=2000 AND "SAL_BO">=200))
SQL> SELECT /*+ DYNAMIC_SAMPLING( 4 ) */ * FROM T
2 WHERE SAL >= 2000
3 AND SAL_BO >= 200;
EMPNO ENAME SAL SAL_BO
6 개의 행이 선택되었습니다.
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
0 | SELECT STATEMENT | 1 | 6 | 00:00:00.01 | 3 | ||
| TABLE ACCESS FULL | T | 1 | 6 | 6 | 00:00:00.01 | 3 |
Predicate Information (identified by operation id):
1 - filter(("SAL">=2000 AND "SAL_BO">=200))
{CODE}
-- 패키지 설치 해야할거같은데. 시간이 없어요 ;;
SQL> var ret varchar2(30);
SQL> exec :ret := dbms_stats.create_extended_stats( user, 't', '(SAL, SAL_BO )' );
BEGIN :ret := dbms_stats.create_extended_stats( user, 't', '(SAL, SAL_BO )' ); END;
*
1행에 오류:
ORA-20000: Unable to create extension: not supported for SYS owned table
ORA-06512: "SYS.DBMS_STATS", 8433행
ORA-06512: "SYS.DBMS_STATS", 32587행
ORA-06512: 1행
SQL> SHOW USER
USER은 "SYS"입니다
SQL> begin
2 dbms_stats.gather_table_stats( user, 'T', method_opt=> 'for all columns size skewonly for columns ( sal,sal_bo ) size 254' );
3 end;
4 /
begin
*
1행에 오류:
ORA-20000: Unable to create extension: not supported for SYS owned table
ORA-06512: "SYS.DBMS_STATS", 23829행
ORA-06512: "SYS.DBMS_STATS", 23880행
ORA-06512: 2행
DROP TABLE T PURGE;
CREATE TABLE T AS SELECT * FROM ALL_OBJECTS;
CREATE INDEX T_OWNER_IDX ON T( OWNER ) ;
begin
dbms_stats.gather_table_stats( user, 'T', method_opt=> 'for all columns size 1' );
end;
/
ALTER SESSION SET "_OPTIMIZER_COST_MODEL" = io;
SET AUTOTRACE TRACEONLY EXP;
SQL> SELECT /*+ INDEX( T ) */ * FROM T WHERE OWNER = 'SYS';
Id | Operation | Name | Rows | Bytes | Cost |
0 | SELECT STATEMENT | 3139 | 297K | 91 | |||||||||
1 | TABLE ACCESS BY INDEX ROWID | T | 3139 | 297K | 91 | – 91 - 8 예상함 ( 클러스터링 팩터가 비용 계산식에 고려 됨 ) |
| INDEX RANGE SCAN | T_OWNER_IDX | 3139 | 8 | – 8 예상함 --- |
Predicate Information (identified by operation id):
2 - access("OWNER"='SYS')
Note
{CODE}
{CODE:sql}
--유효 인덱스 선택도 : 인덱스 Access Predicate
--유효 테이블 선택도 : 인덱스 Access Predicate와 Filter Predicate에 의해 결정된다.
--(최종) 테이블 선택도 : 테이블 Filter Predicate 까지 포함한 모든 조건절에 의해 결정
비용 = blevel + -- 인ㄷ게스 수직적 탐색 비용
(리프 블록 수 * 유효 인덱스 선택도 ) + -- 인덱스 수평적 탐색 비용
(클러스터링 팩터 * 유효 테이블 선택도 ) -- 테이블 Random 엑세스 비용
{CODE}