SELECT 해당층, 평당가 , 입력일, 해당동, 매물구분, 연사용일수, 중개업소코드 FROM
매물 아파트매매
WHERE 인터넷매물 IN ('1', '2', '3')
AND 아파트 시세코드 = 'A01011350900056'
AND 평형 = '59'
AND 평형 타입 = 'A'
ORDER BY 입력일 DESC

이는 다음과 같은 sql을 작성한 거와 같음
SELECT 해당층, 평당가, 입력일, 해당동, 매물구분, 연사용일수, 중개업소코드
FORM 매물 아파트 매매
WHERE 인터넷 매물= '1'
AND 아파트시세코드 = 'A01011350900056'
AND 평형 = '59'
AND 평형타입 = 'A'
UION ALL
SELECT 해당층, 평당가, 입력일, 해당동, 매물구분, 연사용일수, 중개업소코드
FORM 매물 아파트 매매
WHERE 인터넷 매물= '2'
AND 아파트시세코드 = 'A01011350900056'
AND 평형 = '59'
AND 평형타입 = 'A'
UNION ALL
SELECT 해당층, 평당가, 입력일, 해당동, 매물구분, 연사용일수, 중개업소코드
FORM 매물 아파트 매매
WHERE 인터넷 매물= '3'
AND 아파트시세코드 = 'A01011350900056'
AND 평형 = '59'
AND 평형타입 = 'A'
ORDER BY 입력일 DESC
IN-LIST 개수만큼 UNION ALL 브랜치가 생성
각 브랜치마다 모든 컬럼을 = 조건으로 검색하므로 앞선 선두 컬럼에 BETWEEN을 사용할 때와 같은 비효율이 사라짐
INDEX SKIP SCAN 방식으로 유도해도 비슷한 효과를 얻을 수 있음
IN-List 항목 개수가 늘어날 수 있다면 BETWEEN을 IN-List 로 전환하는 방식은 사용하기 곤란하다
SELECT /*+ ORDERD USE_NL(b) */ b.해당층, b.평당가, b.입력일,
b.해당동, b.매물구분, b.연사용일수, b.중개업소코드
from 통합코드 a, 매물아파트매매 b
where a.코드구분, 'CD064' -- 인터넷매물구분
and a.코드 between '1' and '3'
and b.인터넷매물 = a.코드
and b.아파트시세코드 = 'A01011350900056'
and b.평형 = '59'
and b.평형타입 = 'A'
ORDER BY b.입력일 DESC

고객 등급 + 고객 번호 순으로 구성한 인덱스에서 고객번호 = 123 조건을 만족하는 레코드가 서로 멀리떨어져 있을때 BETWEEN 족너을 IN-List로 전환하는 기법이 유용
WHERE 고객등급 BETWEEN 'C' AND 'D'
AND 고객번호 = 123

즉 BETWEEN 조건 때문에 인덱스를 비효율적으로 스캔하더라도 블록 I/O 측면에서는 대개 소량에 그치는 경우가 많음
데이터 분포나 수직적 탐색 비용을 따져 보지도 않고 BETWEEN 을 IN-List로 변환하는 우를 범하지 않기!!
create table 월별고객별판매집계
as
select rownum 고객번호
, '2018' || lpad(ceil(rownum/100000), 2, '0') 판매월
, decode(mod(rownum, 12),1, 'A', 'B') 판매구분
, round(dbms_random.value(1000,10000), -2)판매금액 from dual
connect by level <= 1200000;
create index 월별고객판매집계_IDX1 on 월별고객별판매집계(판매구분, 판매월);
인덱스 설정후
select count(*)
from 월별고객별판매집계 t
where 판매구분 = 'A'
AND 판매월 BETWEEN '201801' AND '201812'
조회시 281개의 블록 I/O 발생
테이블 엑세스는 전혀 발생하지 않음
CREATE INDEX 월별고객판매집계_IDX2 ON 월별고객판매집계(판매월, 판매구분);
BETWEEN 조건 판매월 컬럼이 선두 아래 인덱스 사용시
3090개 블록 IO발생
B인 레코드 까지 모두 스캔하고 버렸기 때문
SELECT /** index(t 월별고객판매집계_IDX2)* /COUNT(*)
FROM 월별고객별판매집계 T
WHERE 판매구분 ='A'
AND 판매월 IN ('201801',201802','201803',201804','201805',201806'
,'201807',201808','201809',201810',
'201811',201812');
3090개이던 블록 I/O 개수가 314개로 감소
인덱스 브랜치 블록을 열두번 반복 탐색 했지만 리프 블록을 스캔할때의 비효율을 제거함으로써 성능이 열배 좋아짐
select /*+ INDEX_SS(t 월별고객별판매집계_IDX2) */ COUNT(*)
FROM 월별고객별판매집계 T
WHERE 판매구분 ='A'
AND 판매월 BETWEEN '201801' AND '201812';
300블록 읽음
| 구분 | IDX1 인덱스 | BETWEEN | IN-LIST | SKIP SCAN |
|---|---|---|---|---|
| 블록I/O | 281 | 3090 | 3140 | 300 |
SELECT * FROM 고객별가입상품
WHERE 고객번호 = :CUST_NO AND 상품ID IN ('NH00037', NH'00041' 'NH00050');
인덱스를 상품 + 고객 번호 순으로 생성하면 상품은 고객 번호 순으로 정렬된 하나 혹은 두개의 리프 블록에 저장
반면 고객번호 기준으로 같은 고객번호가 상품 ID 에 따라 뿔뿔이 흩어진 상태가됨
인덱스가 이런식으로 구성 되면 상품 ID 조건절이 IN-List Iterator 방식으로 풀리는 것이 효과적
고객 번호 조건을 만족하는 레코드가 서로 멀리 떨어져 있기 때문
SELECT *
FROM 고객별가입상품
WHERE 고객번호 = :cust_no
AND 상품ID IN = ‘NH00037’
UNION ALL
SELECT *
FROM 고객별가입상품
WHERE 고객번호 = :cust_no
AND 상품ID IN = ‘NH00041’
UNION ALL
SELECT *
FROM 고객별가입상품
WHERE 고객번호 = :cust_no
AND 상품ID IN = ‘NH00050’
다음과 같이 상품 id 조건절을 이처럼 IN-List Iterator 방식으로 풀면 고객번호와 상품 ID 둘다 인덱스 엑세스 조건으로 사용
인덱스를 고객번호 + 상품 ID 순으로 생성시
고객 상품 ID 순으로 정렬된 상태로 같은 리프 블록에 저장
요컨대, IN 조건은 ‘=’이 아니다. IN조건이 ‘=’이 되려면 IN-List Iterator 방식으로 풀려야만 한다. 그렇지 않으면, IN 조건은 필터 조건이다.
SELECT /*+ num_index_keys(a 고객별가입상품_x1 1) */ *
from 고객별가입상품 a
where 고객번호 = :cust_no
and 상품ID IN ('NH00037', 'NH00041', 'NH00050')
SELECT *
FROM 고객별가입상품
WHERE 고객번호 = :CUST_NO
AND RTRIM(상품ID) IN ('NH00037', 'NH00041', 'NH00050')
SELECT *
FROM 고객별가입상품
WHERE 고객번호 = :CUST_NO
AND 상품ID|| ' ' IN ('NH00037', 'NH00041', 'NH00050')
EX)
WHERE 판매월 BETWEEN '201901' AND '201912'
AND 판매구분 = 'B'
WHERE 판매월 LIKE '2019%'
AND 판매구분 = 'B'

WHERE 판매월 BETWEEN '201901' AND '201912'
ANS 판매구분 = 'A'
WHERE 판매월 LIKE '2019%'
AND 판매구분 = 'A'

SELECT * FROM 거래
WHERE (:cust_id is null or 고객ID = :cust_id)
and 거래일자 between :dt1 and dt2
고객 ID 거래 일자 순으로 인덱스 구성해도 이를 사용할 수 없다
거래일자 + 고객 ID 순으로 구성한 인덱스는 사용할 수 있다
즉 OR 조건 활용한 옵션 조건 처리는
select * from 거래
where 고객ID = :cust_id
and ((:dt_type = 'A' AND 거래일자 BETWEEN :DT1 AND DT2)
OR
(:dt_type = 'B' AND 거래일자 BETWEEN :dt1 and dt2))
SELECT * FROM 상품
WHERE 등록일시 >= trunc(sysdate) // 필수 조건
and 상품분류코드 like :prd_cls_cd || '%' // 옵션조건
SELECT * FROM 상품
WHERE 상품대분류코드 >= :prd_lcls_Cd // 필수 조건
and 상품코드 like :prd_cls_cd || '%' // 옵션조건
LIKE/BETWEEN 패턴 사용하고자 할때 반드시 점검 내용
SELECT * FROM 거래
WHERE :cust_id is null
and 거래일자 between :dt1 and :dt2
union all
select * from 거래
where :cust_id is not null
and 고객id= :cust_id
and 거래일자 between :dt1 and dt2
select * from 거래
where 고객ID = nvl(:cust_id, 고객ID)
and 거래일자 between :dt1 and dt2
또는
select * from 거래
where 고객ID = decode(:cust_id, null, 고객ID, :cust_id)
and 거래일자 between :dt1 and dt2
:cust_id 를 입력하지 않음 거래일자 선두 인덱스
변수 입력시 고객 +거래일자 인덱스 사용
UNION ALL 보다 단순하면서도 UNION ALL 과 같은 성능을 낸다
단점은 앞서 설명한 LIKE 패턴처럼 NULL 허용 컬럼에 사용할 수 없다는데 있다.
옵션 조건 처리용 NVL/DECODE 함수를 여러개 사용하면 그중 변별력이 가장 좋은 컬럼 기준으로 한번만 OR Expansion이 일어난다
느린 이유
select /*+ full(a) */ 회원번호, 회원명, 생년, 생월일, 등록일자
from 회원 a
where 암호화된_전화번호 = encryption(:phone_no)
select /*+ full(a) */ 회원번호, 회원명 , 생년, 생월일, 등록일자 from 회원 a
where 생년 = '1987'
and 암호화된_전화번호 = encryption(:phone_no)
// 조건절 생년을 만족하는 건수만큼 수행됨
create index 회원_X01 on 회원 (생년);
// 암호화된 전화번호 조건절을 테이블 액세스 단계에서 필터링 즉 생년 조건을 만족하는 건수만큼 수행
create index 회원_X02 on 회원 (생년, 생월일, 암호화된_전화번호);
// 인덱스 필터 조건 인덱스 스캔 횟수 즉 생년 조건을 만족하는 건수만큼 수행
create index 회원_X03 on 회원 (생년, 암호화된_전화번호);
// encryption 함수 단 함번 수행