상세 컨텐츠

본문 제목

오라클 실무자를 위한 SQL 튜닝 실무 사례 ( PART III )

프로그래밍/DB

by 라제폰 2008. 12. 26. 21:14

본문

오라클 사용자를 위한

 

 

SQL 튜닝 실무 사례

 

( PART III )

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2007 6 21

 


PART  III

퀴즈 1.  중복된 로우만 골라서 삭제하라..........................................................................................................

퀴즈 2.  정확히 10위까지만 구하라......................................................................................................................

퀴즈 3.  공수별 비용 집계를 구하라..................................................................................................................

퀴즈 4.  제품별 집계와 등급별 집계를 같이 구하라...............................................................................

퀴즈 5.  당일 , 일주일 , 한달 분을 같이 구하라.............................................................................

퀴즈 6.  테이블의 값을 가로로 표시하라..................................................................................

퀴즈 7.  값이 바뀔 마다 일련번호를 다시 시작하라........................................................................

퀴즈 8.  불용 카드 수를 구하라............................................................................................................................

퀴즈 9.  판매일자, 판매량 별로 누적 판매량을 구하라.........................................................................

퀴즈 10.  4/4 분기 동안 점포별  매출액을 구하라...................................................................................

퀴즈 11.  특정 품종에 대한 판매수량과 금액을 구하라................................................................

퀴즈 12.  한번이라도 매출을 일으킨 고객을 구하라...............................................................................

퀴즈 13.  매출액 20,000 이하 또는 없는 고객을 삭제하라...................................................................

퀴즈 14.  가산점과 신용 한도를 증가시켜라................................................................................................

퀴즈 15.  매출 순위 10위까지의 고객 리스트를 작성하라..................................................................

퀴즈 16.  일별 총계 제품별 총계를 같이 구하라...............................................................................

퀴즈 17.  입력 구분에 따라 서로 다른 총매출액을 구하라................................................................

퀴즈 18.  년간 매출을 단위로 매출 구분하여 구하라.....................................................................

 

 

 

 

 

여기서 소개된 퀴즈들은 독자들의 SQL 이해력을 높이고 집합적 사고 능력을 함양 시키고자 준비한 것으로 간단하지만 많은 생각을 하여야만 정확한 답을 구할 수가 있다.

다만, 주의 점은 데이터가 많아졌을 나타날 있는 성능 상의 문제는 고려하지 않았으므로 실무 적용 시에는 다른 여러 가지 요소를 같이 고려하여 적용해야 한다는 것이다.

 


퀴즈 1.  중복된 로우만 골라서 삭제하라.

 

1) 배경

 

프로그램 개발을 위하여 샘플 데이터를 로딩한 PRIMARY KEY UNIQUE 인덱스를 만들고 싶지만 원시 데이터의 잘못으로 중복된 로우가 있으면 에러가 발생한다.

이럴 경우에 이를 확인하고 삭제하고 싶을 때가 있다.

 

EMP 테이블을 예를 들어 설명한다.

 

SQL> SELECT ROWID, ROWNUM, EMPNO, ENAME

  2  FROM EMP

  3  ORDER BY EMPNO, ROWID;

 

ROWID               ROWNUM   EMPNO ENAME

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

00024F04.0002.0005       3    7698 BLAKE

00024F04.0005.0005       4    7698 BLAKE   <-- 삭제 대상

00024F04.0006.0005       5    7782 CLARK

00024F04.0001.0005       2    7788 SCOTT

00024F04.0007.0005       6    7788 SCOTT   <-- 삭제 대상

00024F04.000B.0005      10    7788 SCOTT   <-- 삭제 대상

00024F04.0008.0005       7    7839 KING

00024F04.0000.0005       1    7844 TURNER

00024F04.0009.0005       8    7844 TURNER  <-- 삭제 대상

00024F04.000A.0005       9    7876 ADAMS

 

10 rows selected.

 

2) 요구 내용

 

위의 데이터 EMPNO 컬럼의 값이 중복된 로우에 대하여 ROWID 값이 로우만 골라서 삭제하라.

 

3) 해답

 

중복된 로우를 찾아보기만 하는 경우

 

SELECT  EMPNO, ENAME  FROM EMP A

WHERE   ROWID  >  ( SELECT MIN(ROWID) FROM EMP B

                     WHERE A.EMPNO = B.EMPNO );

 

중복된 로우를 찾아서 ROWID 것을 삭제하는 경우 ROWID 것은 아마도 뒤에 생긴 데이터일 가능성이 많기 때문이다.

 

DELETE FROM EMP A

WHERE  ROWID  >  ( SELECT MIN(ROWID) FROM EMP B

                   WHERE A.EMPNO = B.EMPNO );

 

만약 나중에 들어온 데이터를 살리고 먼저 들어온 데이터를 삭제하고자 한다면 WHERE 조건의 부등호와 MIN MAX 바꾸어서 다음과 같이 하면 된다.

 

DELETE FROM EMP A

WHERE  ROWID  <  ( SELECT MAX(ROWID) FROM EMP B

                   WHERE A.EMPNO = B.EMPNO );

 

 


퀴즈 2.  정확히 10위까지만 구하라.

 

1) 배경

 

고정된 형태의 화면에서 항상 정확히 10 등까지의 데이터만 필요한 경우이다.

다음과 같은 EMP 테이블과 데이터가 있다.

 

SELECT ROWNUM, SAL, ENAME, JOB, EMPNO

FROM EMP

ORDER BY SAL DESC, ENAME;

 

ROWNUM 데이터는 ORDER BY 의해 소트 수행되기 전에 붙여지므로 결과는 다음과 같이 나타난다.

 

 ROWNUM     SAL ENAME      JOB         EMPNO

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

      9    5000 KING       PRESIDENT    7839

     13    3000 FORD       ANALYST      7902

      8    3000 SCOTT      ANALYST      7788

      4    2975 JONES      MANAGER      7566

      6    2850 BLAKE      MANAGER      7698

      7    2450 CLARK      MANAGER      7782

      2    1600 ALLEN      SALESMAN     7499

     10    1500 TURNER     SALESMAN     7844

     14    1300 MILLER     CLERK        7934  <---  9 번째 로우  9

      5    1250 MARTIN     SALESMAN     7654  <--- 10 번째 로우 10   : CASE 2

      3    1250 WARD       SALESMAN     7521  <--- 11 번째 로우 10   : CASE 1

     11    1100 ADAMS      CLERK        7876  <--- 12 번째 로우 12

     12     950 JAMES      CLERK        7900

      1     800 SMITH      CLERK        7369

 

14 rows selected.

 

 

2) 요구 내용

 

위의 데이터를 이용하여 순위를 나타내는 컬럼과 함께 10 까지 나오는 SQL문과 (위의 샘플 데이터 경우 WARD, 11 번째 로우) 정확히 10번째 로우까지만 나오는 (위의 샘플 데이터 경우 MARTIN, 10 번째 로우) SQL문을 작성하라.

, 다음과 같은 결과가 나오게 하라.

 

 ROWNUM     SAL ENAME      JOB         EMPNO

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

      1    5000 KING       PRESIDENT    7839

      2    3000 FORD       ANALYST      7902

      3    3000 SCOTT      ANALYST      7788

      4    2975 JONES      MANAGER      7566

      5    2850 BLAKE      MANAGER      7698

      6    2450 CLARK      MANAGER      7782

      7    1600 ALLEN      SALESMAN     7499

      8    1500 TURNER     SALESMAN     7844

      9    1300 MILLER     CLERK        7934

     10    1250 MARTIN     SALESMAN     7654

     11    1250 WARD       SALESMAN     7521

 

11 rows selected.

 

3) 해답

 

l     CASE 1 : 상위 10 까지 로우를 출력

 

SELECT  ROWNUM, SAL, ENAME, JOB, EMPNO

FROM    EMP E1

WHERE   10 > ( SELECT COUNT(*)

               FROM EMP E2

               WHERE E2.SAL > E1.SAL )

ORDER  BY  SAL DESC ;

 

다음과 같은 출력이 나온다.

 

 ROWNUM     SAL ENAME      JOB         EMPNO

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

      8    5000 KING       PRESIDENT    7839

      7    3000 SCOTT      ANALYST      7788

     10    3000 FORD       ANALYST      7902

      3    2975 JONES      MANAGER      7566

      5    2850 BLAKE      MANAGER      7698

      6    2450 CLARK      MANAGER      7782

      1    1600 ALLEN      SALESMAN     7499

      9    1500 TURNER     SALESMAN     7844

     11    1300 MILLER     CLERK        7934

      2    1250 WARD       SALESMAN     7521

      4    1250 MARTIN     SALESMAN     7654

 

11 rows selected.

 

그러나 출력은 ROWNUM 값이 소트 되기 전에 붙으므로 원하는 결과와 다르다.

ROWNUM 순차적으로 표시하기 위해 IN-LINE VIEW 하나 씌운다.

그러나 이렇게 되면 IN-LINE VIEW 내에서 ORDER BY 문을 사용할 없게 되므로 GROUP BY 문을 활용하여 소트 기능을 대신한다.

 

SELECT ROWNUM  AS RANK,  -SAL SAL, ENAME, JOB, EMPNO

FROM ( SELECT -SAL SAL, ENAME, JOB, EMPNO

       FROM   EMP E1

       WHERE  10  >  ( SELECT COUNT(*)

                       FROM EMP E2

                       WHERE E2.SAL > E1.SAL )

       GROUP BY -SAL, ENAME, JOB, EMPNO) ;

 

다음과 같은 출력이 나온다.

 

 

 

 

ROWNUM     SAL ENAME      JOB         EMPNO

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

      1    5000 KING       PRESIDENT    7839

      2    3000 SCOTT      ANALYST      7788

      3    3000 FORD       ANALYST      7902

      4    2975 JONES      MANAGER      7566

      5    2850 BLAKE      MANAGER      7698

      6    2450 CLARK      MANAGER      7782

      7    1600 ALLEN      SALESMAN     7499

      8    1500 TURNER     SALESMAN     7844

      9    1300 MILLER     CLERK        7934

     10    1250 WARD       SALESMAN     7521

     11    1250 MARTIN     SALESMAN     7654

 

11 rows selected.

 

l     CASE 2 : 상위 10 까지 로우를 출력

 

SELECT ROWNUM  AS RANK,  -SAL SAL, ENAME, JOB, EMPNO

FROM ( SELECT -SAL SAL, ENAME, JOB, EMPNO

       FROM   EMP E1

       WHERE  10  >  ( SELECT COUNT(*)

                       FROM EMP E2

                       WHERE E2.SAL > E1.SAL )

       GROUP BY -SAL, ENAME, JOB, EMPNO)

WHERE ROWNUM <= 10 ;

 

최종 결과는 위와 같다.

정확히 10 번째 로우에서 자르기 위해 ROWNUM <= 10 조건을 추가하였다.

 

그러나 처음부터 10 번째 로우에서 자르겠다면 굳이 위와 같이 하지 않고 간단히 아래와 같이 하는 방법도 있다.

 

 

SELECT  ROWNUM, -SAL SAL, ENAME, JOB, EMPNO

FROM   (SELECT -SAL SAL, ENAME, JOB, EMPNO

        FROM   EMP

        GROUP BY -SAL, ENAME, JOB, EMPNO)

WHERE ROWNUM <= 10 ;

 

위의 SQL문의 결과는 다음과 같다.

 

ROWNUM     SAL  ENAME      JOB         EMPNO

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

      1    5000 KING       PRESIDENT    7839

      2    3000 SCOTT      ANALYST      7788

      3    3000 FORD       ANALYST      7902

      4    2975 JONES      MANAGER      7566

      5    2850 BLAKE      MANAGER      7698

      6    2450 CLARK      MANAGER      7782

      7    1600 ALLEN      SALESMAN     7499

      8    1500 TURNER     SALESMAN     7844

      9    1300 MILLER     CLERK        7934

     10    1250 MARTIN     SALESMAN     7654

 

10 rows selected.

 


퀴즈 3.  공수별 비용 집계를 구하라.

 

1) 배경

 

어떤 건설 회사의 전표 테이블에서 투입된 공임에 대한 데이터를 다음과 같이 관리하고 있다.

동일한 성격의 일을 하고 있는데 작업 번호 101      번은 1 명이 투입되어 6 시간 걸려서 끝냈고 그때의 비용은 200,000 이었고, 102 번은 3 명이 투입되어 2 시간 만에 끝냈으며 때의 비용은 230,000 이다.

 

작업 번호

인원

시간

비용

101

1

6

200,000

102

3

2

230,000

103

2

3

220,000

104

6

1

300,000

105

1

8

300,000

106

5

3

500,000

107

2

6

400,000

108

1

15

550,000

109

1

12

450,000

110

3

5

530,000

 

...

...

...

150

4

6

900,000

 

2) 요구 내용

 

작업 번호 101, 102번을 예로 들면 작업은 서로 다른 같지만 "인원*시간" 값이 같으므로 같은 공수로 있다. 

이와 같은 맥락으로 보면 작업 번호 103, 104번도 동일한 공수이다.

따라서 공수가 6 작업의 비용을 모두 구하면 200,000 + 230,000 + 220,000 + 300,000 = 950,000원이 된다.

 

공수

비용합계

요구 내용

6

950,000

왼편의 표와 같이 모든 공수의 종류를 구하고 공수별로 들어간 비용의 합계를 구하라.

 

8

300,000

12

850,000

15

1,580,000

...

.....

24

900,000

 

 

3) 해답

 

SELECT 시간*인원 "공수", SUM(비용) "금액"

FROM   전표테이블

WHERE  발생일 LIKE '9610%'

GROUP BY 시간*인원 ;

 

GROUP BY 시간*인원 라는 아이디어를 이용하여 하나의 SQL 문으로 있다.

 


퀴즈 4.  제품별 집계와 등급별 집계를 같이 구하라.

 

1) 배경

 

어떤 제조 회사의 제품별 판매 내용이 다음과 같다.

1996 10월에 판매된 제품의 내용은 다음과 같다.

 

제품명

모델명

수량

매출액

집계구분

HP

HJ300

1

10,000

HP

HJ300

2

20,000

HP

HA120

1

5,000

HP

HB580

1

20,000

HP

HB580

3

6,000

LD

A530G

1

100,000

 

LD

A530G

3

300,000

LD

A210K

2

400,000

LD

A210K

3

600,000

PP

P530C

10

100,000

PP

P530D

5

25,000

PP

R640A

10

15,000

PP

R650B

20

20,000

 

2) 요구 내용

 

위와 같이 판매일자와 함께 각각의 제품에 대한 판매 내용이 있을 전체 판매에 대한 수량과 매출액 등의 통계자료를 보고싶은 것이다.

, 통계자료를 제품명이 HP 제품은 저가여서 제품별로 봐도 되니까 모델 별은 무시하고 제품별로 수량과 매출액 합계를 내고, 제품명이 LD 제품은 고가이므로 모델별로 수량과 매출액 합계를 내고, 제품명이 PP 제품은 저가 단품들 이므로 모든 모델을 하나로 몰아서 기타로 하여 수량과 매출액에 대한 합계를 구하라.

그러나 제품명이 PP 제품 중에서 모델명이 P530C 제품은 최근에 개발한 신제품이므로 기타에 합치지 말고 별도로 빼내어 모델명까지 구분하도록 하라.

 

3) 해답

 

GROUP BY DECODE 절묘한 조화로 다음과 같이 하나의 SQL 문으로 답을 구할 있다.

 

SELECT DECODE(제품,'HP', 제품, 'LD', 모델명, 'PP',

                    DECOE(모델명, 'P530C', 모델명, '기타')),  <--- 제품이 'PP'인 경우

       SUM(수량), SUM(매출액)

FROM   매출테이블

WHERE  매충일 LIKE '199610%'

GROUP  BY  DECODE(제품,'HP', 제품, 'LD', 모델명, 'PP',

                        DECODE(모델명, 'P530C', 모델명, '기타')) ;

 

 

 

 

 


퀴즈 5.  당일 , 일주일 , 한달 분을 같이 구하라.

 

1) 배경

 

다음과 같은 판매 데이터가 들어있는 테이블이 있다.

온라인 화면에서 사용되며 통계 테이블은 별도로 준비되어 있지 않다.

 

SALEDATE

SALEDEPT

ITEM

CHULQTY

SALEAMT

 

...

...

...

...

...

 

19961021

100

P100

100

1000

 

19961021

100

P102

50

500

 

19961021

101

Q110

10

2000

 

19961021

101

Q111

10

2000

 

19961021

102

R110

20

1000

 

19961022

100

P100

200

2000

최근 최고분

19961022

100

P102

100

1000

 

19961022

101

Q110

5

1000

최근 최고분

...

...

...

...

...

 

 

2) 요구 내용

 

테이블을 이용하여 당일분 합계, 최근 일주일분 합계, 가장 최근의 최고 판매량분, 월간 합계를 같이 구하라.

 

통상 " 가장 최근의 최고 판매량분" 그날의 판매 데이터 최고 판매량 이지만 그날의 데이터가 아직 들어가지 않은 상태라면 어제의 판매량 최고의 데이터를 가져온다.

만약 오늘이 1996 10 22 이라면 부서별 가장 최근 최고 분은 위의 표시된 로우와 같다.

 

 

 

 

3) 해답

 

DECODE, SIGN, MAX 함수를 사용하여 다음과 같이 하나의 SQL 문으로 해결할 있다.

 

SELECT SALEDEPT,

       SUM(DECODE(SALEDATE, TO_CHAR(SYSDATE,'YYYYMMDD'),SALEQTY,0)) AS '당일합계' ,

       SUM(DECODE(SIGN (8 - (SYSDATE - TO_DATE(SALEDATE,'YYYYMMDD'))

                           ), 1, SALEQTY)) AS  '일주일분합계' ,

       SUBSTR(MAX(SALEDATE || CHULQTY),9,20) AS '최근 최고분'

       SUM(SALEAMT) AS '월간합계'

FROM   판매테이블

WHERE  SALEDATE LIKE '9610%'

GROUP  BY SALEDEPT ;

 

 

 

 

 

 


퀴즈 6.  테이블의 값을 가로로 표시하라.

 

1) 배경

 

일반 프로그램이나 브라우저에서 동일한 테이블의 데이터를 옆으로 반복하여 표시하고 사용할 있다.

다음과 같은 데이터가 있다고 본다.

 

SQL> SELECT EMPNO,ENAME FROM EMP ;

 

EMPNO ENAME

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

   7369 SMITH

   7499 ALLEN

   7521 WARD

   7566 JONES

   7654 MARTIN

   7698 BLAKE

   7782 CLARK

   7788 SCOTT

   7839 KING

   7844 TURNER

   7876 ADAMS

 

11 rows selected.

 

2) 요구 내용

 

위와 같은 결과가 다음과 같이 테이블의 값을 가로로 표시하라.

 

 

 

 

 

    ORD   EMPNO ENAME      JOB         EMPNO ENAME      JOB

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

      1    7369 SMITH      CLERK        7499 ALLEN      SALESMAN

      2    7521 WARD       SALESMAN     7566 JONES      MANAGER

      3    7654 MARTIN     SALESMAN     7698 BLAKE      MANAGER

      4    7782 CLARK      MANAGER      7788 SCOTT      ANALYST

      5    7839 KING       PRESIDENT    7844 TURNER     SALESMAN

      6    7876 ADAMS      CLERK

 

6 rows selected.

 

3) 해답

 

STEP 1 : 우선 ROWNUM 짝수, 홀수로 구분하여 가로로 표시한다.

 

SQL> SELECT ROWNUM,TRUNC((ROWNUM+1)/2) AS ORD,

  2         DECODE(MOD(ROWNUM,2),1,EMPNO,NULL) AS FIRST_EMPNO,

  3         DECODE(MOD(ROWNUM,2),0,EMPNO,NULL) AS SECOND_EMPNO

  4  FROM   EMP ;

 

 ROWNUM     ORD FIRST_EMPNO SECOND_EMPNO

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

      1       1        7369

      2       1                     7499

      3       2        7521

      4       2                     7566

      5       3        7654

      6       3                     7698

      7       4        7782

      8       4                     7788

      9       5        7839

     10       5                     7844

     11       6        7876

 

11 rows selected.

 

STEP 2 : 컬럼을 없애기 위해 SUM(DECODE...) 사용한다.

 

SQL> SELECT TRUNC((ROWNUM+1)/2) AS ORD,

  2         SUM(DECODE(MOD(ROWNUM,2),1,EMPNO,NULL)) AS FIRST_EMPNO,

  3         SUM(DECODE(MOD(ROWNUM,2),0,EMPNO,NULL)) AS SECOND_EMPNO

  4  FROM   EMP

  5  GROUP BY TRUNC((ROWNUM+1)/2) ;

 

    ORD FIRST_EMPNO SECOND_EMPNO

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

      1        7369         7499

      2        7521         7566

      3        7654         7698

      4        7782         7788

      5        7839         7844

      6        7876

 

6 rows selected.

 

STEP 3 : 해당 EMPNO 컬럼에 ENAME, JOB 데이터를 덧붙인다.

 

SQL> SELECT ORD,

  2         F_EMPNO AS EMPNO,A.ENAME,A.JOB,

  3         S_EMPNO AS EMPNO,B.ENAME,B.JOB

  4  FROM   ( SELECT ORD,

  5                  SUM(FIRST_EMPNO)  AS F_EMPNO,

  6                  SUM(SECOND_EMPNO) AS S_EMPNO

  7           FROM   ( SELECT TRUNC((ROWNUM+1)/2) AS ORD,

  8                           DECODE(MOD(ROWNUM,2),1,EMPNO,NULL) AS FIRST_EMPNO,

  9                           DECODE(MOD(ROWNUM,2),0,EMPNO,NULL) AS SECOND_EMPNO

 10                    FROM EMP)

 11           GROUP BY ORD ), EMP A, EMP B

 12  WHERE  F_EMPNO = A.EMPNO

 13  AND    S_EMPNO = B.EMPNO(+)

 14  ORDER BY ORD ;

 

OUTER 조인을 사용하지 않으면 SECOND_EMPNO 컬럼의 값이 NULL 경우 전체 로우의 값이 나오지 않게 되므로 반드시 S_EMPNO 조인되는 B.EMPNO 컬럼에 (+) 사인을 추가해야 한다.

 

    ORD   EMPNO ENAME      JOB         EMPNO ENAME      JOB

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

      1    7369 SMITH      CLERK        7499 ALLEN      SALESMAN

      2    7521 WARD       SALESMAN     7566 JONES      MANAGER

      3    7654 MARTIN     SALESMAN     7698 BLAKE      MANAGER

      4    7782 CLARK      MANAGER      7788 SCOTT      ANALYST

      5    7839 KING       PRESIDENT    7844 TURNER     SALESMAN

      6    7876 ADAMS      CLERK

 

6 rows selected.


퀴즈 7.  값이 바뀔 마다 일련번호를 다시 시작하라.

 

1) 배경

 

다음과 같은 데이터가 들어있는 테이블이 있다.

이해를 돕기 위해 DUMMY 컬럼을 넣어서 작성하였지만 실제로는 DUMMY 컬럼은 없다.

참고로 ROWNUM 컬럼도 같이 보기 바란다.

 

SQL> DESC TSTBL;

 

 Name                            Null?    Type

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

 PK1                                      CHAR(3)

 PK2                                      CHAR(5)

 DUMMY                                    NUMBER

 

SQL> SELECT PK1, PK2, DUMMY, ROWNUM FROM TSTBL

  2  ORDER BY PK1, PK2 ;

 

PK1 PK2     DUMMY  ROWNUM

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

A01 AS011       1       1

A01 AS012       2       2

A01 AS013       3       3

A01 AS014       4       4

A01 AS015       5       5

A02 AS021       1       6

A02 AS022       2       7

A02 AS023       3       8

A03 AS021       1       9

B01 BS011       1      10

B01 BS012       2      11

B01 BS013       3      12

B01 BS014       4      13

B01 BS015       5      14

B01 BS016       6      15

B01 BS017       7      16

B01 BS018       8      17

B01 BS018       8      17

B02 BS021       1      18

B02 BS022       2      19

B02 BS023       3      20

B02 BS024       4      21

C01 CS011       1      22

C01 CS012       2      23

C02 CS021       1      24

C02 CS022       2      25

C02 CS023       3      26

C02 CS024       4      27

 

27 rows selected.

 

2) 요구 내용

 

위의 데이터 PK1, PK2 컬럼만 사용하여 PK1 컬럼의 값이 바뀔 마다 일련번호가 1 부터 새롭게 시작되도록 하라.

, 일련번호가 나오는 컬럼을 SEQ 컬럼이라 하면 PK1, PK2 컬럼만 사용하여 SEQ 컬럼이 값이 DUMMY 컬럼의 값처럼 나오게 하라.

 

3) 해답

 

우선 기존에 나오는 ROWNUM 컬럼의 값과 PK1으로 GROUP BY하여 나오는 COUNT(*) 값을 이용하여 구한다.

 

 

 

 

 

 

 

 

 

STEP 1 : 우선 GROUP BY 값을 COUNT ROWNUM 붙여서 결과를 SELF 조인 하여 바로 PK1 까지 COUNT 값을 구한다.

 

SQL> SELECT B.PK1, B.RNUM AS B_RNUM, C.RNUM AS C_RNUM, C.CNT

  2  FROM   (SELECT ROWNUM AS RNUM, PK1, CNT

  3          FROM   (SELECT PK1, COUNT(*)  AS CNT  FROM   TSTBL

  4                  GROUP BY PK1)) B,

  5         (SELECT ROWNUM AS RNUM, PK1, CNT

  6          FROM   (SELECT PK1, COUNT(*)  AS CNT  FROM   TSTBL

  7                  GROUP BY PK1)) C

  8  WHERE  B.RNUM > C.RNUM ;

 

PK1  B_RNUM  C_RNUM     CNT

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

A02       2       1       5  <-- A01 값의 COUNT(*)  <-- A02

A03       3       1       5  <-- A01 값의 COUNT(*)  <-- A03

A03       3       2       3  <-- A02 값의 COUNT(*)

B01       4       1       5  <-- A01 값의 COUNT(*)  <-- B01

B01       4       2       3  <-- A02 값의 COUNT(*)

B01       4       3       1  <-- A03 값의 COUNT(*)

B02       5       1       5  <-- A01 값의 COUNT(*)  <-- B02

B02       5       2       3  <-- A02 값의 COUNT(*)

B02       5       3       1  <-- A03 값의 COUNT(*)

B02       5       4       8  <-- B01 값의 COUNT(*)

C01       6       1       5  <-- A01 값의 COUNT(*)  <-- C01

C01       6       2       3  <-- A02 값의 COUNT(*)

C01       6       3       1  <-- A03 값의 COUNT(*)

C01       6       4       8  <-- B01 값의 COUNT(*)

C01       6       5       4  <-- B02 값의 COUNT(*)

C02       7       1       5  <-- A01 값의 COUNT(*)  <-- C02

C02       7       2       3  <-- A02 값의 COUNT(*)

C02       7       3       1  <-- A03 값의 COUNT(*)

C02       7       4       8  <-- B01 값의 COUNT(*)

C02       7       5       4  <-- B02 값의 COUNT(*)

C02       7       6       2  <-- C01 값의 COUNT(*)

 

 

 

STEP 2 : 바로 이전 PK1 값까지의 누계 COUNT 값을 알기 위해 PK1 값과 B.RNUM 값으로 GROUP BY 한다.

 

SQL> SELECT B.PK1, B.RNUM, SUM(C.CNT) AS ACC_SUM

  2  FROM   (SELECT ROWNUM AS RNUM, PK1, CNT

  3          FROM   (SELECT PK1,COUNT(*) AS CNT  FROM   TSTBL

  4                  GROUP BY PK1)) B,

  5         (SELECT ROWNUM AS RNUM, PK1, CNT

  6          FROM   (SELECT PK1,COUNT(*) AS CNT  FROM   TSTBL

  7                  GROUP BY PK1)) C

  8  WHERE  B.RNUM > C.RNUM

  9  GROUP  BY  B.PK1, B.RNUM ;

 

PK1    RNUM ACC_SUM

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

A02       2       5

A03       3       8

B01       4       9

B02       5      17

C01       6      21

C02       7      23

 

6 rows selected.

 

 

 

 

 

 

 

 

 

 

 

 

STEP 3 : 바로 이전 PK1 값까지의 누계 값과 원래의 ROWNUM 이용하여 최종결과를 구한다.  이때 OUTER 조인을 사용해야만 "PK1 = A01" 값이 빠지지 않게 된다.

 

SQL> SELECT A.PK1, A.RNUM - NVL(ACC_SUM,0) AS SEQ, A.PK2, A.DUMMY

  2  FROM   (SELECT PK1,ROWNUM AS RNUM,PK2,DUMMY

  3          FROM   (SELECT PK1,PK2,DUMMY

  4                  FROM   TSTBL

  5                  GROUP BY PK1,PK2,DUMMY)) A,

  6         (SELECT B.PK1,B.RNUM,SUM(C.CNT) AS ACC_SUM

  7          FROM   (SELECT ROWNUM AS RNUM, PK1,CNT

  8                  FROM   (SELECT PK1,COUNT(*) AS CNT  FROM   TSTBL

  9                          GROUP BY PK1)) B,

 10                 (SELECT ROWNUM AS RNUM, PK1,CNT

 11                  FROM   (SELECT PK1,COUNT(*) AS CNT  FROM   TSTBL

 12                          GROUP BY PK1)) C

 13          WHERE  B.RNUM > C.RNUM

 14          GROUP BY B.PK1,B.RNUM) D

 15  WHERE  A.PK1 = D.PK1(+)

 16  ORDER  BY A.PK1, A.RNUM - NVL(ACC_SUM,0) ;

 

실행 결과는 다음과 같으며 SEQ 컬럼은 PK1 값이 바뀌면 일련번호를 다시 시작하는 것을 있다.

DUMMY 컬럼은 SEQ 값이 정확히 나왔는가를 검토하는 목적으로 추가된 것이고 실제의 테이블에는 없다.

또한 SQL 컬럼도 IN-LINE VIEW 내의 PSEUDO 컬럼인 ROWNUM 컬럼에서 나왔으므로 실제 테이블에는 없다.

 

 

 

 

 

 

 

 

 

PK1     SEQ PK2     DUMMY

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

A01       1 AS011       1

A01       2 AS012       2

A01       3 AS013       3

A01       4 AS014       4

A01       5 AS015       5

A02       1 AS021       1

A02       2 AS022       2

A02       3 AS023       3

A03       1 AS021       1

B01       1 BS011       1

B01       2 BS012       2

B01       3 BS013       3

B01       4 BS014       4

B01       5 BS015       5

B01       6 BS016       6

B01       7 BS017       7

B01       8 BS018       8

B02       1 BS021       1

B02       2 BS022       2

B02       3 BS023       3

B02       4 BS024       4

C01       1 CS011       1

C01       2 CS012       2

C02       1 CS021       1

C02       2 CS022       2

C02       3 CS023       3

C02       4 CS024       4

 

27 rows selected.

 

 

 

 

 

 


퀴즈 8.  불용 카드 수를 구하라.

 

1) 배경

 

카드 사에서 본인 카드와 그것의 가족 카드를 연관하여 일정 기간 사용되지 않은 카드 수를 계산한다.

다음과 같은 샘플 데이터로 단순화 시켜서 카드 수를 구한다.

 

카드정보 테이블

카드사용실적 테이블

카드번호

본인카드번호

카드번호

사용금액

101

NULL

101

      10,000

102

101

101

       5,500

103

101

105

      35,000

104

NULL

07

      70,000

105

NULL

 

 

106

NULL

 

 

107

104

 

 

108

104

 

 

109

NULL

 

 

110

NULL

 

 

 

2) 요구 내용

 

샘플 데이터로부터 보면 사용 실적이 없는 카드 번호의 단순 리스트는  102, 103, 104, 106, 108, 109, 110 모두 7 카드지만 여기서 문제는 102, 103 카드는 이것의 본인 카드인 101 사용 실적이 있으므로 실제적으로 답에서 빠져야 하며 104 카드도 역시 해당 가족 카드인 107 사용 실적이 있으므로 104, 108 역시 답에서 빠져야 하기 때문에 결과적으로 사용 실적이 없는 카드의 답은 106, 109, 110 모두 3개의 카드가 된다.

이를 구하는 쿼리를 작성하라.

 

 

 

 

3) 해답

 

  SELECT  카드번호 

    FROM  카드정보  A

   WHERE  NOT EXISTS  (SELECT X FROM 카드정보 B

                       WHERE  EXISTS ( SELECT X FROM 카드사용실적 C

                                        WHERE  C.카드번호 = B.카드번호)

                       START WITH  B.카드번호 = NVL(A.본인카드번호, A.카드번호)

                       CONNECT BY PRIOR  B.카드번호 = B.본인카드번호) ;

 

위에서 START WITH B.카드번호 = NVL(A.본인카드번호, A.카드번호) 절은 연관 카드를 찾아서 수행되도록 하여 요구 사항의 누락이 없도록 본인 카드로 부터 시작하여 카드 사용 실적을 찾도록 하기 위함이며 START WITH ~ CONNECT BY PRIOR 이책의 PART II 사례 23, 26번을 참조하여 이해를 구하거나 오라클 "Sql Language Reference Manual" Chapter 4 Commands SELECT 부분을 참조하기 바란다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

퀴즈 9.  판매일자, 판매량 별로 누적 판매량을 구하라.

 

1) 배경

 

다음과 같은 데이터가 들어있는 테이블이 있다.

 

SQL> DESC BFUN1;

 

 Name                            Null?    Type

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

 판매일자                        Not Null VARCHAR2(8)

 판매량                                   NUMBER

 

SQL> SELECT * FROM BFUN1;

 

판매일자  판매량

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

19970225      10

19970214      40

19970218      23

19970220      65

19970208      43

19970203      22

19970211      18

19970218      15

19970213      60

19970218      19

 

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

 

2) 요구 내용

 

위의 10 데이터를 판매일자를 기준으로 소트 하였을 그에 해당하는 판매량 까지 구분하여  판매량을 누적한 누적 판매량을 같이 표시하도록 하라.

, 다음과 같이 데이터가 나오도록 하라.

 

판매일자  판매량 누적판매량

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

19970203      22         22

19970208      43         65    <--- 22 + 43

19970211      18         83    <--- 65 + 18

   ...        ..         ..

19970218      15        198    <--- 판매량까지 구분한 누적판매량

19970218      19        217

19970218      23        240

   ...        ..         ..

 

3) 해답

 

STEP 1 : 우선 판매일자, 판매량으로 GROUP BY 결과를 구한다.

         GROUP BY 의해 소트는 자동적으로 수행된다.

 

SELECT A.판매일자, A.판매량, SUM(B.판매량) AS 누적판매량

FROM   BFUN1 A, BFUN1 B

WHERE  A.판매일자 >= B.판매일자

GROUP  BY A.판매일자, A.판매량 ;

 

판매일자  판매량 누적판매량

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

19970203      22         22

19970208      43         65   

19970211      18         83   

19970213      60        143

19970214      40        183

19970218      15        240     <---+

19970218      19        240         |  문제가 되는 부분

19970218      23        240     <---+ 

19970220      65        305

19970225      10        315

 

 

 

 

STEP 2 : 그러나 이렇게 하면 19960218 판매일자인 경우 누적 판매량이 최종 값인 240으로 같이 나오는 문제가 있다.

         문제를 해결하기 위해 판매일자가 동일할 경우 판매량으로 구분하여 누적 합계를 구하는 부분이 추가되어야 한다.

 

SELECT A.판매일자, A.판매량, SUM(B.판매량) AS 누적판매량

FROM   (SELECT 판매일자, 판매량 FROM  BFUN1 GROUP BY 판매일자, 판매량) A,

       (SELECT 판매일자, 판매량 FROM  BFUN1 GROUP BY 판매일자, 판매량) B,

WHERE  A.판매일자 > B.판매일자    <--- 판매일자까지만 구분하여 누적 분을 구함

   OR  (A.판매일자 = B.판매일자  AND  A.판매량 >= B.판매량)  <--- 판매량까지 구분함

GROUP  BY  A.판매일자, A.판매량;

 

"WHERE  A.판매일자 > B.판매일자" 조건은 판매일자 까지만 구분하여 누적 분을 구하므로 판매일자가 다른 경우는 조건만으로도 해결된다.

그러나, 동일한 판매일자일 경우 판매량 까지 구분하여 누적 합계를 구하고자 한다면 

"OR (A.판매일자 = B.판매일자 AND A.판매량 >= B.판매량)" 조건이 추가되어야 한다.

SQL 수행시키면 다음과 같은 결과를 얻는다.

 

판매일자  판매량 누적판매량

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

19970203      22         22

19970208      43         65   

19970211      18         83   

19970213      60        143

19970214      40        183

19970218      15        198   <--+ 

19970218      19        217      |  판매일자, 판매량 별로 누적 판매량이 계산됨

19970218      23        240   <--+

19970220      65        305

19970225      10        315

 

참고로 판매일자 까지만 구분하여 누적 합계를 구하는 SQL 다음과 같다.

 

 

 

SELECT A.판매일자, SUM(B.판매량) AS 누적판매량

FROM   (SELECT 판매일자 FROM  BFUN1 GROUP BY 판매일자) A,

       (SELECT 판매일자, SUM(판매량) AS 판매량 FROM BFUN1 GROUP BY 판매일자) B,

WHERE  A.판매일자 >= B.판매일자

GROUP  BY  A.판매일자 ;

 

IN-LINE VIEW 내에서는 ORDER BY 문을 사용할 없으므로 GROUP BY 사용하여 소트를 대신한 점을 여겨 볼만 하다.


퀴즈 10.  4/4 분기 동안 점포별  매출액을 구하라.

 

1) 배경

 

다음과 같은 관계의 테이블들이 있다.

l     인덱스 정보 (테이블명 : 인덱스명 : 컬럼구성)

 

점포 : PK_BRANCH             : BR_NO

고객 : PK_CUSTOMER           : CUST_NO

물품 : PK_PRODUCT            : PROD_NO

매출 : PK_SALE               : SALEDATE + CUST_NO + PROD_NO

매출 : SALE_CUST_NO_SALEDATE : CUST_NO + SALEDATE

매출 : SALE_PROD_NO_SALEDATE : PROD_NO + SALEDATE

 

테이블에 대한 상세한 속성은 다음과 같다.

 

 

 

 

테이블명

한글 컬럼명

영문 컬럼명

속성

인덱스

비고

점포

점포번호

BR_NO

VARCHAR(6)

PK

100

BRANCH

점포명

BR_NAME

VARCHAR2(10)

 

 

 

점포주소

BR_ADDR

VARCHAR2(30)

 

 

고객

고객번호

CUST_NO

VARCHAR2(6)

PK

1000

CUATOMER

고객명

CUST_NAME

VARCHAR2(10)

 

 

 

가산점

BONUS_POINT

NUMBER

 

 

 

신용한도

CREDIT_LIMIT

NUMBER

 

 

 

점포번호

BR_NO

VARCHAR2(6)

 

 

매출

매출일자

SALEDATE

VARCHAR2(8)

PK

180,000

SALE

고객번호

CUST_NO

VARCHAR2(6)

PK

 

 

물품번호

PROD_NO

VARCHAR2(6)

PK

 

 

판매량

SALE_AMT

NUMBER

 

 

 

매출구분

SALE_CLASS

VARCHAR2(1)

 

 

 

단가

UNIT_PRICE

NUMBER

 

 

물품

물품번호

PROD_NO

VARCHAR2(6)

PK

500

PRODUCT

물품명

PROD_NAME

VARCHAR2(4)

 

 

 

품종

PROD_CLASS

VARCHAR2(10)

 

 

 

단가

UNIT_PRICE

NUMBER

 

 

 

물품의 데이터 구성 내용은 식품류 80 %, 가구류 10 %, 기타 10 % 비율로 구성되어 있다.

 

2) 요구 내용

 

1995년도 4/4 분기 동안 점포별  매출액을 구하는 가장 효과적인 SQL 문을 작성하시오.

 

3) 해답

 

일반적인 SQL 문은 다음과 같다.

 

 

 

SELECT   A.BR_NAME,   SUM(C.SALE_AMT*C.UNIT_PRICE)  AS  SUM_SALE_AMT

FROM     BRANCH A,  CUSTOMER B,  SALE C

WHERE    A.BR_NO=B.BR_NO

  AND    B.CUST_NO=C.CUST_NO

  AND    C.SALEDATE    BETWEEN   '19951001'   AND   '19951231'

GROUP BY   A.BR_NAME ;                                             ELAPSED : 00:00:44.81

 

SQL 문을 좀더 개선 시켜서 튜닝하면 다음과 같다.

 

SELECT   B.BR_NAME, SUM(V.SUM_SALE_AMT)

FROM  ( SELECT CUST_NO,SUM(SALE_AMT*UNIT_PRICE) AS SUM_SALE_AMT

               FROM   SALE

               WHERE  SALEDATE BETWEEN '19951001' AND '19951231'

               GROUP BY CUST_NO ) V,

               CUSTOMER  A,  BRANCH  B

WHERE   B.BR_NO=A.BR_NO

 AND    A.CUST_NO=V.CUST_NO

GROUP BY B.BR_NAME ;                                               ELAPSED : 00:00:18.15

 

 

 

 

 

 


퀴즈 11.  특정 품종에 대한 판매수량과 금액을 구하라.

 

1) 배경

 

배경은 이전과 동일하다.

 

2) 요구 내용

 

1995 년도 1/4 분기 동안에 품종이 가구류와  가구류/식품류인 제품에 대한 판매 수량과 판매금액을 구하시오.

 

3) 해답

 

SELECT  SUM(SALE_AMT)  AS TOTAL_SALE_QTY,

        SUM(SALE_AMT * A.UNIT_PRICE)  AS TOTAL_SALE_AMT

FROM    PRODUCT A, SALE B

WHERE   A.PROD_NO = B.PROD_NO

  AND   A.PROD_CLASS  IN  ('FURNITURE', 'FOODS')

  AND   B.SALEDATE  BETWEEN  '19950101'  AND  '19950331' ;         ELAPSED : 00:00:30.30

 

SQL 문을 좀더 개선 시켜서 튜닝하면 다음과 같다.

 

SELECT  SUM(V.TOTAL_QTY)  AS TOTAL_SALE_QTY,

        SUM((V.TOTAL_QTY*A.UNIT_PRICE))  AS TOTAL_SALE_AMT

FROM    (SELECT  PROD_NO,  SUM(SALE_AMT) AS TOTAL_QTY

         FROM  SALE  WHERE  SALEDATE  BETWEEN  '19950101'  AND  '19950331'

         GROUP BY  PROD_NO) V,  PRODUCT A

WHERE   A.PROD_NO = V.PROD_NO

AND     A.PROD_CLASS  IN  ('FURNITURE' , 'FOODS') ;                ELAPSED : 00:00:16.48


퀴즈 12.  한번이라도 매출을 일으킨 고객을 구하라.

 

1) 배경

 

배경은 이전과 동일하다.

 

2) 요구 내용

 

1995년도에 한번이라도 매출을 일으킨 고객명 고객이 속한 점포명을 구하시오.

 

3) 해답

 

SELECT   DISTINCT  A.CUST_NAME,  B.BR_NAME

FROM     CUSTOMER  A,  BRANCH  B,  SALE  C

WHERE    A.BR_NO = B.BR_NO   AND  A.CUST_NO = C.CUST_NO

  AND    C.SALEDATE  LIKE  '1995%' ;                               ELAPSED : 00:02:46.46

 

SQL 문을 좀더 개선시켜서 튜닝하면 다음과 같다.

 

SELECT    A.CUST_NAME, B.BR_NAME

FROM      CUSTOMER A, BRANCH B

WHERE     A.BR_NO = B.BR_NO

  AND     A.CUST_NO IN (SELECT CUST_NO FROM  SALE 

                        WHERE  SALEDATE LIKE '1995%') ;            ELAPSED : 00:00:15.76

 

SQL 문을 좀더 개선 시켜서 튜닝하면 다음과 같다.

 

SELECT   A.CUST_NAME, BR_NAME

FROM     CUSTOMER  A,  BRANCH B

WHERE    EXISTS  (SELECT 'X' FROM  SALE  C

                  WHERE   A.CUST_NO = C.CUST_NO  AND   SALEDATE  LIKE '1995%')  

  AND    A.BR_NO = B.BR_NO ;                                       ELAPSED : 00:00:10.33


퀴즈 13.  매출액 20,000 이하 또는 없는 고객을 삭제하라.

 

1) 배경

 

배경은 이전과 동일하다.

 

2) 요구 내용

 

1995년도 매출액이 20,000 이하 이거나 한번도 매출이 없는 고객을 찾아서 고객 정보 테이블에서 삭제하라.

 

3) 해답

 

-- DELETE CUSTOMER A

SELECT  COUNT(CUST_NO) FROM CUSTOMER A   <--- 삭제 시에는 이 줄을 지우고 위 줄을 살린다.

WHERE  A.CUST_NO IN (SELECT CUST_NO FROM SALE

                     WHERE  SALEDATE LIKE '1995%'

                     GROUP BY CUST_NO HAVING SUM(SALE_AMT*UNIT_PRICE) < 200000)

OR     NOT EXISTS (SELECT 'X' FROM SALE C  WHERE  C.CUST_NO = A.CUST_NO

                      AND C.SALEDATE LIKE '1995%') ;               ELAPSED : 00:45:15.43

 

SQL 문을 좀더 개선 시켜서 튜닝하면 다음과 같다.

 

-- DELETE CUSTOMER A

SELECT COUNT(CUST_NO) FROM CUSTOMER A   <--- 삭제 시에는 이 줄을 지우고 위 줄을 살린다.

WHERE  A.CUST_NO IN (SELECT CUST_NO FROM SALE

                     WHERE  SALEDATE LIKE '1995%'

                      AND  A.CUST_NO = C.CUST_NO  <--- 인덱스 사용을 위해 반드시 추가

                      GROUP BY CUST_NO HAVING SUM(SALE_AMT*UNIT_PRICE) < 200000)

OR     NOT EXISTS (SELECT  'X' FROM SALE C  WHERE  C.CUST_NO = A.CUST_NO

                      AND  C.SALEDATE LIKE '199501%') ;            ELAPSED : 00:01:50.33

 


퀴즈 14.  가산점과 신용 한도를 증가시켜라.

 

1) 배경

 

배경은 이전과 동일하다.

 

2) 요구 내용

 

1995년도 하반기 매출액이 300,000 이상인 고객에 대하여 가산점을 매출액 100,000 원당 1 점씩을 증가시키고 신용 한도를 100,000 증가시키는 SQL 문을 작성하라.

 

3) 해답

 

UPDATE CUSTOMER C

SET    CREDIT_LIMIT = CREDIT_LIMIT + 100000,

       BONUS_POINT = (SELECT BONUS_POINT+TRUNC(SUM(SALE_AMT*B.UNIT_PRICE)/100000)

                      FROM   SALE A, PRODUCT B

                      WHERE  SALEDATE BETWEEN '19950701' AND '19951231'

                        AND  A.PROD_NO = B.PROD_NO

                        AND  A.CUST_NO = C.CUST_NO)

WHERE  C.CUST_NO IN (SELECT CUST_NO

                     FROM   SALE A, PRODUCT B

                     WHERE  SALEDATE BETWEEN '19950701' AND '19951231'

                       AND  A.PROD_NO = B.PROD_NO  AND A.CUST_NO = C.CUST_NO

                     GROUP BY A.CUST_NO HAVING SUM(A.SALE_AMT*B.UNIT_PRICE) > 300000) ;

                                                                   ELAPSED : 00:01:46.67

 

SQL 문과 동일한 기능을 갖도록 PL/SQL 작성하면 다음과 같다.

 

 

 

 

 

PROMPT 'Create Procedure PEX4_3'

 

CREATE OR REPLACE PROCEDURE PEX4_3

AS

BEGIN

  DECLARE

    CURSOR EX4_3 IS

      SELECT CUST_NO FROM CUSTOMER;

    CUSTNO  CHAR(6);

    AMOUNT  NUMBER;

  BEGIN

    OPEN EX4_3;

    LOOP

      FETCH EX4_3 INTO CUSTNO;

      EXIT WHEN EX4_3%NOTFOUND;

      SELECT SUM(SALE_AMT*B.UNIT_PRICE)

      INTO   AMOUNT

      FROM   SALE A, PRODUCT B

      WHERE  SALEDATE BETWEEN '19950701' AND '19951231'

      AND    A.PROD_NO = B.PROD_NO

      AND    A.CUST_NO = CUSTNO;

      IF AMOUNT > 300000 THEN

        UPDATE CUSTOMER

        SET    CREDIT_LIMIT = CREDIT_LIMIT + 100000,

               BONUS_POINT = BONUS_POINT+TRUNC(AMOUNT/100000)

        WHERE  CUST_NO = CUSTNO;

      END IF;

    END LOOP;

    CLOSE EX4_3;

  END;

END;

/                                                                  ELAPSED : 00:01:08.79

 

 

 


퀴즈 15.  매출 순위 10위까지의 고객 리스트를 작성하라.

 

1) 배경

 

배경은 이전과 동일하다.

 

2) 요구 내용

 

1995 년도 4/4 분기 매출 순위 10 까지 고객 리스트를 작성하여 순위와 매출액을 함께 나타내는 SQL 문을 작성하라.

, 이때에 판매구분이 'R' 고객은 우수 고객이므로 판매금액을 10 % 증가시켜서 계산하라.

 

3) 해답

 

STEP 1 :  우선 판매금액별로 순위를 정하는 SQL 문을 다음과 같이 작성한다.

 

SELECT CUST_NO,

       SUM(DECODE(SALE_CLASS,'R',SALE_AMT*UNIT_PRICE*1.1,

                                 SALE_AMT*UNIT_PRICE)) AS SUM_SALE_AMT

FROM   SALE

WHERE  SALEDATE BETWEEN '19951001' AND '19951231'

GROUP  BY CUST_NO

ORDER  BY SUM_SALE_AMT DESC ;

 

STEP 2 :  IN-LINE VIE에서는ORDER BY 문을 사용할 없기 때문에 GROUP BY 사용하여 소트 기능을 대신한다.

 

최종 SQL 문은 다음과 같다.

 

 

 

 

SELECT ROWNUM AS ORDER_SALE,

       CUST_NO,-SUM_SALE_AMT AS SUM_SALE_AMT

FROM   (SELECT CUST_NO, -SUM_SALE_AMT AS SUM_SALE_AMT

          FROM   (SELECT CUST_NO,

                        SUM(DECODE(SALE_CLASS,'R',SALE_AMT*UNIT_PRICE*1.1,

                                                        SALE_AMT*UNIT_PRICE)) AS SUM_SALE_AMT

                        FROM   SALE

                        WHERE  SALEDATE BETWEEN '19951001' AND '19951231'

                        GROUP  BY CUST_NO)

          GROUP BY -SUM_SALE_AMT, CUST_NO)

WHERE  ROWNUM <= 10 ;

 

 


퀴즈 16.  일별 총계 제품별 총계를 같이 구하라.

 

1) 배경

 

배경은 이전과 동일하다.

 

2) 요구 내용

 

1995년도 3/4 분기 제품별 매출액을 일별 총계 제품별 총계를 하나의 SQL 문으로 같이 구하라.

 

3) 해답

 

SELECT V.SALEDATE, V.PROD_NO,

       DECODE(V.PROD_NO,'P00000','일별 총계',A.PROD_NAME) AS PROD_NAME,

       V.TOTAL_SALE_AMT

FROM   PRODUCT A,                                 

       (SELECT SALEDATE,'P00000' AS PROD_NO,                <--- 일별 총계           

               SUM(SALE_AMT*UNIT_PRICE) AS TOTAL_SALE_AMT

        FROM   SALE

        WHERE  SALEDATE BETWEEN '19950701' AND '19950930'

        GROUP BY SALEDATE

        UNION ALL

        SELECT SALEDATE,PROD_NO,                            <--- 일별, 제품별 총계

               SUM(SALE_AMT*UNIT_PRICE) AS TOTAL_SALE_AMT

        FROM   SALE

        WHERE  SALEDATE BETWEEN '19950701' AND '19950930'

        GROUP BY  SALEDATE, PROD_NO) V

WHERE   V.PROD_NO = A.PROD_NO(+)   <--- 'P00000' 때문에 반드시 OUTER 조인을 사용해야 함

ORDER  BY  V.SALEDATE, V.PROD_NO ;                                ELAPSED : 00:00:38.61

 

SQL 문을 좀더 개선 시켜서 튜닝하면 다음과 같다.

 

REPLICA 값이 1 이면 일자별 총계 이고 2 이면 일자별, 제품별 총계이다.

 

SELECT  V2.SALEDATE,   V2.PROD_NO,

        DECODE(V2.PROD_NO,'P00000','일별 총계',A.PROD_NAME) AS PROD_NAME,

        V2.TOTAL_SALE_AMT

FROM    PRODUCT A,

        (SELECT  V1.SALEDATE,

                 DECODE(REPLICA,'1','P00000',V1.PROD_NO)  AS PROD_NO,

                 SUM(V1.TOTAL_SALE_AMT)  AS TOTAL_SALE_AMT

         FROM    (SELECT SALEDATE, PROD_NO, SUM(SALE_AMT*UNIT_PRICE) AS TOTAL_SALE_AMT

                  FROM   SALE

                  WHERE  SALEDATE BETWEEN '19950701' AND '19950930'

                  GROUP BY SALEDATE,PROD_NO ) V1, REPLICA1

         GROUP  BY  V1.SALEDATE, DECODE(REPLICA,'1','P00000',V1.PROD_NO) ) V2

WHERE   V2.PROD_NO = A.PROD_NO(+) ;                              ELAPSED: 00: 00 : 21.30

 

l         참고로 SQL IN-LINE VIEW 안에 있는 REPLICA1테이블은 복제를 위해서 사용되는 테이블로 다음과 2로우를 갖고 있다.

 

CREATE TABLE REPLICA1  ( REPLICA   CHAR(1) );

INSERT INTO REPLICA1 VALUES('1');

INSERT INTO REPLICA1 VALUES('2');

COMMIT;

 

 


퀴즈 17.  입력 구분에 따라 서로 다른 총매출액을 구하라.

 

1) 배경

 

배경은 이전과 동일하다.

 

2) 요구 내용

 

1995년도 제품 번호가 P20081 해당하는 매출액을 구하는데 입력 구분의 값이 1 이면 P20081 대해서만 구하고 2 이면 P20081 품종에 해당하는 모든 제품에 대해서 매출액을 구하라.

 

3) 해답

 

WHERE 조건에 DECODE 함수를 활용하여 다음과 같이 작성할 있다.

 

SELECT   SUM(SALE_AMT*B.UNIT_PRICE)

FROM     SALE A, PRODUCT B

WHERE    SALEDATE LIKE '1995%'

AND      A.PROD_NO = B.PROD_NO

AND      DECODE ('&입력구분', '1', B.PROD_NO, '2', 'P20081') = 'P20081'

AND      B.PROD_CLASS IN (SELECT PROD_CLASS FROM PRODUCT WHERE PROD_NO = 'P20081') ;

 

 

 

 

 

 

 

 

 


퀴즈 18.  년간 매출을 단위로 매출 구분하여 구하라

 

1) 배경

 

배경은 이전과 동일하다.

 

2) 요구 내용

 

1995년도 매출액을 주간 단위로 구하는데 보는 컬럼은 판매월, 주일련번호, 현금매출액, 신용매출액, 주별합계 이고 주를 계산하는데 2 가지 형태로 구한다.

 

CASE 1 1995 1 1 수요일이면 구분을 수요일부터 다음주 화요일을 1주로하여 보는 것이고,

CASE 2 1995 첫째 주는 11()부터 1 5()이고 다음부터는 월요일부터 일요일을 단위로 계산한다.

 

3) 해답

 

DATE 타입의 'IW', 'WW' 이용하여 구한다.

 

l     CASE 1

 

  SELECT SUBSTR(매출일자,1,6)                        AS 판매월,

         TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'WW')  AS 주일련번호,

         SUM(DECODE(매출구분,'현금',판매량*단가))    AS 현금매출액,

         SUM(DECODE(매출구분,'신용',판매가*단가))    AS 신용매출액,

         SUM(판매량*단가)                            AS 주별합계

  FROM   매출

  WHERE  매출일자  LIKE  '1995%'

  GROUP BY  SUBSTR(매출일자,1,6), TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'WW')

 


l     CASE 2

 

  SELECT SUBSTR(매출일자,1,6)                        as 판매월,

         TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'IW')  as 주일련번호,

         SUM(DECODE(매출구분,'현금',판매량*단가))    as 현금매출액,

         SUM(DECODE(매출구분,'신용',판매가*단가))    as 신용매출액,

         SUM(판매량*단가)                            as 주별합계

  FROM   매출

  WHERE  매출일자  LIKE  '1995%'

  GROUP BY  SUBSTR(매출일자,1,6), TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'WW')

 

CASE 1TO_CHAR(TO_DATE(매출일자,'YYYYMMDD') , 'WW')

CASE 2TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'), 'IW') 주의해서 보기를 바란다. 그런데 CASE 2 다음의 주의를 요한다.

1년은 53 인데 만약, 1995 12 31일이 일요일이 아닐 경우, 예를 들어 금요일 이었다면 마지막 일요일 (12 26)까지는 52번째 주가 되고 12 27() 부터 다음 해인 199612() 53주째가 아닌 첫째 ('01') 다시 계산하여 53번째 주가 나오지 않게 된다.

따라서, 다음과 같이 DECODE 이용하여 매출년월(SUBSTR(매출일자,1,6)) '199512'이고,'IW' '01'이면 이는 '53'으로 값을 주어 정확한 결과가 나오도록 쿼리를 수정해야 한다. 참고로 경우는 GROUP BY 절에 매출월이 들어가 있어서 금액은 틀리지 않으나 만약에 GROUP BY 주일련번호만으로 하였다면 1227일부터 12 31일까지의 합계가 첫째 ('01') 같이 합쳐져서 틀린 결과가 나오게 된다.

이를 반영하여 정확한 값이 나오도록 하기 위해서는 다음과 같이 SQL 수정되어야 한다.

 

 

 

 

 

 

 

 

 

l     수정 CASE 2

 

  SELECT SUBSTR(매출일자,1,6)                                  AS 판매월,

         DECODE(SUBSTR(매출일자,1,6),'199512',

           DECODE(TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'IW'),'01','53',

                  TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'IW')),

           TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'IW'))  AS 주일련번호,

         SUM(DECODE(매출구분,'현금',판매량*단가))              AS 현금매출액,

         SUM(DECODE(매출구분,'신용',판매가*단가))              AS 신용매출액,

         SUM(판매량*단가)                                      AS 주별합계

  FROM   매출

  WHERE  매출일자  LIKE  '1995%'

  GROUP BY  SUBSTR(매출일자,1,6),

            DECODE(SUBSTR(매출일자,1,6),'199512',

              DECODE(TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'IW'),'01','53',

                     TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'IW')),

              TO_CHAR(TO_DATE(매출일자,'YYYYMMDD'),'IW')) ;

 


찾아보기

PART III

 

퀴즈 1.  중복된 로우만 골라서 삭제하라..........................................................................................................

위의 데이터 EMPNO 컬럼의 값이 중복된 로우에 대하여 ROWID 값이 로우만 골라서 삭제하라.       

퀴즈 2.  정확히 10위까지만 구하라......................................................................................................................

위의 데이터를 이용하여 순위를 나타내는 컬럼과 함께 10 까지 나오는 SQL문과 (위의 샘플 데이터 경우 WARD, 11 번째 로우) 정확히 10번째 로우까지만 나오는 (위의 샘플 데이터 경우 MARTIN, 10 번째 로우) SQL문을 작성하라.               

퀴즈 3.  공수별 비용 집계를 구하라..................................................................................................................

왼편의 표와 같이 모든 공수의 종류를 구하고 공수별로 들어간 비용의 합계를 구하라. 

퀴즈 4.  제품별 집계와 등급별 집계를 같이 구하라...............................................................................

위와 같이 판매일자와 함께 각각의 제품에 대한 판매 내용이 있을 전체 판매에 대한 수량과 매출액 등의 통계자료를 보고싶은 것이다.

, 통계자료를 제품명이 HP 제품은 저가여서 제품별로 봐도 되니까 모델 별은 무시하고 제품별로 수량과 매출액 합계를 내고, 제품명이 LD 제품은 고가이므로 모델별로 수량과 매출액 합계를 내고, 제품명이 PP 제품은 저가 단품들 이므로 모든 모델을 하나로 몰아서 기타로 하여 수량과 매출액에 대한 합계를 구하라.

퀴즈 5.  당일 , 일주일 , 한달 분을 같이 구하라.............................................................................

테이블을 이용하여 당일분 합계, 최근 일주일분 합계, 가장 최근의 최고 판매량분, 월간 합계를 같이 구하라.    

퀴즈 6.  테이블의 값을 가로로 표시하라..................................................................................

위와 같은 결과가 다음과 같이 테이블의 값을 가로로 표시하라. 

퀴즈 7.  값이 바뀔 마다 일련번호를 다시 시작하라........................................................................

위의 데이터 PK1, PK2 컬럼만 사용하여 PK1 컬럼의 값이 바뀔 마다 일련번호가 1 부터 새롭게 시작되도록 하라.             

퀴즈 8.  불용 카드 수를 구하라............................................................................................................................

카드 사에서 본인 카드와 그것의 가족 카드를 연관하여 일정 기간 사용되지 않은 카드 수를 계산한다.       

퀴즈 9.  판매일자, 판매량 별로 누적 판매량을 구하라.........................................................................

위의 10 데이터를 판매일자를 기준으로 소트 하였을 그에 해당하는 판매량 까지 구분하여  판매량을 누적한 누적 판매량을 같이 표시하도록 하라.        

퀴즈 10.  4/4 분기 동안 점포별  매출액을 구하라...................................................................................

1995년도 4/4 분기 동안 점포별  매출액을 구하는 가장 효과적인 SQL 문을 작성하시오.       

퀴즈 11.  특정 품종에 대한 판매수량과 금액을 구하라................................................................

1995 년도 1/4 분기 동안에 품종이 가구류와  가구류/식품류인 제품에 대한 판매 수량과 판매금액을 구하시오.

퀴즈 12.  한번이라도 매출을 일으킨 고객을 구하라...............................................................................

1995년도에 한번이라도 매출을 일으킨 고객명 고객이 속한 점포명을 구하시오.          

퀴즈 13.  매출액 20,000 이하 또는 없는 고객을 삭제하라...................................................................

1995년도 매출액이 20,000 이하 이거나 한번도 매출이 없는 고객을 찾아서 고객 정보 테이블에서 삭제하라.                

퀴즈 14.  가산점과 신용 한도를 증가시켜라................................................................................................

1995년도 하반기 매출액이 300,000 이상인 고객에 대하여 가산점을 매출액 100,000 원당 1 점씩을 증가시키고 신용 한도를 100,000 증가시키는 SQL 문을 작성하라.   

퀴즈 15.  매출 순위 10위까지의 고객 리스트를 작성하라..................................................................

1995 년도 4/4 분기 매출 순위 10 까지 고객 리스트를 작성하여 순위와 매출액을 함께 나타내는 SQL 문을 작성하라. 

, 이때에 판매구분이 'R' 고객은 우수 고객이므로 판매금액을 10 % 증가시켜서 계산하라.     

퀴즈 16.  일별 총계 제품별 총계를 같이 구하라...............................................................................

1995년도 3/4 분기 제품별 매출액을 일별 총계 제품별 총계를 하나의 SQL 문으로 같이 구하라.  

퀴즈 17.  입력 구분에 따라 서로 다른 총매출액을 구하라................................................................

1995년도 제품 번호가 P20081 해당하는 매출액을 구하는데 입력 구분의 값이 1 이면 P20081 대해서만 구하고 2 이면 P20081 품종에 해당하는 모든 제품에 대해서 매출액을 구하라.       

퀴즈 18.  년간 매출을 단위로 매출 구분하여 구하라.....................................................................

1995년도 매출액을 주간 단위로 구하는데 보는 컬럼은 판매월, 주일련번호, 현금매출액, 신용매출액, 주별합계 이고 주를 계산하는데 2 가지 형태로 구한다.


관련글 더보기