2021년 11월 14일 일요일

오라클 로우넘(ORACLE ROWNUM)

 오라클 로우넘(ORACLE ROWNUM)

재생 수10
  

ROWNUM은 SELECT절에 의해 추출되는 WHERE절을 만족하는 행(ROW)에 붙는 순번이다. 다시 말해 WHERE절 조건을 만족 시킨 행에 대해 1부터 붙는 순번이다.

ROWNUM은 SELECT절에 의해 추출되는 WHERE절을 만족하는 행(ROW)에 붙는 순번이다. 다시 말해 WHERE절 조건을 만족 시킨 행에 대해 1부터 붙는 순번이다.

https://www.youtube.com/channel/UC9LYhJdG5r7jImSUpnp1q1g

ROWNUM은 의사 칼럼으로 참조만 될 뿐 데이터베이스에 물리적으로 저장되지 않는다.

(DESC 명령으로 보이지 않는 의사 칼럼).

SELECT 리스트에 ROWNUM을 이용하는 것도 물론 가능하다.

ORDER BY를 사용한다면 WHERE절까지 만족 시킨 결과에 ROWNUM이 붙은 상태로 ORDER BY가 반영된다.(ORDER BY는 맨 마지막에 실행된다) 즉 ROWNUM은 ORDER BY 실행 전에 부여되며, ORDER BY는 맨 나중에 실행되므로 추출되는 순서대로 ROWNUM이 붙어 있지는 않다.

다른 데이터베이스에서 주로 사용되는 top-N 쿼리의 기능을 ROWNUM을 이용하여 구현할 수 있다.

- ROWNUM을 변경하기 위해 DML(데이터 조작어, INSERT, UPDATE, DELETE)을 사용할 수 없다.

- 주로 <, <= 사용하며 >, >= 인 경우 ROWNUM은 동작하지 않는다.

- ROWNUM = 1은 사용 가능 하지만 ROWNUM > 1, ROWNUM=2 인 경우는 데이터가 추출되지 않는다. (ROWNUM은 WHERE절을 만족하는 레코드에 붙이는 순번이므로 처음 한 건 추출해서 ROWNUM이 2인지 비교한다. 처음 레코드는 ROWNUM이 1, 조건에 맞지 않으므로 버리고 다른 다음 레코드 선택 후 또 ROWNUM이 2인지 비교하지만 역시 새로 추출되는 레코드는 ROWNUM이 1이므로 버리고 새로운 레코드를 추출한다, 이 과정을 반복해 보면 ROWNUM = 2는 도달할 수 없는 값이 됨을 알 수 있다.)

SQL> select rownum, empno, ename from emp;

ROWNUM EMPNO ENAME

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

1 7369 SMITH

2 7499 ALLEN

3 7521 WARD

……

14 개의 행이 선택되었습니다.

-- 4건만 SEELCT

SQL> select rownum, empno, ename from emp where rownum < 5;

ROWNUM EMPNO ENAME

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

1 7369 SMITH

2 7499 ALLEN

3 7521 WARD

4 7566 JONES

SQL> select rownum, empno, ename from emp;

ROWNUM EMPNO ENAME

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

1 7369 SMITH

2 7499 ALLEN

3 7521 WARD

……

14 개의 행이 선택되었습니다.

-- 4건만 SELECT, 먼저 다른 조건없이 4건을 꺼내고 ROWNUM을 1부터 부여 (이름으로 정렬해서 4건을 꺼내는 것은 아님) 후 ename으로 정렬하니 ROWNUM이 깨진다.

-- ORDER BY는 ORDER BY를 제외한 쿼리를 먼저 실행 후 그 결과에 대해 정렬하므로 ROWNUM이 ENAME기준으로 정렬되게 되어 순차적으로 부여되지 않는다.

SQL> select rownum, empno, ename from emp where rownum < 5 order by ename;

ROWNUM EMPNO ENAME

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

2 7499 ALLEN

4 7566 JONES

1 7369 SMITH

3 7521 WARD

-- 위 쿼리를 이름순으로 정렬한 후 ROWNUM을 부여하려면 다음과 같이 해야한다.

-- 안쪽 SELECT에서 이름 오름차순으로 데이터를 정렬한 후 하나씩 꺼내면서 ROWNUM을 부여하고 --- 5보다 작은지 확인한다.

SQL> select * from

( select rownum, empno, ename

from emp

order by ename )

where ROWNUM < 5;

SQL> select rownum, empno, ename from emp where rownum = 1;

ROWNUM EMPNO ENAME

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

1 7369 SMITH

-- ROWNUM=2인 경우 선택되는 로우(행)가 없다.

-- WHERE절에 ROWNUM 조건 외에는 없으므로 모든 데이터가 조건을 만족하게 되고, 최초 한건을 읽으면 ROWNUM이 1이되어 ROWNUM = 2 조건에 만족하지 않으므로 버리고, 다음 레코드를 또 읽어 ROWNUM을 부여하는데 이전에 읽은 데이터는 조건에 맞지 않아 버렸으므로 다시 ROWNUM이 1이되어 ROWNUM = 2 조건에 맞지않아 데이터를 버리고 다음 레코드를 읽게 된다. 이런식으로 하다보면 결국 ROWNUM = 2 조건을 만족하는 데이터는 없게된다.

SQL> select rownum, empno, ename from emp where rownum = 2;

선택된 레코드가 없습니다.

-- 처음 한건을 추출하고 ROWNUM이 1보다 큰지 확인 => 거짓이므로 버림, 다음 레코드 선택 후 또 ROWNUM이 1보다 큰지 확인하지만 역시 거짓이므로 버린다. 결국 모두 거짓이 되어 선택된는 레코드는 없다.

SQL> select * from emp where ROWNUM > 1;

선택된 레코드가 없습니다.

이번에는 게시판 페이지 나누기 쿼리를 해보자. 개발할 때 흔히 사용되는 쿼리인데 ROWNUM을 이용하고, 안쪽 SELECT 쿼리에서 ORDER BY를 사용하지 않고 인덱스 및 인덱스와 연관된 힌트를 사용하는 것이 포인트 이다.

myemp1 테이블(1000만건)에서 ename 칼럼을 기준으로 적절한 인덱스를 생성 후 게시판 페이지 쿼리를 작성하시오. (이미 ename 기준으로 오름차순 인덱스가 생성되어 있다.)

- 한페이지당 10개 출력.

- ename를 기준으로 내림차순으로 Dislplay.

- 10,000번째 페이지를 출력하는 쿼리를 작성하시오.

-- 먼저 myemp1 테이블의 ename컬럼에 걸려 있는 인덱스 이름을 확인

SQL> SELECT a.index_name, a.column_name, b.visibility

FROM USER_IND_COLUMNS a, USER_INDEXES b

WHERE a.table_name = 'MYEMP1'

AND a.index_name = b.index_name;

IDX_MYEMP1_ENAME

-- 인덱스가 오름차순으로 생성되었으니 내림차순으로 결과를 보기 위해 INDEX_DESC 힌트를 사용하자.

-- 오라클12C에서 17초

-- 실행계획을 살펴보면 안쪽 SELECT에 FULL TABLE SCAN 한다.

-- ROWNUM은 크거나같다(>=)가 동작하지 않기에 SELECT로 한번더 사서 rnum 이라는 이름으로 비교

-- 맨안쪽 SELECT에서 모든 데이터를 다 읽고 맨 바깥쪽에서 해당 페이지를 추출하므로 느리다.

SELECT *

FROM (

SELECT a.*, ROWNUM rnum

FROM (

SELECT empno, ename, sal

FROM myemp1 e

ORDER BY ENAME DESC

) a

) b

WHERE rnum <= 10000*10

AND rnum >= 9999*10 +1;

-- 오라클12C에서 14초

-- 실행계획을 살펴보면 안쪽 SELECT에 INDEX RABGE SCAN 한다.

SELECT *

FROM (

SELECT a.*, ROWNUM rnum

FROM (

SELECT /*+ INDEX_DESC(e IDX_MYEMP1_ENAME)*/

empno, ename, sal

FROM myemp1 e

WHERE ename > '가'

) a

) b

WHERE rnum <= 10000*10

AND rnum >= 9999*10 +1;

-- 0초

-- 안쪽 SELECT에서 ROWNUM으로 한계치를 미리 지정하여 건수를 줄임

SELECT *

FROM (

SELECT a.*, ROWNUM rnum

FROM (

SELECT empno, ename, sal

FROM myemp1 e

--WHERE ROWNUM <= 10000*10

ORDER BY ENAME DESC -- order by 때문에 rownum있어서 한번더 싼 SELECT에서 ROWNUM비교

) a

WHERE ROWNUM <= 10000*10

)

WHERE rnum >= 9999 * 10 +1;

-- 0초

-- 안쪽 SELECT에서 ROWNUM으로 한계치를 미리 지정하여 건수를 줄임

-- ORDER BY를 사용 안하니 직접 ROWNUM으로 비교 가능하므로 SELECT 절이 하나 빠짐

select empno, ename, sal

from

(

select /*+ index_desc(e IDX_MYEMP1_ENAME) */

rownum rnum,

empno,

ename,

sal

from myemp1 e

where rownum <= 10000*10

)

where rnum >= 9999*10+1;

오라클 인덱스, Bitmap 조인 인덱스(Bitmap Join Indexes)

 

오라클 인덱스, Bitmap 조인 인덱스(Bitmap Join Indexes)


Bitmap 조인 인덱스bitmap join indexes는 조인 성능 향상을 위해 두 테이블 조인시 조인한 결과 칼럼에 비트맵 인덱스를 생성하는 것 입니다. 


이 인덱스 역시 Bitmap 인덱스로 생성 방법이 조인 컬럼에 대해 생성을 하므로 Bitmap 조인 인덱스라고 합니다. Bitmap 조인 인덱스는 PKprimary key(UKunique key), FKforeign key 관계를 가진 테이블에서만 생성 가능한 인덱스 입니다.


실제 실습을 하면서 이해를 해보겠습니다.


Bitmap 조인 인덱스를 사용하지 않고 기존 B*Tree 인덱스를 이용하여 쿼리를 한 후, Bitmap 조인 인덱스를 만들고 다시 쿼리하여 성능 차이를 확인하고 실행계획도 확인을 해보겠습니다.

실습


MYEMP 테이블의 컬럼에 생성되어 있는 있는 인덱스를 조회한 후 PK 인덱스를 제외하고 삭제하세요.


SELECT A.INDEX_NAME, A.COLUMN_NAME, B.VISIBILITY 

FROM   USER_IND_COLUMNS A, USER_INDEXES B 

WHERE  A.TABLE_NAME = 'MYEMP' 

AND     A.INDEX_NAME = B.INDEX_NAME;


<실행결과>

 

INDEX_NAME

COLUMN_NAME

VISIBILITY

1

PK_MYEMP

EMPNO

VISIBLE

2

BIDX_MYEMP_DEPTNO

DEPTNO

VISIBLE


필자의 경우 PK 인덱스가 아닌 인덱스는 BIDX_MYEMP_DEPTNO 입니다.


BIDX_MYEMP_DEPTNO 인덱스 삭제하세요.


DROP INDEX BIDX_MYEMP_DEPTNO;


<실행결과>

Index BIDX_MYEMP_DEPTNO이(가) 삭제되었습니다.


MYEMP 테이블 인덱스에는 현재 PK인덱스(empno 컬럼의 인덱스)만 존재합니다.

MYEMP, MYDEPT 테이블에서  “개발1팀”이 아닌 부서원의 수를 구해 보겠습니다. 


서브쿼리를 이용하여 MYEMP, MYDEPT 테이블에서 “개발1팀”이 아닌 부서원의 수를 구하세요.


SELECT COUNT(*)

FROM    MYEMP E

WHERE E.DEPTNO IN (SELECT DEPTNO FROM MYDEPT 

                                        WHERE DNAME != '개발1팀');


<실행결과>

 

      COUNT(*)

1

      15000000


<실행계획>

F-wVJ_NnOtQRV0nZ9bAl24r8TmfmuptuocEjqgy1


실행시간은 필자 노트북 기준으로 11초 이상 걸렸습니다.(실행시간은 시스템 상황에 따라 달라질 수 있습니다.)


실행계획을 읽을 때는 가장 오른쪽으로 들여쓰기 되어 있는 곳부터 상위로 읽어오며, 같은 들여쓰기라면 위 쪽 단계를 먼저 읽습니다. MYDEPT 테이블을 먼저 FULL SCAN 하여 “개발1팀” 이 아닌 데이터를 찾고, MYEMP 테이블 역시 FULL SCAN 후 두 테이블을 deptno 컬럼을 기준으로 해시 조인을 하고 COUNT 값을 구했습니다. 집합함수 COUNT가 사용 되었으므로 최종적으로 한 행에 건수를 출력합니다.


Bitmap 조인 인덱스를 생성한 후 다시 쿼리해 보겠습니다.

실습


MYEMP와 MYDEPT 테이블을 조인하여 추출한 부서명(dname) 컬럼에 대해 IDX_MYEMP_MYDEPT_DNAME이라는 이름으로 Bitmap 조인 인덱스를 생성 합니다.


Bitmap 조인 인덱스를 생성 합니다.


CREATE BITMAP INDEX BIDX_MYEMP_MYDEPT_DNAME

ON MYEMP (MYDEPT.DNAME)

FROM          MYEMP, MYDEPT

WHERE       MYEMP.DEPTNO = MYDEPT.DEPTNO;


<실행결과>

INDEX BIDX_MYEMP_MYDEPT_DNAME(가) 생성되었습니다.


MYDEPT.DNAME 컬럼이 인덱스 생성 시 ON 절에서 참조되므로 MYDEPT 테이블에 조인하여 DNAME 컬럼을 검색하는 MYEMP 테이블에 대한 쿼리는 DEPT 테이블을 참조하지 않고도 수행 할 수 있습니다. 즉  “개발1팀”이 아닌 부서원의 수를 구하기 위해 MYDEPT 테이블에서 “개발1팀”이 아닌  deptno 컬럼을 읽어 이를 MYEMP의 deptno 컬럼과 비교하는 부분에서  MYDEPT 테이블과의 조인이 필요없어지고 “개발1팀”이 아닌 사원들의 수를 생성한 Bitmap 조인 인덱스에서 값을 가져오게 되므로 성능이 향상 됩니다.


앞에서 실행한 쿼리를 다시 실행 합니다.


서브쿼리를 이용하여 MYEMP, MYDEPT 테이블에서 “개발1팀”이 아닌 부서원의 수를 출력하세요.


SELECT  COUNT(*)

FROM     MYEMP

WHERE  DEPTNO IN (SELECT DEPTNO 

                                     FROM    MYDEPT

                                     WHERE  DNAME !=  '개발1팀');


<실행결과>

 

COUNT(*)

1

15000000


<실행계획>

XH0fdS7DoMbYKVVTWPbVdqrnRyqEaifWLkE9Xx57


생성한 Bitmap 조인 인덱스를 이용했으며 실행시간은 0.15초 정도 걸렸습니다. 


현재  MYEMP 테이블의 데이터가 2,000천만 건정도 밖에 안되지만 쿼리 수행 시간 차이는 상당 합니다. 데이터가 훨씬 많은 대용량 테이블에서는 더 차이가 날것 입니다. 


하지만 무조건 인덱스를 만드는게 좋은것은 아닙니다. 입력/수정이 빈번하다면 역효과가 날수 있으니 주의해야 하며, 특히 Bitmap 인덱스 계열은 컬럼값이 변경 될 때 동일한 값을 가진 다른 행 들도 LOCK이 발생하므로 이 인덱스는 통계성 데이터를 가지는 조회 용도의 테이블에 유리합니다.


생성한 Bitmap 조인 인덱스를 삭제 합니다.


BIDX_MYEMP_MYDEPT_DNAME 인덱스 삭제하세요.


DROP INDEX BIDX_MYEMP_MYDEPT_DNAME;


<실행결과>

Index BIDX_MYEBIDX_MYEMP_MYDEPT_DNAMEMP_DEPTNO이(가) 삭제되었습니다.

 

#비트맵조인인덱스, #인덱스, #오라클인덱스, #비트맵조인, #bitmapjoinindex, #bitmapjoin인덱스, #INDEX, #ORACLE인덱스​

(C#교육동영상)C# ADO.NET 실습 ODP.NET/ODAC 설치 오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원

  (C#교육동영상)C# ADO.NET 실습  ODP.NET/ODAC 설치  오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원 https://www.youtube.com/watch?v=qIPU85yAlzc&list=PLxU-i...