create table 월별지점매출
as
select deptno "지점"
, row_number() over (partition by deptno order by empno) "판매월"
, round(dbms_random.value(500, 1000)) "매출"
from emp
order by deptno ;
select * from 월별지점매출;
----------------------------------------------------------
지점 판매월 매출
---------- ---------- ----------
10 1 692
10 2 925
10 3 751
20 1 980
20 2 977
20 3 822
20 4 653
20 5 877
30 1 589
30 2 628
30 3 533
30 4 515
30 5 849
30 6 603
14 개의 행이 선택되었습니다.
경 과: 00:00:00.01
----------------------------------------------------------
select 지점, 판매월, 매출
, sum(매출) over (partition by 지점 order by 판매월
range between unbounded preceding and current row) 누적매출
from 월별지점매출;
----------------------------------------------------------
지점 판매월 매출 누적매출
---------- ---------- ---------- ----------
10 1 692 692
10 2 925 1617
10 3 751 2368
20 1 980 980
20 2 977 1957
20 3 822 2779
20 4 653 3432
20 5 877 4309
30 1 589 589
30 2 628 1217
30 3 533 1750
30 4 515 2265
30 5 849 3114
30 6 603 3717
14 개의 행이 선택되었습니다.
경 과: 00:00:00.01
----------------------------------------------------------
select t1.지점, t1.판매월, min(t1.매출) 매출, sum(t2.매출) 누적매출
from 월별지점매출 t1, 월별지점매출 t2
where t2.지점 = t1.지점
and t2.판매월 <= t1.판매월
group by t1.지점, t1.판매월
order by t1.지점, t1.판매월;
----------------------------------------------------------
지점 판매월 매출 누적매출
---------- ---------- ---------- ----------
10 1 692 692
10 2 925 1617
10 3 751 2368
20 1 980 980
20 2 977 1957
20 3 822 2779
20 4 653 3432
20 5 877 4309
30 1 589 589
30 2 628 1217
30 3 533 1750
30 4 515 2265
30 5 849 3114
30 6 603 3717
14 개의 행이 선택되었습니다.
경 과: 00:00:00.02
----------------------------------------------------------
create table 월도 (기준월, 시작일자, 종료일자)
as
select '2009/06', '2009/06/01', '2009/06/30' from dual union all
select '2009/07', '2009/07/01', '2009/07/31' from dual union all
select '2009/08', '2009/08/01', '2009/08/31' from dual union all
select '2009/09', '2009/09/01', '2009/09/30' from dual union all
select '2009/10', '2009/10/01', '2009/10/31' from dual;
----------------------------------------------------------
select * from 월도; --테이블 생성 확인.
기준월 시작일자 종료일자
-------------- -------------------- --------------------
2009/06 2009/06/01 2009/06/30
2009/07 2009/07/01 2009/07/31
2009/08 2009/08/01 2009/08/31
2009/09 2009/09/01 2009/09/30
2009/10 2009/10/01 2009/10/31
경 과: 00:00:00.05
----------------------------------------------------------
create table 선분이력(상품번호, 시작일자, 종료일자, 데이터)
as
select 'A', '2009/07/13', '2009/08/08', 'A1' from dual union all
select 'A', '2009/08/09', '2009/08/20', 'A2' from dual union all
select 'A', '2009/08/21', '2009/10/07', 'A3' from dual;
----------------------------------------------------------
select * from 선분이력; -- 테이블 생성 확인.
상품번호 시작일자 종료일자 데이터
-------- -------------------- -------------------- --------
A 2009/07/13 2009/08/08 A1
A 2009/08/09 2009/08/20 A2
A 2009/08/21 2009/10/07 A3
경 과: 00:00:00.03
----------------------------------------------------------
--> 위에서 생성된 '월도', '선분이력' 두 테이블을 부등호 조인을 사용해 6개의 선분을 생성
select a.기준월, b.시작일자, b.종료일자, b.상품번호, b.데이터
from 월도 a, 선분이력 b
where b.시작일자 <= a.종료일자
and b.종료일자 >= a.시작일자
order by a.기준월, b.시작일자 ;
----------------------------------------------------------
기준월 시작일자 종료일자 상품번호 데이터 |시작선분 복제 스타일
----------- ----------------- ----------------- ------- ------- |
2009/07 2009/07/13 2009/08/08 A A1 |---5 -> 8 -- 스타일 a
2009/08 2009/07/13 2009/08/08 A A1 |---5 -> 9 -- 스타일 b
2009/08 2009/08/09 2009/08/20 A A2 |---6 -> 10 -- 스타일 c
2009/08 2009/08/21 2009/10/07 A A3 |---7 -> 11 -- 스타일 a
2009/09 2009/08/21 2009/10/07 A A3 |---7 -> 12 -- 스타일 d
2009/10 2009/08/21 2009/10/07 A A3 |---7 -> 13 -- 스타일 b
6 개의 행이 선택되었습니다.
경 과: 00:00.00
-------------------------------------------------
select 상품번호
, case when lst = 시작일자1 and gst = 종료일자2 then 시작일자2 -- 스타일 a
when lst = 시작일자2 and gst = 종료일자1 then 시작일자1 -- 스타일 b
when lst = 시작일자1 and gst = 종료일자1 then 시작일자2 -- 스타일 c
when lst = 시작일자2 and gst = 종료일자2 then 시작일자1 -- 스타일 d
end 시작일자
, case when lst = 시작일자1 and gst = 종료일자2 then 종료일자1 -- 스타일 a
when lst = 시작일자2 and gst = 종료일자1 then 종료일자2 -- 스타일 b
when lst = 시작일자1 and gst = 종료일자1 then 종료일자2 -- 스타일 c
when lst = 시작일자2 and gst = 종료일자2 then 종료일자1 -- 스타일 d
end 종료일자
, 데이터
from (
select b.상품번호, b.데이터, a.기준월
, a.시작일자 시작일자1, b.시작일자 시작일자2
, a.종료일자 종료일자1, b.종료일자 종료일자2
, least(a.시작일자, a.종료일자, b.시작일자, b.종료일자) lst
, greatest(a.시작일자, a.종료일자, b.시작일자, b.종료일자) gst
from 월도 a, 선분이력 b
where b.시작일자 <= a.종료일자
and b.종료일자 >= a.시작일자
) ;
----------------------------------------------------------
상품번호 시작일자 종료일자 데이터
---------- -------------------- -------------------- ----------
A 2009/07/13 2009/07/31 A1
A 2009/08/01 2009/08/08 A1
A 2009/08/09 2009/08/20 A2
A 2009/08/21 2009/08/31 A3
A 2009/09/01 2009/09/30 A3
A 2009/10/01 2009/10/07 A3
6 개의 행이 선택되었습니다.
경 과: 00:00:00.01
----------------------------------------------------------
쿼리를 작성하다 보면 데이터 복제 기법을 활용해야 할 때가 많다. 데이터복제를 위해 일부러 카티션 곱(Cartesian Product)을 발생시켜 복제하기도 한다.
전통적으로 많이 쓰던 방식은 복제용 테이블(copy_t)을 미리 만들어두고 이를 활용하는 것이다. (*데카르트)
create table copy_t (no number, no2 varchar2(2));
테이블이 생성되었습니다.
경 과: 00:00:00.12
insert into copy_t
15:27:14 2 select rownum, lpad(rownum,2,'0') from all_tables where rownum<=31;
31개의 행이 생성되었습니다.
경 과: 00:00:01.36
alter table copy_t add constraint copy_t_pk primary key(no);
테이블이 변경되었습니다.
경 과: 00:00:00.32
create unique index copy_t_no2_idx on copy_t(no2);
인덱스가 생성되었습니다.
경 과: 00:00:00.00
select * from emp a, copy_t b where b.no<=3;
** 결과 첨부 생략
select rownum from dual connect by level <= 2;
ROWNUM
----------
1
2
경 과: 00:00:00.00
select * from emp a, (select rownum no from dual connect by level <= 2) b
15:48:32 2 order by 1,2;
** 결과 첨부 생략
다음은 데이터 복제 기법을 사용하면 단일 sql로도 부서별 소계를 구할 수 있음을 보여준다.
break on 부서번호
column 부서번호 format 9999
column 사원번호 format a10
select deptno 부서번호
, decode(no, 1, to_char(empno), 2, '부서계') 사원번호
, sum(sal) 급여합, round(avg(sal)) 급여평균
from emp a, (select rownum no from dual connect by level <= 2)
group by deptno, no, decode(no, 1, to_char(empno), 2, '부서계')
order by 1, 2;
----------------------------------------------------------
부서번호 사원번호 급여합 급여평균
-------- ---------- ---------- ----------
10 7782 2450 2450
7839 5000 5000
7934 1300 1300
부서계 8750 2917
20 7369 800 800
7566 2975 2975
7788 3000 3000
7876 1100 1100
7902 3000 3000
부서계 10875 2175
30 7499 1600 1600
7521 1250 1250
7654 1250 1250
7698 2850 2850
7844 1500 1500
7900 950 950
부서계 9400 1567
17 개의 행이 선택되었습니다.
경 과: 00:00:00.00
----------------------------------------------------------
column 부서번호 format a10
select decode(no, 3, null, to_char(deptno)) 부서번호
, decode(no, 1, to_char(empno), 2, '부서계', 3, '총계') 사원번호
, sum(sal) 급여합, round(avg(sal)) 급여평균
from emp a, (select rownum no from dual connect by level <= 3)
group by decode(no, 3, null, to_char(deptno))
, no, decode(no, 1, to_char(empno), 2, '부서계', 3, '총계')
order by 1, 2;
----------------------------------------------------------
부서번호 사원번호 급여합 급여평균
---------- ---------- ---------- ----------
10 7782 2450 2450
7839 5000 5000
7934 1300 1300
부서계 8750 2917
20 7369 800 800
7566 2975 2975
7788 3000 3000
7876 1100 1100
7902 3000 3000
부서계 10875 2175
30 7499 1600 1600
7521 1250 1250
7654 1250 1250
7698 2850 2850
7844 1500 1500
7900 950 950
부서계 9400 1567
총계 29025 2073
18 개의 행이 선택되었습니다.
경 과: 00:00:00.02
----------------------------------------------------------
break on 부서번호
column 부서번호 format 9999
column 사원번호 format a10
select deptno 부서번호
, case when grouping(empno) = 1 and grouping(deptno) = 1 then '총계'
when grouping(empno) = 1 then '부서계'
else to_char(empno) end 사원번호
, sum(sal) 급여합, round(avg(sal)) 급여평균
from emp
group by rollup(deptno, empno)
order by 1, 2;
----------------------------------------------------------
부서번호 사원번호 급여합 급여평균
---------- ---------- ---------- ----------
10 7782 2450 2450
7839 5000 5000
7934 1300 1300
부서계 8750 2917
20 7369 800 800
7566 2975 2975
7788 3000 3000
7876 1100 1100
7902 3000 3000
부서계 10875 2175
30 7499 1600 1600
7521 1250 1250
7654 1250 1250
7698 2850 2850
7844 1500 1500
7900 950 950
부서계 9400 1567
총계 29025 2073
18 개의 행이 선택되었습니다.
경 과: 00:00:00.01
----------------------------------------------------------
**Quiz
SELECT LEVEL
FROM (SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= 2)
CONNECT BY LEVEL <= 3
--출력이 몇.... 개?
SELECT /*+ ordered use_nl(b) use_nl(c) use_nl(c) use_nl(d) */
a.주문번호, a.결제일자, a.결제금액
, NVL(b.온라인권번호, c.실권번호) 상품권번호
, NVL(b.발행일시, d.발행일시) 발행일시
FROM 상품권결제 a, 온라인권 b, 실권 c, 실권발행 d
WHERE a.결제일자 BETWEEN :dt1 AND :dt2
AND b.온라인권번호(+) = a.온라인권번호
AND c.실권번호(+) = a.실권번호
AND d.발행번호(+) = c.발행번호;
SELECT x.주문번호, x.결제일자, x.결제금액, y.온라인권번호 상품권번호, y.발행일시, ...
FROM 상품권결제 x, 온라인권 y
WHERE x.상품권구분 = '1'
AND x.결제일자 BETWEEN :dt1 AND :dt2
AND y.온라인권번호(+) = x.상품권번호
UNION ALL
SELECT x.주문번호, x.결제일자, x.결제금액, y.실권번호 상품권번호, z.발행일시, ...
FROM 상품권결제 x, 실권 y, 실권발행 z
WHERE x.상품권구분 = '2'
AND x.결제일자 BETWEEN :dt1 AND :dt2
AND y.실권번호(+) = x.상품권번호
AND z.발행번호(+) = y.발행번호;
SELECT /*+ ordered use_nl(b) use_nl(c) use_nl(c) use_nl(d) */
a.주문번호, a.결제일자, a.결제금액
, NVL(b.온라인권번호, c.실권번호) 상품권번호
, NVL(b.발행일시, d.발행일시) 발행일시
FROM 상품권결제 a, 온라인권 b, 실권 c, 실권발행 d
WHERE a.결제일자 BETWEEN :dt1 AND :dt2
AND b.온라인권번호(+) = a.온라인권번호
AND c.실권번호(+) = a.실권번호
AND d.발행번호(+) = c.발행번호;
3. 중복 액세스에 의한 비효울 제거
SELECT /*+ ordered use_nl(b) use_nl(c) use_nl(c) use_nl(d) */
a.주문번호, a.결제일자, a.결제금액
, NVL(b.온라인권번호, c.실권번호) 상품권번호
, NVL(b.발행일시, d.발행일시) 발행일시
FROM 상품권결제 a, 온라인권 b, 실권 c, 실권발행 d
WHERE a.결제일자 BETWEEN :dt1 AND :dt2
AND b.온라인권번호(+) = DECODE(a.상품권구분, '1', a.상품권번호)
AND c.실권번호(+) = DECODE(a.상품권구분, '2', a.상품권번호)
AND d.발행번호(+) = c.발행번호;
*화면 페이지 처리시 흔히 사용되는 방식이다.
*조건과 정렬 컬럼을 결합인덱스로 구성하면 인덱스 블록만 읽어 테이블 액세스 범위와 조인 범위를 줄일 수 있다. ( 교재 참고 page. 312~ )
*조건과 정렬 컬럼이 많아져 인덱스 조정이 어려울 때는, 해당 테이블까지 액세스하여 전체를 읽어 정렬한 후 불필요한 조인 횟수를 줄일 수 있다. ( 교재 참고 page. 316~ )
*조건과 정렬 컬럼이 여러 테이블 각각 있다면? => 반정규화
**반정규화에 따른 고려사항
1. 반정규화 컬럼의 데이터 갱신을 위한 추가 프로그램 필요하다
2. 데이터 정합성 훼손의 위험성이 높아지므로, 데이터 부정합이 발생하지 않도록 조치한다.
3. 반정규화 없이 성능 문제를 해결할 수 있는 방법을 고민한다.
--
--
=> 수신확인자수와 수신대상자수를 세고 새글여부를 확인하는 스칼라 서브쿼리 성능문제로 발송메세지건수, 수신인수와 같은 추출 속성을 추가로 생성--
--SELECT ..
--FROM (
-- SELECT ..
-- FROM (
-- SELECT a.발신인ID, a,발송일시, a.제목, b.사용자이름 AS 보낸이
-- , ( SELECT COUNT(수신일시) FROM 메시지수신인 ..) 수신확인자수
-- , ( SELECT COUNT(*) FROM 메시지수신인 ..) 수신대상자수
-- , ( CASE WHEN EXISTS ( SELECT 'x' FROM 메시지수신인
-- WHERE 발신자ID = a.발신자ID
-- AND 발송일시 = a.발송일시
-- AND 수신자ID = :로그인사용자ID
-- AND 수신일시 IS NULL ) THEN 'Y' END ) 새글여부
-- FROM 메시지 a, 사용자 b
-- ORDER BY a.발송일시 DESC
-- ) a
-- WHERE rownum <= 10
--)
--WHERE no between 1 and 10;
--=> 최종 출력되는 10건에 대해서만 수신정보와 새글 여부를 확인하는 방식으로 쿼리 변경하여 성능 문제 해결--
--SELECT a.발신인ID, a,발송일시, a.제목, b.사용자이름 AS 보낸이
-- , ( SELECT COUNT(수신일시) || '/' || COUNT(*) FROM 메시지수신인 ..) 수신확인
-- , ( CASE WHEN EXISTS ( .. ) THEN 'Y' END ) 새글여부
--FROM (
-- SELECT ROWNUM NO, ...
-- FROM ( SELECT 발신인ID, a,발송일시, a.제목 FROM 메시지 ORDER BY a.발송일시 DESC )
-- WHERE ROWNUM <= 30
-- ) a, 사용자 b
--WHERE NO BETWEEN 21 AND 30;
h3 (6) 징검다리 테이블 조인을 이용한 튜닝
h3.(7) 점이력 조회
select a.고객명, a.거주지역, a.주소, a.연락처, b.연체금액
from 고객 a, 고객별연체이력 b
where a.가입회사 = 'C70'
and b.고객번호 = a.고객번호
and b.변경일자 = (select /*+ no_unnest */ max(변경일자)
from 고객별연체이력
where 고객번호 = a.고객번호
and 변경일자 <= a.서비스만료일);
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 60 | 332 (0)| 00:00:04 |
| 1 | TABLE ACCESS BY INDEX ROWID | 고객별연체이력| 2 | 34 | 3 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 16 | 960 | 32 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID | 고객 | 10 | 430 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | 고객_IDX01 | 10 | | 1 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | 고객별연체이력_IDX01 2 | | 2 (0)| 00:00:01 |
| 6 | SORT AGGREGATE | | 1 | 13 | | |
| 7 | FIRST ROW | | 5039 | 65507 | 3 (0)| 00:00:01 |
|* 8 | INDEX RANGE SCAN (MIN/MAX)| 고객별연체이력_IDX01 5039 | 65507 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
select a.고객명, a.거주지역, a.주소, a.연락처, b.연체금액
from 고객 a, 고객별연체이력 b
where a.가입회사 = 'C70'
and b.고객번호 = a.고객번호
and b.변경일자 = (select /*+ no_unnest */ max(변경일자)
from 고객별연체이력
where 고객번호 = a.고객번호
and 변경일자 <= a.서비스만료일);
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 60 | 332 (0)| 00:00:04 |
| 1 | TABLE ACCESS BY INDEX ROWID | 고객별연체이력| 2 | 34 | 3 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 16 | 960 | 32 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID | 고객 | 10 | 430 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | 고객_IDX01 | 10 | | 1 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | 고객별연체이력| 2 | | 2 (0)| 00:00:01 |
| 6 | SORT AGGREGATE | | 1 | 13 | | |
| 7 | FIRST ROW | | 5039 | 65507 | 3 (0)| 00:00:01 |
|* 8 | INDEX RANGE SCAN (MIN/MAX)| 고객별연체이력| 5039 | 65507 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
select .....
,(selct substr(max(변경일자 || 연체금액), 9) from ...)
from 고객 a where .....
select a.고객명, a.거주지역, a.주소, a.연락처
,(select /*+ index_desc(b 고객별연체이력_idx01) */ 연체금액
from 고객별연체이력 b
where b.고객번호 = a.고객번호
and b.변경일자 <= a.서비스만료일
and rownum <= 1) 연체금액
from 고객 a
where 가입회사 = 'C70';
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 430 | 2 (0)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID | 고객별연체이력| 2 | 34 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN DESCENDING| 고객별연체이력_IDX01| 907 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | 고객 | 10 | 430 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | 고객_IDX01 | 10 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
select 고객명, 거주지역, 주소, 연락처
, to_number(substr(연체, 3)) 연체금액
, to_number(substr(연체, 1, 2)) 연체개월수
from (select a.고객명, a.거주지역, a.주소, a.연락처
,(select /*+ index_desc(b 고객별연체이력_idx01) */
lpad(연체개월수, 2) || 연체금액
from 고객별연체이력
where 고객번호 = a.고객번호
and 변경일자 <= a.서비스만료일
and rownum <= 1) 연체
from 고객 a
where 가입회사 = 'C70'
);
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 700 | 2 (0)| 00:00:01 |
| 1 | VIEW | | 10 | 700 | 2 (0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| 고객 | 10 | 430 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | 고객_IDX0| 10 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
select /*+ ordered use_nl(b) rowid(b) */ a.*, b.연체금액, b.연체개월수
from (select a.고객명, a.거주지역, a.주소, a.연락처
,(select /*+ index_desc(b 고객별연체이력_idx01) */ rowid rid
from 고객별연체이력 b
where b.고객번호 = a.고객번호
and b.변경일자 <= a.서비스만료일
and rownum <= 1) rid
from 고객 a
where 가입회사 = 'C70') a, 고객별연체이력 b
where b.rowid = a.rid;
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100K| 7381K| 12 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 100K| 7381K| 12 (0)| 00:00:01 |
| 2 | VIEW | | 10 | 560 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| 고객 | 10 | 430 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | 고객_IDX0| 10 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY USER ROWID | 고객별연체별이력 | 10079 | 187K| 1 (0)| 00:00:01 | --
------------------------------------------------------------------------------------------
select /*+ ordered use_nl(b) rowid(b) */
a.고객명, a.거주지역, a.주소, a.연락처, b.연체금액, b.연체개월수
from 고객 a, 고객별연체이력 b
where a.가입회사 = 'C70'
and b.rowid = (select /*+ index(c 고객별연체이력_idx01) */ rowid
from 고객별연체이력 c
where c.고객번호 = a.고객번호
and c.변경일자 <= a.서비스만료일
and rownum <= 1);
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 957 | 59334 | 312 (0)| 00:00:04 |
| 1 | NESTED LOOPS | | 9574K| 566M| 12 (0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| 고객 | 10 | 430 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | 고객_IDX01 | 10 | | 1 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY USER ROWID | 고객별연체이력| 1007K| 18M| 1 (0)| 00:00:01 |
|* 5 | COUNT STOPKEY | | | | | |
|* 6 | INDEX RANGE SCAN | 고객별연체이력| 2 | 38 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
select /*+ full(a) full(b) full(c) use_hash(a b c) no_merge(b) */
a.고객명, a.거주지역, a.주소, a.연락처, c.연체금액, c.연체개월수
from 고객 a
,(select 고객번호, max(변경일자) 변경일자
from 고객별연체이력
where 변경일자 <= to_char(sysdate, 'yyyymmdd')
group by 고객번호) b, 고객별연체이력 c
where b.고객번호 = a.고객번호
and c.고객번호 = b.고객번호
and c.변경일자 = b.변경일자;
select a.고객명, a.거주지역, a.주소, a.연락처
, to_number(substr(연체, 11)) 연체금액
, to_number(substr(연체, 9, 2)) 연체개월수
from 고객 a
,(select 고객번호, max(변경일자 || lpad(연체개월수, 2) || 연체금액 ) 연체
from 고객별연체이력
where 변경일자 <= to_char(sysdate, 'yyyymmdd')
group by 고객번호) b
where b.고객번호 = a.고객번호;
select a.고객명, a.거주지역, a.주소, a.연락처, b.연체금액, b.연체개월수
from 고객 a
,(select 고객번호, 연체금액, 연체개월수, 변경일자
, row_number() over (partition by 고객번호 order by 변경일자 desc) no
from 고객별연체이력
where 변경일자 <= to_char(sysdate, 'yyyymmdd')) b
where b.고객번호 = a.고객번호
and b.no = 1;
select a.고객명, a.거주지역, a.주소, a.연락처, b.연체금액, b.연체개월수
from 고객 a
,(select 고객번호, 연체금액, 연체개월수, 변경일자
, max(변경일자) over (partition by 고객번호) max_dt
from 고객별연체이력
where 변경일자 <= to_char(sysdate, 'yyyymmdd')) b
where b.고객번호 = a.고객번호
and b.변경일자 = b.max_dt;
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9881 | 781K| | 869 (4)| 00:00:11 |
|* 1 | HASH JOIN | | 9881 | 781K| | 869 (4)| 00:00:11 |
| 2 | TABLE ACCESS FULL | 고객 | 10 | 300 | | 3 (0)| 00:00:01 |
|* 3 | VIEW | | 9881 | 492K| | 865 (4)| 00:00:11 |
| 4 | WINDOW SORT | | 9881 | 183K| 632K| 865 (4)| 00:00:11 |
|* 5 | TABLE ACCESS FULL| 고객별연| 9881 | 183K| | 804 (4)| 00:00:10 |
----------------------------------------------------------------------------------------
select c.고객번호, c.고객명, c1.고객등급, c2.전화번호
from 고객 c, 고객등급변경이력 c1, 전화번호변경이력 c2
where c.고객번호 = 123
and c1.고객번호 = c.고객번호
and c2.고객번호 = c.고객번호
and :dt between c1.시작일자 and c1.종료일자
and :dt betwwen c2.시작일자 and c2.종료일자
*123번 고객의 등급과 전화번호 변경이력 레코드를 수평선사에 펼쳐 시계열적으로 표현했을 때 위 쿼리 결과로서 고객등급 'B', 고객전화번호는 '987-6543'으로 조회될 것이다.
select c.고객번호, c.고객명, c1.고객등급, c2.전화번호
from 고객 c, 고객등급변경이력 c1, 전화번호변경이력 c2
where c.고객번호 = 123
and c1.고객번호 = c.고객번호
and c2.고객번호 = c.고객번호
and c1.종료일자 = '99991231'
and c2.종료일자 = '99991232'
select c.고객번호, c.고객명, c1.고객등급, c2.전화번호
from 고객 c, 고객등급변경이력 c1, 전화번호변경이력 c2
where c.고객번호 = 123
and c1.고객번호 = c.고객번호
and c2.고객번호 = c.고객번호
and to_char(sysdate, 'yyyymmdd') between c1.시작일자 and c1.종료일자
and to_char(sysdate, 'yyyymmdd') between c2.시작일자 and c2.종료일자
Between조인
select a.거래일자, a.종목코드, b.종목한글명, b.종목영문명, b.상장주식수
, a.시가, a.종가, a.체결건수, a.체결수량, a.거래대금
from 일별종목거래및시세 a, 종목이력 b
where a.거래일자 between to_char(add_months(sysdate, -20*12), 'yyyymmdd')
and to_char(sysdate-1, 'yyyymmdd')
and a.종가 = a.초고가
and b.종목코드 = a.종목코드
and a.거래일자 between b.시작일자 and b.종료일자
조인에 실패했을때, 정해진 특정 레코드에서 가져온 값으로 보여주고 싶을때는 어떻게 쿼리해야할까?
cdr_rating(CDR 과금)테이블에 아래와 같이 국가와 지역별 요금 정보가 입력돼 있음
SQL> select 국가코드, '''' || 지역 || '''', 요금 from cdr_rating ;
국가 ''''||지역||'''' 요금
---- ------------------------ ----------
82 'A' 100
82 'B' 200
82 'C' 500
82 'D' 300
82 'E' 100
84 'A' 300
84 'B' 500
84 'C' 400
84 ' ' 800
86 'A' 500
86 'B' 200
86 ' ' 700
국가코드 82(한국)는 모든 지역에 대한 요금정보를 갖고 있음
하지만 국가코드 84(베트남)의 경우 A,B,C 가 아닌지역에 대해서는 일괄적으로 800원을 부과하려고 위와 같이 공백문자(' ')로 입력해 둔 것임
국가코드 86(중국)도 A,B가 아닌 지역에 대해서는 일괄적으로 700원이 부과됨
SQL> select * from cdr;
통화시간 국가 지역
------------------------------ ---- --------------------
20050315 010101 82 A
20050315 020101 82 B
20050315 030101 82 C
20050315 040101 84 A
20050315 050101 84 B
20050315 060101 84 C
20050315 070101 84 D --> cdr_rating에 매칭되는 정보 없음
20050315 080101 84 E |
20050315 090101 86 A
20050315 100101 86 B
20050315 110101 86 C --> cdr_rating에 매칭되는 정보 없음
20050315 120101 86 D |
20050315 130101 86 E |
20050315 140101 86 F |
이제 cdr 과 cdr_rating 두 테이블을 조인해 2005년 3월 15일 시점 통화내역과 요금정보를 출력하려 하는데,
일반적인 조인문을 사용하면 아래와 같이 조인에 실패한 통화내역은 출력이 되지 않음
SQL> select /*+ ordered use_nl(r) */
2 c.통화시간, c.국가코드, c.지역, r.요금
3 from cdr c, cdr_rating r
4 where c.통화시간 like '20050315%'
5 and c.국가코드 = r.국가코드
6 and c.지역 = r.지역 ;
통화시간 국가 지역 요금
------------------------------ ---- -------------------- ----------
20050315 010101 82 A 100
20050315 020101 82 B 200
20050315 030101 82 C 500
20050315 040101 84 A 300
20050315 050101 84 B 500
20050315 060101 84 C 400
20050315 090101 86 A 500
20050315 100101 86 B 200
cdr을 기준으로 Outer조인하면 통화내역은 모두 출력되겠지만 기타지역 통화내역에 대한 요금정보가 Null로 출력됨
SQL> select /*+ ordered use_nl(r) */
2 c.통화시간, c.국가코드, c.지역, r.요금
3 from cdr c, cdr_rating r
4 where c.통화시간 like '20050315%'
5 and c.국가코드 = r.국가코드(+)
6 and c.지역 = r.지역(+) ;
통화시간 국가 지역 요금
------------------------------ ---- -------------------- ----------
20050315 010101 82 A 100
20050315 020101 82 B 200
20050315 030101 82 C 500
20050315 040101 84 A 300
20050315 050101 84 B 500
20050315 060101 84 C 400
20050315 070101 84 D
20050315 080101 84 E
20050315 090101 86 A 500
20050315 100101 86 B 200
20050315 110101 86 C
20050315 120101 86 D
20050315 130101 86 E
20050315 140101 86 F
어떻게 쿼리해야 조인에 실패했을 때 지역이 공백 (' ')인 요금 정보를 가져올 수 있을까?
SQL> select /*+ ordered use_nl(r) */
2 c.통화시간, c.국가코드, c.지역, r.요금
3 from cdr c, cdr_rating r
4 where c.통화시간 like '20050315%'
5 and (r.국가코드, r.지역) =
6 (select c.국가코드, max(지역)
7 from cdr_rating
8 where 국가코드 = c.국가코드
9 and 지역 in (' ', c.지역) ) ;
통화시간 국가 지역 요금
------------------------------ ---- -------------------- ----------
20050315 010101 82 A 100
20050315 020101 82 B 200
20050315 030101 82 C 500
20050315 040101 84 A 300
20050315 050101 84 B 500
20050315 060101 84 C 400
20050315 070101 84 D 800
20050315 080101 84 E 800
20050315 090101 86 A 500
20050315 100101 86 B 200
20050315 110101 86 C 700
20050315 120101 86 D 700
20050315 130101 86 E 700
20050315 140101 86 F 700
9i까지는 use_concat 힌트를 이용해 아래와 같이 쿼리할수 있음
IN-List가 concatenation(or-expansion)방식으로 풀리면 뒤쪽에 있는 값이 먼저 실행되는 특징을 이용 하는 것임
select /*+ ordered use_nl(r) index(r pk_cdr_rating) */
c.통화시간, c.국가코드, c.지역, r.요금
from cdr c, cdr_rating r
where c.통화시간 like '20050315%'
and (r.국가코드, r.지역) =
(select /*+ use_concat */ c.국가코드, 지역
from cdr_rating
where 국가코드 = c.국가코드
and 지역 in (' ', c.지역)
and rownum <= 1)
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.00 0 35 0 14
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.00 0 35 0 14
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 59
Rows Row Source Operation
------- ---------------------------------------------------
14 NESTED LOOPS
14 INDEX RANGE SCAN PK_CDR (object id 32540)
14 TABLE ACCESS BY INDEX ROWID CDR_RATING
14 INDEX UNIQUE SCAN PK_CDR_RATING (object id 32538)
14 COUNT STOPKEY
14 INLIST ITERATOR
14 INDEX RANGE SCAN PK_CDR_RATING (object id 32538)
통화시간 국 지역 요금
--------------- -- ---------- ----------
20050315 010101 82 A 100
20050315 020101 82 B 200
20050315 030101 82 C 500
20050315 040101 84 A 800
20050315 050101 84 B 800
20050315 060101 84 C 800
20050315 070101 84 D 800
20050315 080101 84 E 800
20050315 090101 86 A 700
20050315 100101 86 B 700
20050315 110101 86 C 700
20050315 120101 86 D 700
20050315 130101 86 E 700
20050315 140101 86 F 700
-> 책내용: 아래와 같이하면 결과가 틀리게 됨
모든 결과에서 INLIST ITERATOR 로풀림 (Oracle 9.2.0.8)
select /*+ ordered use_nl(r) index(r pk_cdr_rating) */
c.통화시간, c.국가코드, c.지역, r.요금
from cdr c, cdr_rating r
where c.통화시간 like '20050315%'
and (r.국가코드, r.지역) =
(select /*+ use_concat */ c.국가코드, 지역
from cdr_rating
where 국가코드 = c.국가코드
and 지역 in (c.지역,' ')
and rownum <= 1)
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.00 0 35 0 14
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.00 0 35 0 14
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 59
Rows Row Source Operation
------- ---------------------------------------------------
14 NESTED LOOPS
14 INDEX RANGE SCAN PK_CDR (object id 32540)
14 TABLE ACCESS BY INDEX ROWID CDR_RATING
14 INDEX UNIQUE SCAN PK_CDR_RATING (object id 32538)
14 COUNT STOPKEY
14 INLIST ITERATOR
14 INDEX RANGE SCAN PK_CDR_RATING (object id 32538)
통화시간 국 지역 요금
--------------- -- ---------- ----------
20050315 010101 82 A 100
20050315 020101 82 B 200
20050315 030101 82 C 500
20050315 040101 84 A 800
20050315 050101 84 B 800
20050315 060101 84 C 800
20050315 070101 84 D 800
20050315 080101 84 E 800
20050315 090101 86 A 700
20050315 100101 86 B 700
20050315 110101 86 C 700
20050315 120101 86 D 700
20050315 130101 86 E 700
20050315 140101 86 F 700
10g부터는 일반적인 use_concat 힌트로는 OR-Expansion 일어나지 않기 때문에 위와 같은 기법을 쓸수 없음
10g에서는 위와 같은 기법을 사용하려면
SQL> explain plan for
2 select /*+ ordered use_nl(r) index(r pk_cdr_rating) */
3 c.통화시간, c.국가코드, c.지역, r.요금
4 from cdr c, cdr_rating r
5 where c.통화시간 like '20050315%'
6 and (r.국가코드, r.지역) =
7 (select /*+ use_concat(@subq 1) qb_name(subq) ordered_predicates */ c.국가코드, 지역
8 from cdr_rating
9 where 국가코드 = c.국가코드
10 and 지역 in (' ', c.지역)
11 and rownum <= 1) ;
해석되었습니다.
SQL> @plan
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 1842647818
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 29 | 2 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 9 | 261 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | PK_CDR | 13 | 273 | 1 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| CDR_RATING | 1 | 8 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | PK_CDR_RATING | 1 | | 0 (0)| 00:00:01 |
|* 5 | COUNT STOPKEY | | | | | |
| 6 | CONCATENATION | | | | | |
|* 7 | FILTER | | | | | |
|* 8 | INDEX UNIQUE SCAN | PK_CDR_RATING | 1 | 5 | 0 (0)| 00:00:01 |
|* 9 | FILTER | | | | | |
|* 10 | INDEX UNIQUE SCAN | PK_CDR_RATING | 1 | 5 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C"."통화시간" LIKE '20050315%')
filter("C"."통화시간" LIKE '20050315%')
4 - access(("R"."국가코드","R"."지역")= (SELECT /*+ USE_CONCAT (1) QB_NAME ("SUBQ") */
:B1,"지역" FROM "CDR_RATING" "CDR_RATING"???)
5 - filter(ROWNUM<=1)
7 - filter(ROWNUM<=1)
8 - access("국가코드"=:B1 AND "지역"=:B2)
9 - filter(ROWNUM<=1)
10 - access("국가코드"=:B1 AND "지역"=' ')
filter(LNNVL("지역"=:B1))
아래는 인덱스를 두 번 액세스하지 않고 서브쿼리에서 얻은 rowid로 테이블을 직접 액세스하도록 최종적으로 튜닝한 것임
SQL> explain plan for
2 select /*+ ordered use_nl(r) rowid(r) */
3 c.통화시간, c.국가코드, c.지역, r.요금
4 from cdr c, cdr_rating r
5 where c.통화시간 like '20050315%'
6 and r.rowid =
7 (select /*+ use_concat(@subq 1) qb_name(subq) ordered_predicates */ rowid
8 from cdr_rating
9 where 국가코드 = c.국가코드
10 and 지역 in (' ', c.지역)
11 and rownum <= 1
12 ) ;
해석되었습니다.
SQL> @plan
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------
Plan hash value: 1791744221
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 29 | 14 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 156 | 4524 | 14 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | PK_CDR | 13 | 273 | 1 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY USER ROWID| CDR_RATING | 12 | 96 | 1 (0)| 00:00:01 |
|* 4 | COUNT STOPKEY | | | | | |
| 5 | CONCATENATION | | | | | |
|* 6 | FILTER | | | | | |
|* 7 | INDEX UNIQUE SCAN | PK_CDR_RATING | 1 | 8 | 0 (0)| 00:00:01 |
|* 8 | FILTER | | | | | |
|* 9 | INDEX UNIQUE SCAN | PK_CDR_RATING | 1 | 8 | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C"."통화시간" LIKE '20050315%')
filter("C"."통화시간" LIKE '20050315%')
4 - filter(ROWNUM<=1)
6 - filter(ROWNUM<=1)
7 - access("국가코드"=:B1 AND "지역"=:B2)
8 - filter(ROWNUM<=1)
9 - access("국가코드"=:B1 AND "지역"=' ')
filter(LNNVL("지역"=:B1))
http://wiki.gurubee.net/pages/viewpage.action?pageId=4948223]