create table temp (id number primary key, num number not null) / insert into temp values(1,1) / insert into temp values(2,2) / insert into temp values(3,3) / insert into temp values(4,4) / insert into temp values(5,1) / insert into temp values(6,1) / commit / select * from temp /실행화면은 다음과 같다.
테이블이 생성되었습니다. 1 개의 행이 만들어졌습니다. 1 개의 행이 만들어졌습니다. 1 개의 행이 만들어졌습니다. 1 개의 행이 만들어졌습니다. 1 개의 행이 만들어졌습니다. 1 개의 행이 만들어졌습니다. 커밋이 완료되었습니다. ID NUM ---------- ---------- 1 1 2 2 3 3 4 4 5 1 6 1 6 개의 행이 선택되었습니다.위에서 보다시피 테이블의 ID컬럼은 primary key가, NUM컬럼은 데이터가 들어있는 형태이다. 그런데 이 테이블에는 NUM컬럼의 데이터가 중복되어 들어가 있다. 즉, ID=1일 때 NUM=1이며, ID=5일 때와 ID=6일 때도 NUM값은 1이다. 이제 NUM필드에 중복된 값을 모두 삭제해버리도록 해보자. 즉, NUM값이 1인 row를 어느것이든 한 개만 남기도록 하는 것이다.
SQL> r 1 select * from temp t1, temp t2 2* where t1.rowid > t2.rowid and t1.num = t2.num ID NUM ID NUM ---------- ---------- ---------- ---------- 5 1 1 1 6 1 1 1 6 1 5 1 SQL>이 쿼리는 중복된 row를 찾아준다. 따라서 이 쿼리를 사용하면 간단하게 중복된 데이터를 삭제할 수 있을 것이다. 그러나 위의 쿼리는 중복된 데이터를 중복되게 찾아준다. 즉, 중복된 값을 가지고 있는 row가 5,6,6으로 6이 2회 나타난다. 결과가 이렇게 나오는게 아니라 정확히 중복된 row의 ID들이 나타나게 하려면 어떻게 해야 할까?
select * from temp t1 where t1.rowid > (select min(rowid) from temp where t1.num = num) / ID NUM ---------- ---------- 5 1 6 1자 이제 원하는 데이터를 찾았으니 삭제하는 것은 매우 간단하다.
1 delete from temp 2 where id in 3 ( 4 select id from temp t1 5 where t1.rowid > 6 ( 7 select min(rowid) from temp 8 where t1.num = num 9 ) 10* ) 11 / 2 행이 삭제되었습니다. SQL> select * from temp; ID NUM ---------- ---------- 1 1 2 2 3 3 4 4 SQL>필자의 SQL스타일이 보기 싫다고 탓하지는 않기 바란다. 내용전달에 힘을 쏟느라 거기까지는 여력이 못미쳤으니… 자, 이 문제를 처음 접했고 훌륭히 풀어냈다면 당신은 뛰어난 sql 구사자이다. 이 문제가 복잡한 이유는 바로 셀프조인을 활용해야 하기 때문이다. 그리고 이러한 셀프조인의 아이디어를 생각해내고 활용하는 것이 바로 OLAP에서 나타나는 다차원 쿼리가 될 것이다. 이에 대한 깊은 내용은 생략하고(사실 알지도 못한다 -_-; 참 유연히 넘어가지 않는가? 약간 뻔뻔하다.. ^^), 우리의 본격적인 작업으로 넘어가 보겠다.
SQL> SELECT * FROM EMP; EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ---------- -------------------- ------------------ ---------- -------- ---------- ---------- ------- 7369 SMITH CLERK 7902 80/12/17 800 20 7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30 7521 WARD SALESMAN 7698 81/02/22 1250 500 30 7566 JONES MANAGER 7839 81/04/02 2975 20 7654 MARTIN SALESMAN 7698 81/09/28 1250 1400 30 7698 BLAKE MANAGER 7839 81/05/01 2850 30 7782 CLARK MANAGER 7839 81/06/09 2450 10 7788 SCOTT ANALYST 7566 87/04/19 3000 20 7839 KING PRESIDENT 81/11/17 5000 10 7844 TURNER SALESMAN 7698 81/09/08 1500 0 30 7876 ADAMS CLERK 7788 87/05/23 1100 20 7900 JAMES CLERK 7698 81/12/03 950 30 7902 FORD ANALYST 7566 81/12/03 3000 20 7934 MILLER CLERK 7782 82/01/23 1300 10 14 개의 행이 선택되었습니다. SQL>멋지다! 자 이제 문제는 SAL이 가장 높은 순으로 10명을 뽑으라는 것이다. 일단 간단히 ORDER BY로 SAL을 정렬하면 될 것이다.
SQL> SELECT * FROM EMP 2 ORDER BY SAL DESC; EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ---------- -------------------- ------------------ ---------- -------- ---------- ---------- ------- 7839 KING PRESIDENT 81/11/17 5000 10 7788 SCOTT ANALYST 7566 87/04/19 3000 20 7902 FORD ANALYST 7566 81/12/03 3000 20 7566 JONES MANAGER 7839 81/04/02 2975 20 7698 BLAKE MANAGER 7839 81/05/01 2850 30 7782 CLARK MANAGER 7839 81/06/09 2450 10 7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30 7844 TURNER SALESMAN 7698 81/09/08 1500 0 30 7934 MILLER CLERK 7782 82/01/23 1300 10 7521 WARD SALESMAN 7698 81/02/22 1250 500 30 7654 MARTIN SALESMAN 7698 81/09/28 1250 1400 30 7876 ADAMS CLERK 7788 87/05/23 1100 20 7900 JAMES CLERK 7698 81/12/03 950 30 7369 SMITH CLERK 7902 80/12/17 800 20 14 개의 행이 선택되었습니다. SQL>자 이 쿼리를 한 번 더 둘러싸주면 간단히 10명을 뽑을 수 있다.
SQL> R 1 SELECT E.* FROM 2 ( 3 SELECT * FROM EMP 4 ORDER BY SAL DESC 5 ) E 6* WHERE ROWNUM <= 10 EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ---------- -------------------- ------------------ ---------- -------- ---------- ---------- ------- 7839 KING PRESIDENT 81/11/17 5000 10 7788 SCOTT ANALYST 7566 87/04/19 3000 20 7902 FORD ANALYST 7566 81/12/03 3000 20 7566 JONES MANAGER 7839 81/04/02 2975 20 7698 BLAKE MANAGER 7839 81/05/01 2850 30 7782 CLARK MANAGER 7839 81/06/09 2450 10 7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30 7844 TURNER SALESMAN 7698 81/09/08 1500 0 30 7934 MILLER CLERK 7782 82/01/23 1300 10 7521 WARD SALESMAN 7698 81/02/22 1250 500 30 10 개의 행이 선택되었습니다. SQL>쿼리가 약간 이상해보이는가? E.* 부분이 그럴지도 모르겠다. 저러한 표현이 가능하다는 것 정도는 알아두자. 고백할 것이 있다. 이 쿼리는 오라클 8i 이상에서만 작동하며 오라클 7이나 오라클 8에서는 동작하지 않는다. 왜냐하면 이들 오라클 7이나 8버전에서는 서브쿼리내에 order by를 사용할 수 없기 때문이다. 이러한 경우에는 약간의 편법을 사용해야 한다.
SQL> GET B 1 SELECT EMPNO, ENAME, ABS(SAL) FROM 2 ( 3 SELECT E.EMPNO, -E.SAL SAL, MAX(E.ENAME) ENAME FROM EMP E 4 GROUP BY -E.SAL, E.EMPNO 5 ) 6* WHERE ROWNUM <= 10 SQL> / EMPNO ENAME ABS(SAL) ---------- -------------------- ---------- 7839 KING 5000 7788 SCOTT 3000 7902 FORD 3000 7566 JONES 2975 7698 BLAKE 2850 7782 CLARK 2450 7499 ALLEN 1600 7844 TURNER 1500 7934 MILLER 1300 7521 WARD 1250 10 개의 행이 선택되었습니다. SQL>이번엔 쿼리가 약간 복잡하다. 세세히 뜯어보도록 하자.
3 SELECT E.EMPNO, -E.SAL SAL, MAX(E.ENAME) ENAME FROM EMP E 4 GROUP BY -E.SAL, E.EMPNOgroup by를 사용해 e.sal으로 묶어준다. 그러나 이렇게만 묶어버리면 sal이 중복되는 row들이 사라져버린다. 따라서 여기서는 EMPNO(EMP테이블의 primary key이다)까지 묶어서 group by를 해주었다. group by 는 desc구문이 존재하지 않으므로 sal이 큰 순으로 나오려면 -E.SAL 형태로 group by를 해주어야 한다. 그리고 group by에 나타나지 않은 컬럼들인 E.ENAME과 은 단순히 MAX()함수를 취해주었다. 이것은 항상 그룹이 단일 row단위로 들어오므로 언제나 단순히 E.ENAME을 돌려준다.
1 SELECT EMPNO, ENAME, ABS(SAL) FROM 2 ( 3 SELECT E.EMPNO, -E.SAL SAL, MAX(E.ENAME) ENAME FROM EMP E 4 GROUP BY -E.SAL, E.EMPNO 5 ) 6* WHERE ROWNUM <= 10이제 전체적인 데이터가 도달한 상태에서 10개의 row를 뽑아주기만 하면 된다. 단, SAL 값은 -에 의해 음수로 변해서 왔으므로 이는 다시 ABS함수를 쓰던가 다시 -를 해주어 원래 양수값으로 바꿔줘야 한다. 여기서 제시한 오라클 7,8에서의 서브쿼리에서의 소팅 방법은 group by를 사용해 서브쿼리에서 소트를 할 수 있다는 것과, 소트할 컬럼을 group by의 제일 앞에 명시하고, 다음에 중복된 row가 사라지는 것을 막기위해 pk를 group by에 명시하며 마지막으로, group by 에 명시되지 않았으면서 select 절에 나타난 컬럼들에 max()를 취해준다는 것이다. 그러나 다음과 같이 해볼 수도 있겠다.
SQL> get b 1 SELECT EMPNO, ENAME, ABS(SAL) FROM 2 ( 3 SELECT E.EMPNO, -E.SAL SAL, E.ENAME ENAME FROM EMP E 4 GROUP BY -E.SAL, E.EMPNO, E.ENAME 5 ) 6* WHERE ROWNUM <= 10 SQL> / EMPNO ENAME ABS(SAL) ---------- -------------------- ---------- 7839 KING 5000 7788 SCOTT 3000 7902 FORD 3000 7566 JONES 2975 7698 BLAKE 2850 7782 CLARK 2450 7499 ALLEN 1600 7844 TURNER 1500 7934 MILLER 1300 7521 WARD 1250 10 개의 행이 선택되었습니다. SQL>여기서는 모든 컬럼을 group by에 명시하되 sal컬럼만 앞에 가게 해준다는 것이다. 어느 방법이든 맘에 드는 것을 골라 쓰면 되겠다.
1 SELECT EMPNO, ENAME, SAL FROM EMP E1 2 WHERE ( 3 SELECT COUNT(1) FROM EMP E2 4 WHERE E1.SAL < E2.SAL 5 ) <= 10 6* ORDER BY SAL DESC SQL> / EMPNO ENAME SAL ---------- -------------------- ---------- 7839 KING 5000 7788 SCOTT 3000 7902 FORD 3000 7566 JONES 2975 7698 BLAKE 2850 7782 CLARK 2450 7499 ALLEN 1600 7844 TURNER 1500 7934 MILLER 1300 7521 WARD 1250 7654 MARTIN 1250 11 개의 행이 선택되었습니다. SQL>ORDER BY SAL DESC가 존재하지만 이 ORDER BY 문장은 10개의 데이터에 대해서만 SORT를 하므로 SORT비용이 매우 낮음에 주목하라. 그러나 이에 비해 앞서 했던 10번째까지의 사람 구하기는 GROUP BY나 ORDER BY에 의해서 사용되었으므로 SORT비용이 매우 컸다.
SQL> SELECT EMPNO, ENAME, SAL, DNAME 2 FROM EMP E, DEPT D 3 WHERE E.DEPTNO = D.DEPTNO 4 / EMPNO ENAME SAL DNAME ---------- -------------------- ---------- ---------------------------- 7369 SMITH 800 RESEARCH 7499 ALLEN 1600 SALES 7521 WARD 1250 SALES 7566 JONES 2975 RESEARCH 7654 MARTIN 1250 SALES 7698 BLAKE 2850 SALES 7782 CLARK 2450 ACCOUNTING 7788 SCOTT 3000 RESEARCH 7839 KING 5000 ACCOUNTING 7844 TURNER 1500 SALES 7876 ADAMS 1100 RESEARCH 7900 JAMES 950 SALES 7902 FORD 3000 RESEARCH 7934 MILLER 1300 ACCOUNTING 14 개의 행이 선택되었습니다. SQL>우리가 하고자 하는 것은 각 부서별로 2등까지(두 번째 까지가 아니다)의 사람을 구하는 것이다. 원하는 데이터 형태는 아래와 같이 나타난다.
DNAME RANK EMPNO ENAME 부서A 1 XXXX 홍길동 2 YYYY 김길동 부서B 1 NNNN 박길동 <- 부서 B에는 한명의 사원만 존재하는 경우 부서C 1 XXXX 서길동 2 ZZZZ 황길동이것은 쉽지 않을 것이다. 머리 싸매고 연구해보자.
1 SELECT * FROM 2 ( 3 SELECT DNAME, EMPNO, ENAME, SAL, RANK() OVER ( PARTITION BY E.DEPTNO ORDER BY SAL DESC ) RNK 4 FROM DEPT D, EMP E 5 WHERE D.DEPTNO = E.DEPTNO 6 ) 7* WHERE RNK <= 2 SQL> / DNAME EMPNO ENAME SAL RNK ---------------------------- ---------- -------------------- ---------- ---------- ACCOUNTING 7839 KING 5000 1 7782 CLARK 2450 2 RESEARCH 7788 SCOTT 3000 1 7902 FORD 3000 1 SALES 7698 BLAKE 2850 1 7499 ALLEN 1600 2 6 개의 행이 선택되었습니다. SQL>답을 보고 의아하게 생각하는 사람들이 있을지 모르겠다. 그러나 사실 ANSI SQL가지고만 풀기에 이 문제는 난이도가 높다. 위에 등장한 SQL코드에는 8i부터 등장한 analytic function이 사용되었다. 이들을 사용해 이 문제와 같은 매우 복잡한 SQL문장을 단 한번에 한 큐에 날려보낼 수 있으며, 더군다나 이들은 OLAP의 다차원 쿼리에 활용될 수 있는 강력한 무기이다.
3 SELECT DNAME, EMPNO, ENAME, SAL, RANK() OVER ( PARTITION BY E.DEPTNO ORDER BY SAL DESC ) RNK 4 FROM DEPT D, EMP E 5 WHERE D.DEPTNO = E.DEPTNO바로 이 부분이 핵심인데 그 중에서도 핵심은 RANK() OVER ( PARTITION BY E.DEPTNO ORDER BY SAL )의 의미를 파악하면 이 쿼리를 완전히 아는 것이다. 먼저 RANK() 함수는 순위를 구하는 함수이며 PARTITION 은 부분을 나누는 구문이다. 그리고 ORDER BY는 각 PARTITION(부분) 내에서의 정렬을 위한 컬럼을 명시하는데 쓰인다. 즉, 위의 코드는 E.DEPTNO 를 기준으로 파티션들을 분할하고 그들 각각을 SAL에 의해서 역순으로 정렬한 순위를 얻어오라는 것이다. 이 코드만 실행하면 아래와 같은 결과를 얻는다.
SQL> GET B 1 SELECT DNAME, EMPNO, ENAME, SAL, RANK() OVER ( PARTITION BY E.DEPTNO ORDER BY SAL DESC ) RNK 2 FROM DEPT D, EMP E 3* WHERE D.DEPTNO = E.DEPTNO SQL> / DNAME EMPNO ENAME SAL RNK ---------------------------- ---------- -------------------- ---------- ---------- ACCOUNTING 7839 KING 5000 1 7782 CLARK 2450 2 7934 MILLER 1300 3 RESEARCH 7788 SCOTT 3000 1 7902 FORD 3000 1 7566 JONES 2975 3 7876 ADAMS 1100 4 7369 SMITH 800 5 SALES 7698 BLAKE 2850 1 7499 ALLEN 1600 2 7844 TURNER 1500 3 7521 WARD 1250 4 7654 MARTIN 1250 4 7900 JAMES 950 6 14 개의 행이 선택되었습니다. SQL>보다시피 RANK()가 각 파티션 내의 순위를 구해주고 있다.
1 SELECT * FROM 2 ( 3 SELECT DNAME, EMPNO, ENAME, SAL, RANK() OVER ( PARTITION BY E.DEPTNO ORDER BY SAL DESC ) RNK 4 FROM DEPT D, EMP E 5 WHERE D.DEPTNO = E.DEPTNO 6 ) 7* WHERE RNK <= 2그리고 위에서처럼 SELECT문으로 한번 더 묶고 RNK로 FILTERING을 해주어 원하는 결과를 획득한다.
이전 글 : 이해하기 어려운 C ?
다음 글 : 이아스님이 제공하는 자바 헤드라인
최신 콘텐츠