별의 공부 블로그 🧑🏻‍💻
728x90
728x170

SQLD 실전 문제 : 인덱스와 조인

문제 1

Q. 아래 SQL과 트레이스 결과를 보고, 최적의 튜닝 방안을 선택하시오.
select o.주문번호, o.주문일시, o.고객번호, o.주문금액
from 주문 o inner join 고객 c on c.고객번호 = o.고객번호
where o.주문일시 = :ord_dt
Call        Count    CPU Time Elapsed Time  Disk    Query    Current    Rows
--------- --------- --------- ------------- ----- --------- --------- ---------
Parse            1    0.000      0.028         0         0         0         0
Execute          1    0.000      0.000         0         0         0         0
Fetch        45939    9.234     12.642     13563    149480         0    459379
--------- --------- --------- ------------- ----- --------- --------- ---------
total        45941    9.234     12.670     13563    149480         0    459379

Rows        Row Source Operation
-------    --------------------------------------------------------------------
 459379     NESTED LOOPS (cr=149481 pr=13563 pw=0 time=19337719 us)
 459379     TABLE ACCESS BY INDEX ROWID 주문 (cr=103541 pr=13562 pw=0 time=8766716 us)
 459379     INDEX RANGE SCAN 주문_주문일시_IDX (cr=46902 pr=968 pw=0 time=1879909 us)
 459379     INDEX UNIQUE SCAN 고객+PK (cr=45939 pr=1 pw=0 time=5375998 us)


① 힌트를 이용해 고객 테이블을 먼저 드라이빙하도록 유도한다.

select /*+leading(c)use_nl(o)*/ o.주문번호, o.주문일시, o.고객번호, o.주문금액
from 주문 o inner join 고객 c on c.고객번호 = o.고객번호
where o.주문일시 = :ord_dt


② 힌트를 이용해 해시 조인으로 유도한다.

select /*+use_hash(oc)*/ o.주문번호, o.주문일시, o.고객번호, o.주문금액
from 주문 o inner join 고객 c on c.고객번호 = o.고객번호
where o.주문일시 = :ord_dt


③ 고객과의 조인문을 Exists 서브쿼리로 변환한다.

select o.주문번호, o.주문일시, o.고객번호, o.주문금액
from 주문 o
where o.주문일시 = :ord_dt
and exists (select 'x' from 고객 where 고객번호 = o.고객번호)


④ 고객과의 조인을 제거한다.

select 주문번호, 주문일시, 고객번호, 주문금액
from 주문
where 주문일시 = :ord_dt

 

더보기

정답 : ④

해설 : ERD에서 주문과 고객은 M:1 관게이고, 주문 테이블 고객번호는 Null 값을 허용하지 않는다. 뿐만 아니라 SQL문에서 조인 조건 외에 어디서도 고객 테이블을 참조하지 않고 있다. 따라서 고객과의 조인은 불필요하다.

 

문제 2

Q. 인덱스 탐색 과정은 수직적 탐색과 수평적 탐색으로 나눠서 설명할 수 있다. 수평적 탐색은 인덱스 리프 블록에 저장된 레코드끼리 연결된 순서에 따라 좌에서 우, 또는 우에서 좌로 스캔하기 때문에 '수평적'이라고 표현한다. 수직적 탐색은 수평적 탐색을 위한 시작 지점을 찾는 과정이라고 말할 수 있다. 다음 중 아래 SQL에서 EMP_IDX 인덱스의 수평적 탐색 시작 지점의 값을 보기에서 고르시오.

<인덱스 구성>

emp_idx : deptno + sal + comm

select /*+ index_asc(e emp_idx) */ empno, ename, sal, comm, hiredate
from emp e
where deptno = 20
and sal between 2000 and 3000
and comm <= 100


① 인덱스 정렬 순서 상 deptno = 20 조건을 만족하는 첫 번째 레코드
② 인덱스 정렬 순서 상 deptno = 20 and sal = 2000 조건을 만족하는 첫 번째 레코드
③ 인덱스 정렬 순서 상 deptno = 20 and sal = 2000 and comm = 100 조건을 만족하는 첫 번째 레코드
④ 인덱스 정렬 순서 상 deptno = 20 and sal = 3000 and comm = 100 조건을 만족하는 첫 번째 레코드

 

더보기

정답 : 

해설 : 인덱스 정렬 순서 상 deptno = 20 and sal = 2000 조건을 만족하는 첫 번째 레코드에서부터 수평적 탐색을 시작한다. comm 조건이 less than or equal인 점에 주목한다. comm 조건이 more than or equal 이면 deptno = 20 and sal = 2000 and comm = 100 조건을 만족하는 첫 번째 레코드에서부터 스캔을 시작한다.

 

문제 3

Q. 다음 중 아래와 같은 인덱스 상황에서 Index Range Scan이 불가능한 SQL은?
create index emp_idx on emp(deptno, job, ename);


① 

select empno, ename, job, hiredate
from emp
where deptno = :deptno
and ename = :ename;


② 

select empno, ename, job, hiredate
from emp
where deptno = :deptno
and job is null;


③ 

select empno, ename, job, hiredate
from emp
where deptno is null
and job = -job;


④ 

select empno, ename, job, hiredate
from emp
where job = :job
and ename = :ename;

 

더보기

정답 :

해설 : Index Range Scan이 가능하려면 인덱스 선두 칼럼이 조건절에 사용되어야 한다.

 

Index Range Scan은 인덱스 루트 블록에서 리프 블록까지 수직적으로 탐색한 후에 리프 블록을 필요한 범위(Range)만 스캔하는 방식이다.

 

문제 4

Q. 다음 중 힌트를 사용하더라도 Index Skip Scan 방식으로 실행되지 않는 SQL인 것은?
create index 고객_x01 on 고객(거주지역, 성별, 가입일자);
create index 고객_x02 on 고객(고객등급, 생일);


① 

select * from 고객
where 거주지역 = '충청'
and 가입일자 between '20110201' and '20110228'


② 

select * from 고객
where 가입일자 between '20110201' and '20110228'


③ 

select * from 고객
where 고객등급 between 'A' and 'C'
and 생일 = '0326'


④ 

select * from 고객
where 고객등급 in ('A', 'B', 'C')
and 생일 = '0326'

 

더보기

정답 : 

해설 : Index Skip Scan을 활용하려면 인덱스 선행 칼럼이 누락됐거나 부등호, between, like 같은 범위 검색 조건이어야 한다. ①번의 경우, 인덱스 선두 칼럼이 사용됐지만, 성별 칼럼이 조건에서 누락됐으므로 Index Skip Scan 활용이 가능하다.

 

※ Index Skip Scan

- 루트 또는 브랜치 블록에서 읽은 칼럼 값 정보를 이용해 조건에 부합하는 레코드를 포함할 "가능성이 있는" 하위 블록(브랜치 또는 리프 블록)만 골라서 액세스하는 방식

 

※ Index Full Scan

- 수직적 탐색 없이 인덱스 리프 블록을 처음부터 끝까지 수평적으로 탐색하는 방식

- 대개는 데이터 검색을 위한 최적의 인덱스가 없을 때 차선으로 선택됨.

 

※ Index Unique Scan 

- 수직적 탐색만으로 데이터를 찾는 스캔 방식

- Unique 인덱스를 '=' 조건으로 탐색하는 경우에 작동함.

 

※ Index Fast Full Scan

- Index Full Scan 보다 빠름. (인덱스 트리 구조를 무시하고 인덱스 세그먼트 전체를 Multiblock Read 방식으로 스캔하기 때문)

 

문제 5

Q. 다음 중 인덱스 구성이 아래와 같을 때 SQL을 처리하는 데 활용 가능한 인덱스 스캔 방식을 2개 고르시오.
create index emp_x01 on emp(deptno, sal);

select empno, ename, deptno, sal
from emp
where sal between 2000 and 4000;


① index range scan
② index full scan
③ index fast full scan
④ index skip scan

 

더보기

정답 : ②, ④

해설 : sal 칼럼을 선두로 갖는 인덱스가 없으므로 range scan은 사용할 수 없다. fast full scan은 인덱스에 포함된 칼럼으로만 조회할 때 사용할 수 있다.

 

문제 6

Q. 다음 중 비트맵 인덱스에 대한 설명으로 가장 부적절한 것은?

① B*Tree 인덱스에 비해 테이블 Random 액세스를 획기적으로 줄여주므로 성별처럼 Distinct Value 개수가 적은 칼럼에도 좋은 성능을 나타낸다.
② 고객번호처럼 Distinct Value 개수가 아주 많을 때 B*Tree 인덱스보다 훨씬 많은 공간을 차지한다.
③ 여러 비트맵 인덱스를 동시에 사용 가능하고, Bitwise Not 연산도 가능하다.
④ Lock에 의한 DML 부하가 심각해서 OLTP성 환경에는 부적합하다.

 

더보기

정답 : ①

해설 : 성별처럼 Distinct Value 개수가 적은 칼럼에 사용할 때 B*Tree 인덱스보다 훨씬 적은 공간을 차지하기 때문에 유리하지만, 테이블 Random 액세스 발생 측면에서는 그다지 이점이 없다.

 

※ 비트맵 인덱스

- Lock에 의한 DML 부하가 심한 것이 단점이다.

- 레코드 하나만 변경되더라도 해당 비트맵 범위에 속한 모든 레코드에 Lock이 걸린다.

- OLTP성 환경에 비트맵 인덱스를 쓸 수 없는 이유가 여기에 있다.

- 비트맵 인덱스는 읽기 위주의 대용량 DW(특히, OLAP) 환경에 아주 적합하다.

 

문제 7

Q. 다음 중 SQL을 처리하기에 가장 효율적인 인덱스 구성을 고르시오. (단, 클러스터링 팩터는 고려하지 않는다.)

[SQL문]

select *
from 주문
where 주문유형코드 = :a
and 지점코드 = :b
and 계좌번호 = :c
and 주문일자 between :d and :e;


[값의 종류 개수(Number Of Distinct Value)]

주문유형코드      : 5
계좌번호          : 500,000
지점코드          : 100
주문일자          : 1,800


① 지점코드 + 주문일자 + 계좌번호
② 계좌번호 + 주문일자 + 지점코드
③ 주문일자 + 계좌번호 + 지점코드
④ 주문유형코드 + 지점코드 + 주문일자 + 계좌번호

 

더보기

정답 : 

해설 : 계좌번호 =, 지점번호 between 조건만으로도 충분히 스캔 범위를 줄일 수 있다.

 

문제 8

Q. 다음 중 아래 SQL문을 튜닝하기 위한 분석을 진행 중 분석한 내용으로 가장 부적절한 것은?
create index 주문_idx on 주문(주문일자);

select 고객ID, 연락처, 고객등급
from 주문
where 주문일자 = '20110725'
and 배송상태 = 'ING';


① [주문일자 = '20110725'] 조건만으로 인덱스 탐색이 이루어진다.
② [배송상태 = 'ING'] 조건에 대한 필터링은 테이블 액세스 단계에서 이루어진다.
③ SQL 문장에 [배송상태 = 'ING'] 조건절이 없다면 테이블은 액세스하지 않아도 된다.
④ [배송상태 = 'ING'] 조건을 만족하는 레코드 비중이 아주 낮다면, 주문일자_idx 인덱스가 주문일자와 배송상태 두 칼럼을 모두 포함하도록 구성함으로써 쿼리 성능을 향상시킬 수 있다. 이 쿼리만 고려한다면, 두 칼럼 간 순서는 자유롭게 선택해도 무방하다.

 

더보기

정답 : ③

해설 : 배송상태 = 'ING' 조건절이 없더라도 고객ID, 연락처, 고객등급을 읽기 위해 테이블을 액세스해야 한다.

 

문제 9

Q. 성능 향상을 위해 아래 왼쪽 SQL을 오른쪽과 같이 변환하였다. 인덱스 구성을 보기와 같이 ①②③④ 순으로 바꿔 가며 SQL을 실행할 때, 오른쪽 변환된 SQL의 블록 I/O가 오히려 늘어나는 인덱스 구성을 2개 고르시오.
select *
from 고객
where 가입일자 like '2010%'
and 고객등급 between 'A' and 'B'

                                                                    ▼

select *
from 고객
where 가입일자 like '2010%'
and 고객등급 = 'A'
union all
select *
from 고객
where 가입일자 like '2010%'
and 고객등급 = 'B'


① 가입일자
② 고객등급
③ 가입일자 + 고객등급
④ 고객등급 + 가입일자

 

더보기

정답 : ①, ③

해설 : ①번과 같은 인덱스 구성에서 SQL을 오른쪽과 같이 변환하면 인덱스에서 가입일자 like 조건에 해당하는 범위를 2번 스캔하게 된다. 고객등급을 테이블에서 필터링하므로 테이블 Random 액세스량도 2배 증가한다. ③번과 같은 인덱스 구성에서는 고객등급을 인덱스에서 필터링하므로 테이블 Random 액세스량은 늘지 않지만, 인덱스에서 같은 범위를 2번 스캔하므로 블록 I/O가 오히려 늘어난다.

 

문제 10

Q. 아래 트레이스 결과를 가장 적절히 설명한 보기를 2개 고르시오.
Call        Count    CPU Time Elapsed Time  Disk    Query    Current    Rows
--------- --------- --------- ------------- ----- --------- --------- ---------
Parse            1    0.010      0.012         0         0         0         0
Execute          1    0.000      0.000         0         0         0         0
Fetch           78   10.150     49.199     27830    266468         0      1909
--------- --------- --------- ------------- ----- --------- --------- ---------
Total           80   10.160     49.231     27830    266468         0      1909

Rows         Row Source Operation
--------    ---------------------------------------------------------------------------
   1909     TABLE ACCESS BY INDEX ROWID TAB1 (cr=266468 pr=27830 pw=0 time=58480816 us)
 266476     INDEX RANGE SCAN TAB1_X01 (cr=511 pr=300 pw=0 time=1893462 us) OF ...


① TAB1_X01 인덱스의 클러스터링 팩터는 매우 좋은 상태다.
② TAB1_X01 인덱스에 칼럼을 추가하면 성능을 높이는 데 매우 큰 도움이 된다.
③ TAB1_X01 인덱스 칼럼 순서를 조정하면 성능을 높이는 데 매우 큰 도움이 된다.
④ CPU Time과 Elapsed Time 간 39초 가량 차이가 발생한 이유는, 27,000여 개 디스크 블록을 읽기 위한 I/O Call 과정에 발생한 대기 현상 때문일 가능성이 가장 높다.

 

더보기

정답 : ②, ④

해설 : TAB1_X01 인덱스의 클러스터링 팩터는 매우 나쁜 상태다. 테이블 액세스 횟수만큼 블록 I/O가 발생한 것을 통해 이를 알 수 있다. 511개 인덱스 블록을 스캔했는데, 이것은 인덱스 스캔 과정에서 얻는 266,476개 레코드 수에 비하면 그리 큰 수치가 아니다. 비효율이 크지 않다는 뜻이다. TAB_X01 인덱스 칼럼 순서를 조정하면 블록 I/O가 약간 감소할 수 있을지 모르지만, 전체 성능에 미치는 영향은 크지 않을 것이다.

 

※ 인덱스 설계를 위해 고려해야 할 요소

- 쿼리 수행 빈도
- 업무상 중요도
- 클러스터링 팩터
- 데이터량
- DML 부하(= 기존 인덱스 개수, 초당 DML 발생량, 자주 갱신되는 칼럼 포함 여부 등)
- 저장 공간
- 인덱스 관리 비용 등

 

728x90

 

문제 11

Q. 다음 중 SQL에 사용된 다양한 액세스 유형을 분석해 시스템 전체적인 관점에서 전략적인 인덱스 설계를 하려고 한다. 결합 인덱스 키 칼럼을 선정하는 데 있어 가장 중요한 선택 기준 두 가지를 순서대로 나열한 것으로 가장 적절한 것은?
ㄱ. 데이터 분포
ㄴ. '=' 조건으로 자주 조회되는지
ㄷ. 조건절에 항상 또는 자주 사용되거나
ㄹ. 정렬 기준 칼럼으로 자주 사용되는지


① ㄱ → ㄴ
② ㄴ
③ ㄷ  ㄴ
④ ㄷ  ㄹ

 

더보기

정답 :

해설 : 인덱스를 설계하는 가장 중요한 선택 기준은, 조건절에 항상 또는 자주 사용되는지 여부와 '=' 조건으로 자주 조회되는지 여부이다. 인덱스 선두 칼럼이 일단 조건절에 사용돼야 해당 인덱스가 사용될 수 있으므로 전자가 특히 중요하다.

 

※ 결합 인덱스 구성

- 첫 번째 기준 : 조건절에 항상 사용되거나, 적어도 자주 사용되는 칼럼들을 선정한다.
- 두 번째 기준 : 그렇게 선정된 칼럼 중 '='
조건으로 자주 조회되는 칼럼을 앞쪽에 두어야 한다.
- 세 번째 기준 : 소트 오퍼레이션을 생략하도록 하기 위해 칼럼을 추가한다.

 

문제 12

Q. 다음 중 아래 인덱스 구성과 SQL을 고려할 때, 실행 계획 맨 아래(ID=5) 주문_IDX 인덱스의 액세스 조건으로 가장 적절한 것은?

<인덱스 구성>

create index 주문_IDX on 주문(주문일자, 고객번호);

select /*+ ordered use_nl(o) */ *
from 고객 c, 주문 o
where c. 가입일자 = '20130414'
and o.고객번호 = c.고객번호
and o.주문일자 = '20130414'
and o.상품코드 = 'A123';
Execution Plan
------------------------------------------------------------
0   SELECT STATEMENT Optimizer=ALL_ROWS
1 0   TABLE ACCESS (BY INDEX ROWID) OF '주문' (TABLE)
2 1     NESTED LOOPS
3 2      TABLE ACCESS (BY INDEX ROWID) OF '고객' (TABLE)
4 3        INDEX (RANGE SCAN) OF '고객_X01' (INDEX)
5 2      INDEX (RANGE SCAN) OF '주문_IDX' (INDEX)


① o.고객번호 = c.고객번호
② o.주문일자 = '20130414'
③ o.주문일자 = '20130414' and o.고객번호 = c.고객번호
④ o.주문일자 = '20130414' and o.고객번호 = c.고객번호 and o.상품코드 = 'A123'

 

더보기

정답 : ③

해설 : 상품코드는 인덱스 구성 칼럼이 아니므로 주문 테이블 액세스(ID=1) 단게의 필터 조건이다.

 

문제 13

Q. SQL 트레이스를 수집한 결과 Row Source Operation이 아래와 같았다. 튜닝을 위해 가장 우선적으로 검토할 사항으로 가장 적절한 것은? (단, 한 달간 주문 건수는 평균 50만 건이다.)
select c.고객명, c.연령, c.전화번호, o.주문일자, o.주문종금액, o.배송지주소
from 고객 c, 주문 o
where o.고객번호 = c.고객번호
and c.고객등급 = 'A'
and c.연령 between 51 and 60
and o.주문일자 between '20101201' and '20101231'
Rows         Row Source Operation
-------    ---------------------------------------------------------------------------
   10       NESTED LOOPS
   23       TABLE ACCESS BY INDEX ROWID 고객
 2978       INDEX RANGE SCAN 고객_IDX
   10       TABLE ACCESS BY INDEX ROWID 주문
   28       INDEX RANGE SCAN 주문_IDX


① 고객_IDX 인덱스 칼럼 순서를 조정한다.
② 고객_IDX 인덱스에 칼럼을 추가한다.
③ 주문_IDX 인덱스에 칼럼을 추가한다.
④ 테이블 조인 순서를 변경한다.

 

더보기

정답 : 

해설 : 고객_IDX가 연령+고객등급 구성이라면 순서를 바꿨을 때 엑세스량 개선이 가능하다. 한 달간 주문 건수는 평균 50만 건이므로 조인 순서 변경은 성능에 도움이 되지 않는다.

 

문제 14

Q. 테이블과 인덱스 구성은 아래와 같다. INDEX RANGE SCAN으로 데이터를 액세스할 수 있는 SQL로 가장 적절한 것은?
테이블명 : 고객

[칼럼 리스트]
고객 ID : NOT NULL, VARCHAR2(10)
고객명 : NOT NULL, VARCHAR2(20)
직업코드 : NULLABLE, VARCHAR2(3)

[인덱스 구성]
고객_PK : 고객ID
고객_X01 : 직업코드, 고객명


① SELECT * FROM 고객 WHERE SUBSTR(고객ID, 1, 3) = '100'
② SELECT * FROM 고객 WHERE 고객명 = '김시험'
③ SELECT * FROM 고객 WHERE 직업코드 IS NULL
④ SELECT * FROM 고객 WHERE 고객ID = 1000000009

 

더보기

정답 :

해설 : 직업코드 조건으로 인덱스를 Range Scan하려면 직업코드를 선두로 갖는 인덱스여야 한다. 고객 _X01 인덱스가 여기에 해당한다. 인덱스 구성 칼럼 중 하나라도 NOT NULL 칼럼이면 IS NULL 조회에 인덱스를 사용할 수 있다. 따라서 직업코드 IS NULL 조회에 고객_X01 인덱스를 사용할 수 있다.

 

문제 15

Q. Oracle에서 TAB1, TAB2 순으로 NL 조인하도록 유도하고 싶다. 다음 중 (  ㄱ  ) 안에 넣을 바른 힌트 사용법을 2개 고르시오.
SELECT /* + (    ㄱ    ) */ ...
FROM TAB1 A, TAB2 B
WHERE A.KEY = B.KEY


① ORDERED USE_NL(B)
② ORDERED USE_NL(TAB2)
③ LEADING(A) USE_NL(B)
④ DRIVING_SITE(A) USE_NL(B)

 

더보기

정답 : ①,

해설 : 테이블 Alias가 있는 상황에선 반드시 Alias를 사용해야 한다.

 

문제 16

Q. 다음 중 Hash Join에 대한 설명으로 가장 부적절한 것을 2개 고르시오.

① 조인 연결고리에 equi-join 조건이 하나라도 있어야 한다.
② 일반적으로, 큰 집합으로 해시 테이블을 생성하고서 작은 집합을 읽으면서 이 해시 테이블을 탐색하는 게 유리하다.
③ OLTP 환경에서 수행 빈도가 아주 높은 쿼리라도 부담 없이 사용할 수 있다.
④ Build Input으로 선택된 집합의 조인 칼럼에는 중복 값이 거의 없어야 효과적이다.

 

더보기

정답 : ②, 

해설 : 해시 조인을 할 때는 작은 집합으로 해시 테이블을 생성하는 것이 유리하다. OLTP 환경에서 수행 빈도가 아주 높은 쿼리를 해시 조인으로 처리하면 CPU Usage가 높아질 수 있다.

 

※ Hash Join 성능을 좌우하는 키 포인트
- 한 쪽 테이블이 가용 메모리에 담길 정도로 충분히 작아야 함.
- Build Input 해시 키 칼럼에 중복 값이 거의 없어야 함.

 

※ Hash Join 선택 기준
- 조인 칼럼에 적당한 인덱스가 없어 NL Join이 비효율적일 때
- 조인 칼럼에 인덱스가 있더라도 NL Join 드라이빙 집합에서 Inner 쪽 집합으로의 조인 액세스량이 많아 Random 액세스 부하가 심할 때
- Sort Merge Join 하기에는 두 테이블이 너무 커 소트 부하가 심할 때
- 수행 빈도가 낮고 쿼리 수행 시간이 오래 걸리는 대용량 테이블을 조인할 때

 

문제 17

Q. 다음 중 스칼라 서브쿼리의 특징을 설명한 것으로 가장 부적절한 것은?

① 스칼라 서브쿼리를 이용한 조인은 NL 조인처럼 한 레코드씩 순차적으로 진행한다.
② 메인 쿼리의 레코드마다 스칼라 서브쿼리를 통해 하나의 레코드, 하나의 값만 리턴한다.
③ 조인에 실패할 경우, Null을 리턴한다.
④ 입력 값(=조인 컬럼)과 출력 값(=리턴 값)을 Shared Pool에 있는 Result Cache에 캐싱했다가 같은 입력 값에 대해서는 캐싱된 값을 리턴함으로써 조인 부하를 줄여준다.

 

더보기

정답 : ④

해설 : Result Cache가 아니라 PGA에 캐싱한다.

 

※ Scalar Subquery

- 서브쿼리 중에서 함수처럼 한 레코드당 정확히 하나의 값만을 리턴하는 서브쿼리

- 주로 Select-list 에서 사용되지만, 몇 가지 예외사항을 뺀다면 칼럼이 올 수 있는 대부분 위치에서 사용 가능 

 

문제 18

Q. 다음 중 아래 SQL1과 SQL2의 실행 계획에 대한 설명으로 가장 부적절한 것은?

[SQL1]

SELECT /*+ LEADING(A) USE_NL(B) */ b.상품분류코드, a.기준일자,
       SUM(a.판매금액) AS 판매금액, SUM(a.판매수량) AS 판매수량
FROM 일별매출 a, 상품 b
WHERE a.기준일자 BETWEEN TO_DATE(:v_시작기준일자, 'YYYYMMDD') AND TO_DATE(:v_종료기준일자, 'YYYYMMDD')
      AND b.상품코드 = a.상품코드
      AND b.판매시작일자 >= TO_DATE(:v_판매시작일자, 'YYYYMMDD')
GROUP BY b.상품분류코드, a.기준일자;
------------------------------------------------------------------
| Id  | Operation                                    | Name      |
------------------------------------------------------------------
|   0 | SELECT STATEMENT                             |           |
|   1 |  HASH GROUP BY                               |           |
|*  2 |   FILTER                                     |           |
|   3 |    NESTED LOOPS                              |           |
|   4 |     NESTED LOOPS                             |           |
|   5 |      PARTITION RANGE ITERATOR                |           |
|   6 |       TABLE ACCESS BY LOCAL INDEX ROWID      |일별매출    |
|*  7 |        INDEX RANGE SACN                      |일별매출_X1 |
|*  8 |       INDEX RANGE SCAN                       |상품_PK     |
|*  9 |      TABLE ACCESS BY INDEX ROWID             |상품        |
------------------------------------------------------------------


[SQL2]

SELECT /*+ LEADING(A) USE_NL(B) */ b.상품분류코드, a.기준일자,
       SUM(a.판매금액) AS 판매금액, SUM(a.판매수량) AS 판매수량
FROM (SELECT  a.기준일자, a.상품코드, SUM(a.판매금액) AS 판매금액, SUM(a.판매수량) AS 판매수량
      FROM 일별매출 a
      WHERE a.기준일자 BETWEEN TO_DATE(:v_시작기준일자, 'YYYYMMDD') AND TO_DATE(:v_종료기준일자, 'YYYYMMDD')
      GROUP BY a.기준일자, a.상품코드) a, 상품 b
WHERE b.상품코드 = a.상품코드 AND b.판매시작일자 >= TO_DATE(:v_판매시작일자, 'YYYYMMDD')
GROUP BY b.상품분류코드, a.기준일자;
------------------------------------------------------------------
| Id  | Operation                                    | Name      |
------------------------------------------------------------------
|   0 | SELECT STATEMENT                             |           |
|   1 |  HASH GROUP BY                               |           |
|   2 |    NESTED LOOPS                              |           |
|   3 |     NESTED LOOPS                             |           |
|   4 |      VIEW                                    |           |
|   5 |       HASH GROUP BY                          |           |
|*  6 |        FILTER                                |           |
|   7 |         PARTITION RANGE ITERATOR             |           |
|*  8 |          TABLE ACCESS BY LOCAL INDEX ROWID   |일별매출    |
|*  9 |           INDEX RANGE SCAN                   |일별매출_X1 |
|* 10 |      INDEX UNIQUE SCAN                       |상품_PK     |
|* 11 |     TABLE ACCESS BY INDEX ROWID              |상품        |
------------------------------------------------------------------


[테이블]

CREATE TABLE 상품 (
    상품코드 VARCHAR2(10) NOT NULL,
    상품명  VARCHAR2(100) NOT NULL,
    상품분류코드 VARCHAR2(5) NOT NULL,
    판매시작일자 DATE NOT NULL,
    판매종료일자 DATE NULL);

CREATE UNIQUE INDEX 상품_PK ON 상품(상품코드);
CREATE INDEX 상품_X1 ON 상품(판매시작일자);

CREATE TABLE 일별매출 (
    기준일자 DATE NOT NULL,
    상품코드 VARCHAR2(10) NOT NULL,
    판매금액 NUMBER(10) NOT NULL,
    판매수량 NUMBER(10) NOT NULL);

CREATE UNIQUE INDEX 일별매출_PK ON 일별매출(상품코드, 기준일자);
CREATE INDEX 일별매출_X1 ON 일별매출(기준일자);

- 단, 상품 테이블은 판매시작일자별 상품 수가 비슷한 것으로 가정한다.
- 단, 일별매출 테이블은 기준일자로 파티셔닝되어 있다고 가정한다.

① SQL1은 조인 과정에서 과도한 I/O가 발생할 수 있다.
② SQL1은 판매시작일자 조건을 만족하는 상품 건수가 적다면, 조인 순서 변경만으로 성능이 개선될 가능성이 높다.
③ SQL2는 v_판매시작일자 변수에 오랜 과거 일자를 입력할 때보다 최근 일자를 입력할 때 비효율이 더 적다.
④ Query Transformer는 쿼리 변환을 수행하여 SQL1에 대해 SQL2와 유사한 실행 계획을 수립하기도 한다

 

더보기

정답 : ③

해설 : 대량 집합을 기준으로 NL 조인하면 많은 랜덤 I/O가 발생한다. SQL1에서 판매시작일자 조건을 만족하는 상품 건수가 적다면, 일별매출보다 상품 테이블을 먼저 드라이빙하는 게 유리할 수 있다. SQL2에서 v_판매시작일자 변수에 최근일자를 입력하면 판매시작일자 조건을 만족하는 상품 건수가 적어지므로 그만큼 비효율이 커진다. 기준일자 Between 조건에 해당하는 많은 일별매출 데이터를 읽고 Group By 처리까지 마쳤는데, 상품 테이블과 조인하는 과정에 많은 데이터가 필터링되기 때문이다.

 

문제 19

Q. 다음 중 아래와 같은 SQL에서 가능한 조인 방법을 모두 나열한 것은?
SELECT emp.ename, salgrade.grade
FROM emp t1, salgrade t2
WHERE t1.sal BETWEEN t2.losal AND t2.hisal


① Nested Loop Join
② Nested Loop Join, Sort Merge Join
③ Sort Merge Join, Hash Join
④ Nested Loop Join, Sort Merge Join, Hash Join

 

더보기

정답 : ②

해설 : Hash 조인은 '=' 조인만 가능하다.

 

300x250

 

문제 20

Q. 아래와 같이 고객 테이블과 2개의 변경이력 테이블이 있다. 두 변경이력 테이블에 있는 시작일자, 종료일자는 선분이력으로 관리된다. 즉, 시작일자에는 이력 레코드 생성일자가 입력되고, 종료일자에는 처음에 '99991231'로 입력되었다가 다음 번 새 이력 레코드가 생성되는 순간 바로 전 일자로 갱신된다. 이 세 테이블에서 이름이 '홍길동'인 고객의 1998년 5월 29일자 고객등급과 전화번호를 조회하려고 한다. 아래 (  ㄱ  )에 들어갈 조건절로 가장 적절한 것은? (단, 고객번호와 변경순번을 제외한 나머지 속성은 모두 문자형)


<인덱스 상황>

* 고객_PK : 고객번호
* 고객_X01 : 고객명
* 고객등급변경이력_PK : 고객번호 + 변경순번
* 고객등급변경이력_X01 : 고객번호 + 시작일자 + 종료일자
* 고객전화번호변경이력_PK : 고객번호 + 변경순번
* 고객전화번호변경이력_X01 : 고객번호 + 종료일자 + 시작일자
select c.고객번호, c.고객명, c1.고객등급, c2.전화번호
from 고객 c, 고객등급변경이력 c1, 전화번호변경이력 c2
where c.고객명 = '홍길동'
and c1.고객번호 = c.고객번호
and c2.고객번호 = c.고객번호
(                    ㄱ                    )


① 

and c1.변경순번 = (select max(변경순번)
                  from 고객등급변경이력
                  where 시작일자 <= '19980529')
and c2.변경순번 = (select max(변경순번)
                  from 전화번호변경이력
                  where 시작일자 <= '19980529')


② 

and c1.변경순번 = (select 변경순번
                  from 고객등급변경이력
                  where '19980529' between 시작일자 and 종료일자)
and c1.변경순번 = (select 변경순번
                  from 전화번호변경이력
                  where '19980529' between 시작일자 and 종료일자)


③ 

and '19980529' between c1.시작일자 and c1.종료일자
and '19980529' between c2.시작일자 and c2.종료일자


④ 

and '19980529' between c1.시작일자 and c1.종료일자
and c1.종료일자 >= c2.시작일자
and c1.시작일자 <= c2.종료일자

 

더보기

정답 : ③

해설 : 선분 이력 각각을 between으로 조회하면 된다.

 

※ 선분 이력 vs. 점 이력

- 예를 들어 고객별연체금액변경이력을 관리할 때, 이력의 시작 지점만을 관리하는 것을 '점 이력' 모델이라 하고, 시작 지점과 종료 시점을 함께 관리하는 것을 '선분 이력' 모델이라고 한다.

 

문제 21

Q. 다음 중 Local 파티션 인덱스의 특징과 거리가 먼 것은?

① 테이블 파티션과 1:1 대응 관계
② 테이블에 파티션 ADD/DROP/SPLIT/EXCHANGE 작업 시, 인덱스 파티션도 자동 관리됨.
③ Local 파티션 인덱스의 경우, 테이블 파티션 키(=인덱스 파티션 키)가 인덱스 키 선두 컬럼에 위치해야 함. 예를 들어 테이블과 인덱스 파티션 키가 '주문일자'이면, Local 파티션 인덱스 키는 '주문일자'로 시작해야 함.
④ 테이블 파티션 키가 SQL 조건절에 없을 때, 인덱스 사용 시 비효율 발생

 

더보기

정답 : ③

해설 : 인덱션 파티션 키가 인덱스 선두 칼럼이어야 한다는 제약은 Global 파티션 인덱스에 해당한다. Local 파티션이든 Global 파티션이든, 인덱스 파티션 키가 조건절에 없으면 인덱스 사용 시 비효율이 발생한다. 문제를 풀 때, Local 파티션 인덱스의 경우, 테이블 파티션 키가 인덱스 파티션 키가 된다는 점에 주의해야 한다.

 

※ Local 파티션 인덱스

- 테이블 파티션과 1:1로 대응이 되도록 파티셔닝한 인덱스

- 인덱스 파티션 키를 사용자가 따로 지정하지 않으며, 테이블과 1:1 관계를 유지하도록 DBMS가 자동으로 관리해 줌.

- SQL Server에서는 '정렬된(Aligned) 파티션 인덱스'라고 부름.

 

문제 22

Q. 우리가 개발하고자 하는 시스템은 업무적으로 7~9월에 매출이 집중돼 있다. 아래 월별매출집계 테이블(보관주기 3년)을 파티셔닝하기로 했고, 매출원열 기준으로 각 파티션에 데이터를 고르게 분산 저장하고자 할 때, 다음 중 사용할 파티션 전략으로 가장 적절한 것을 2개 고르시오.


① Range 파티션
② List 파티션
③ Hash 파티션
④ Manual 파티션

 

더보기

정답 : ①, ②

해설 : List 파티셔닝과 Range 파티셔닝 모두 가능하다. Range 파티셔닝의 경우, 예를 들어, 매년 1~3, 4~6, 7, 8, 9, 10~12월 6개로 파티셔닝하면 된다. Hash 파티셔닝은 정해진 파티션 개수로 파티션 키 값에 따라 DBMS가 기계적으로 분할 저장하기 때문에 월별 매출 특성을 고려한 파티셔닝을 하기 곤란하다.

 

※ Oracle이 지원하는 파티션 유형

① Range 파티서닝
- 파티션 키 값의 범위(Range)로 분할
- 파티셔닝의 가장 일반적인 형태이며, 주로 날짜 칼럼을 기준으로 함.

예) 판매 데이터를 월별로 분할


② Hash 파티셔닝
- 파티션 키 값에 해시 함수를 적용하고, 거기서 반환된 값으로 파티션 매밍
- 데이터가 모든 파티션에 고르게 분산되도록 DBMS가 관리 → 각 행(Row)의 저장 위치 예측 불가
- 파티션 키의 데이터 분포가 고른 칼럼이어야 효과적
예)고객번호, 주문일련번호 등
- 병렬처리 시 성능 효과 극대화
- DML 경합 분산에 효과적

 

③ List 파티서닝
- 불연속적인 값의 목록을 각 파티션에 지정
- 순서와 상관없이, 사용자가 미리 정한 그룹핑 기준에 따라 데이터를 분할 저장
예) 판매 데이터를 지역별로 분할


④ Composite 파티셔닝
- Range나 List 파티션 내에 또 다른 서브 파티션(Range, Hash, List) 구성
예) Range + List 또는 List + Hash 등
- Range나 List 파티션이 갖는 이점 + 각 서브 파티션 구성의 이점

 

문제 23

Q. 다음 중 테이블 tab1에 다음과 같은 데이터가 존재할 경우, 올바르게 설명한 것은? (각 칼럼의 타입은 number이다.)
col1    col2    col3
=====================
 10      20     NULL
 15     NULL    NULL
 60      70      20


① select sum(col2) from tab1 의 결과는 NULL이다.
② select sum(col1 + col2 + col3) from tab1 의 결과는 185 이다
③ select sum(col2 + col3) from tab1의 결과는 90 이다.
④ select sum(col2) + sum(col3) from tab1 의 결과는 90 이다.

 

더보기

정답 : ③

해설 : 칼럼끼리 연산할 때 null을 포함하면 결과는 null이다. 레코드끼리 연산할 때 null 을 포함하면 결과가 null이 아니며, 이유는 null을 연산에서 제외하기 때문이다.

 

문제 24

Q. 다음 중 아래 실행 계획과 보기의 SQL을 서로 연결할 때, 보기 SQL 중 실행 계획이 없는 것은?
------------------------------    ------------------------------
|  Id  | Operation           |    |  Id  | Operation           |
------------------------------    ------------------------------
|   0  | SELECT STATEMENT    |    |   0  | SELECT STATEMENT    | 
|   1  |  SORT AGGREGATE     |    |   1  |  SORT ORDER BY      |
|   2  |   TABLE ACCESS FULL |    |   2  |   TABLE ACCESS FULL |
------------------------------    ------------------------------

------------------------------    ------------------------------
|  Id  | Operation           |    |  Id  | Operation           |
------------------------------    ------------------------------
|   0  | SELECT STATEMENT    |    |   0  | SELECT STATEMENT    | 
|   1  |  SORT UNIQUE        |    |   1  |  SORT GROUP BY      |
|   2  |   TABLE ACCESS FULL |    |   2  |   TABLE ACCESS FULL |
------------------------------    ------------------------------


select * from emp order by deptno;
② select distinct deptno from emp;
③ select deptno, count(*) from emp group by deptno;
④ select empno, ename, sal, avg(sal) over (partition by deptno) from emp;

 

더보기

정답 :

해설 : 윈도우 함수가 사용된 SQL 실행 계획에는 WINDOW SORT 오퍼레이션이 나타난다.

 

※ 소트와 관련된 오퍼레이션 유형

① Sort Aggregate

- 전체 로우를 대상으로 집계를 수행할 때 나타남.

- Oracle 실행 계획에 sort라는 표현이 사용됐지만, 실제 소트가 발생하진 않음.


② Sort Order By

- 정렬된 결과 집합을 얻고자 할 때 나타남.

 

③ Sort Group By

- Sorting 알고리즘을 사용해 그룹별 집계를 수행할 때 나타남.


④ Sort Unique

- 선택된 결과 집합에서 중복 레코드를 제거하고자 할 때 나타남.

- Union 연산자나 Distinct 연산자를 사용할 때가 대표적


⑤ Sort Join 
- Sort Merge Join을 수행할 때 나타남.

 

⑥ Window Sort 
- 윈도우 함수를 수행할 때 나타남.

 

문제 25

Q. 다음 중 정렬(Sort) 오퍼레이션을 포함하지 않는 SQL은?

① select * from emp order by ename
② select * from emp where deptno = 20
    union all
    select * from emp where deptno = 30
③ select empno, ename, rank() over (partition by deptno order by sal desc) from emp
④ select * from dept d, emp e where d.deptno = e.deptno
    option(force order, merge join)

 

더보기

정답 : ②

해설 : Union All은 정렬 오퍼레이션을 발생시키지 않는다. ④번 option 구문은 오라클 ordered use_merge에 해당하는 MS-SQL Server 힌트이다. Sort Merge 조인이므로 정렬 오퍼레이션이 발생한다.

 

문제 26

Q. 다음 중 아래 ERD와 Dictionary 조회 결과를 고려할 때, 보기 중 union 대신 union all을 사용해도 가능한 것으로 가장 적절한 것은?
SQL> select column_name, num_distinct
  2  from user_tab_columns
  3  where table_name = 'EMP';
  
COLUMN_NAME    NUM_DISTINCT
-----------    ------------
EMPNO               14
ENAME               14
DEPTNO               3
JOB                  5
MGR                  6
SAL                 12


select deptno, job, mgr from emp where empno = 7499
    union
    select deptno, job, mgr from emp where empno = 7654;
② select job, mgr from emp where deptno = 10
    union
    select job, mgr from emp where deptno = 20;
③ select deptno, job, mgr from emp where deptno = 10
    union
    select deptno, job, mgr from emp where deptno = 20;
④ select empno, job, mgr from emp where deptno = 10
    union
    select empno, job, mgr from emp where deptno = 20;

 

더보기

정답 :

해설 : union을 union all로 대체하려면 아래 두 조건을 만족해야 한다.

1. 위아래 두 집합이 서로 배타적이어야 한다.

2. 위아래 각 집합에 중복 값이 없어야 한다.

3. 위아래 두 집합이 서로 배타적이지만, 각 집합에 중복 값이 있을 수 있다.

4. PK 칼럼을 포함하기 때문에 모든 레코드가 완전 배타적이다.

 

문제 27

Q. 모니터링 결과, 아래 쿼리의 수행 빈도가 가장 높아 시스템에 미치는 영향이 큰 것으로 조사됐다. 다음 중 고려할 튜닝 방안으로 가장 부적절한 것은?
create index 게시판_idx on 게시판(작성일시, 게시판구분);

select *
from (
    select *
    from 게시판
    where 게시판구분 = :gubun
    and 작성일시 >= trunc(sysdate) - 1
    order by 작성일시, 작성자명
)
where rownum <= 100;


 rownum 조건을 인라인 뷰 안에 사용한다.
② 게시판 구분의 데이터 분포(선택도)를 고려해 인덱스를 [작성일시 + 작성자명] 순으로 변경할지 검토한다. 최종 결정 시 다른 SQL에 미치는 영향도도 검토해야 한다.
③ 게시판 구분의 데이터 분포(선택도)를 고려해 인덱스를 [게시판구분 + 작성일시] 순으로 변경할지 검토한다. 최종 결정 시 다른 SQL에 미치는 영향도도 검토해야 한다.
④ 작성자명이 데이터 정렬 순서로서 의미 있는 것인지 현업 사용자에게 문의한 후, 불필요하다면 제거한다.

 

더보기

정답 :

해설 : ①번 방식으로 처리할 경우, 결과가 틀릴 수 있다.

 

문제 28

Q. 다음 중 아래의 고객과 고객변경이력 테이블에서 전체 고객을 대상으로 2010년 12월 4일자 이력을 조회하려고 할 때, 가장 효과적인 SQL인 것은?



select b.고객ID, b.변경순번, b.전화번호, b.주소, b.자녀수, b.직업, b.고객등급
from 고객 a, 고객변경이력 b
where b.고객번호 = a.고객번호
and b.변경순번 = (select max(변경순번)
                 from 고객변경이력
                 where 고객번호 = a.고객번호
                 and 변경일자 <= '20101204')


② 

select b.고객ID, b.변경순번, b.전화번호, b.주소, b.자녀수, b.직업, b.고객등급
from (select 고객ID, max(변경순번) 변경순번
      from 고객변경이력
      where 변경일자 <= '20101204'
      group by 고객ID) a, 고객변경이력 b
where b.고객번호 = a.고객번호
and b.변경순번 = a.변경순번


③ 

select 고객ID, 변경순번, 전화번호, 주소, 자녀수, 직업, 고객등급
from (select 고객ID, 변경순번, 
             rank() over (partition by 고객ID order by 변경순번 desc) rnum,
             전화번호, 주소, 자녀수, 직업, 고객등급     
      from 고객변경이력
      where 변경일자 <= '20101204')
where rnum = 1


④ 

select 고객ID, 변경순번, 전화번호, 주소, 자녀수, 직업, 고객등급
from (select 고객ID, 변경순번, 
             max(변경순번) (partition by 고객ID) 변경순번2, 
             전화번호, 주소, 자녀수, 직업, 고객등급     
      from 고객변경이력
      where 변경일자 <= '20101204')
where 변경순번 = 변경순번2

 

더보기

정답 : ③

해설 : 번은 Random 액세스 방식이므로 전체 고객을 대상으로 조회할 때 비효율적이다. ②번은 고객변경이력 테이블을 2번 액세스하는 비효율이 있다. ④번은 Top-N 쿼리 알고리즘이 작동하지 않아 ③번에 비해 소트 부하가 크다.

 

728x90
그리드형(광고전용)
⚠️AdBlock이 감지되었습니다. 원할한 페이지 표시를 위해 AdBlock을 꺼주세요.⚠️
starrykss
starrykss
별의 공부 블로그 🧑🏻‍💻


📖 Contents 📖