by-nc-sa     개발자, DBA가 함께 만들어가는 구루비 지식창고!

제4절 고급 조인 기법




1. 인라인 뷰 활용

  • 대부분 조인은 1:M 관계인 테이블끼리의 조인이고 조인 결과는 M쪽 집합과 같은 단위가 되는데,
    이를 다시 1쪽 집합 단위로 그룹핑해야 한다면 M쪽 집합을 먼저 1쪽 단위로 그룹핑하고 나서 조인하는 것이 조인 횟수를 줄여주기 때문에 유리하다.
    그런 처리를 위해 인라인 뷰를 사용할 수 있다. 2009년도 상품별 판매수량과 판매금액을 집계하는 아래 쿼리를 예로 들어보자.
인라인 뷰 적용 전
SELECT min(t2.상품명) 상품명,
       sum(t1.판매수량) 판매수량,
       sum(t1.판매금액) 판매금액
  FROM 일별상품판매 t1,
       상품 t2
 WHERE t1.판매일자 BETWEEN '20090101' AND '20091231'
       AND t1.상품코드 = t2.상품코드
 GROUP BY t2.상품코드

Call      Count    CPU Time     Elapsed Time    Disk    Query    Current    Rows
----       ----     -------        ---------    ----     ----       ----    ----
Parse         1       0.000            0.000       0        0          0       0
Execute       1       0.000            0.000       0        0          0       0
Fetch       101       5.109           13.805   52744   782160          0    1000
----       ----     -------        ---------    ----     ----       ----    ----
Total       103       5.109           13.805   52744   782160          0    1000

  Rows Row Source Operation
 ----- ---------------------------------------------------
  1000 SORT GROUP BY (cr=782160 pr=52744 pw=0 time=13804391 us)
365000   NESTED LOOPS (cr=782160 pr=52744 pw=0 time=2734163731004 us)
365000     TABLE ACCESS FULL 일별상품판매 (cr=52158 pr=51800 pw=0 time=456175026878 us)
365000     TABLE ACCESS BY INDEX ROWID 상품 (cr=730002 pr=944 pw=0 time=872397482545 us)
365000       INDEX UNIQUE SCAN 상품_PK (cr=365002 pr=4 pw=0 time=416615350685 us)

Row Source Operation을 분석해 보면, 일별상품판매 테이블로부터 읽힌 365,000개 레코드마다 상품 테이블과 조인을 시도했다.
조인 과정에서 730,002개의 블록 I/O가 발생했고, 총 소요시간은 13.8초다.
아래 처럼 상품코드별로 먼저 집계하고서 조인하도록 바꾸고 다시 수행해 보자.

인라인 뷰 적용 후
SELECT t2.상품명,
       t1.판매수량,
       t1.판매금액
  FROM
       (SELECT 상품코드,
              sum(판매수량) 판매수량,
              sum(판매금액) 판매금액
         FROM 일별상품판매
        WHERE 판매일자 BETWEEN '20090101' AND '20091231'
        GROUP BY 상품코드
       ) t1,
       상품 t2
 WHERE t1.상품코드 = t2.상품코드

Call      Count    CPU Time     Elapsed Time    Disk    Query    Current    Rows
----       ----     -------        ---------    ----     ----       ----    ----
Parse         1       0.000            0.000       0        0          0       0
Execute       1       0.000            0.000       0        0          0       0
Fetch       101       1.422            5.540   51339    54259          0    1000
----       ----     -------        ---------    ----     ----       ----    ----
Total       103       1.422            5.540   51339    54259          0    1000

  Rows Row Source Operation
 ----- ---------------------------------------------------
  1000 NESTED LOOPS (cr=54259 pr=51339 pw=0 time=5540320 us)
  1000   VIEW (cr=52158 pr=51339 pw=0 time=5531294 us)
  1000   SORT GROUP BY (cr=52158 pr=51339 pw=0 time=5530293 us)
365000     TABLE ACCESS FULL 일별상품판매 (cr=52158 pr=51339 pw=0 time=2920041 us)
  1000   TABLE ACCESS BY INDEX ROWID 상품 (cr=2101 pr=0 pw=0 time=8337 us)
  1000     INDEX UNIQUE SCAN 상품_PK (cr=1101 pr=0 pw=0 time=3747 us)

상품코드별로 먼저 집계한 결과건수가 1,000건이므로 상품 테이블과 조인도 1,000번만 발생했다.
조인 과정에서 발생한 블록 I/O는 2,101개에 불과하고 수행시간도 5.5초 밖에 걸리지 않았다.

2. 배타적 관계의 조인

엔터티가 두 개 이상의 다른 엔터티의 합집합과 관계(Relationship)를 갖는 것을 '상호배타적(Exclusive OR) 관계'라고 한다.
<그림 Ⅲ-4-31>에서 작업지시 테이블과 개통신청 및 장애접수 테이블과의 관계가 여기에 해당하며, 관계선에 표시된 아크(Arc)를 확인하기 바란다.

※ Exclusive OR

  • 배타적 논리합
  • Bool 연산자의 하나이며 2개의 피연산자 중 하나가 참이고 다른 하나가 거짓일 때에만 연산의 결과가 참이 된다.
    'A XOR B'는 'A∀B' 등으로 표기하기도 한다.
  • 출처 : IT용어사전, 두산동아, 한국정보통신기술협회

1. ERD 업무 설명

  • 고객으로부터 개통이나 장애처리 요청을 받으면 작업기사에게 작업지시서를 발행
  • 한 작업자에게만 작업지시를 내릴 때가 많지만, 작업 내용에 따라서는 여러 작업자가 필요할 수도 있다.
  • 또한, 여러 작업자가 동시에 출동하는가 하면, 시간 간격을 두고 따로 출동하기도 한다.
  • ERD에 다 표현하진 않았지만 개통신청과 장애접수는 관리하는 속성이 상당히 달라 별도의 테이블로 설계했다.
  • 반면, 작업지시는 개통신청이든 장애접수든 거의 같은 속성을 관리하므로 한 테이블로 설계했다.
  • 한 테이블로 통합하더라도 개통신청이나 장애접수 중 어느 것과 관계를 갖는지 구분할 수 있어야 한다.
  • [그림 Ⅲ-4-31]과 같은 데이터 모델을 실제 데이터베이스로 구현할 때, 작업지시 테이블에는 아래 두 가지 방법 중 하나를 사용한다.

① 개통신청번호, 장애접수번호 두 칼럼을 따로 두고, 레코드별로 둘 중 하나의 칼럼에만 값을 입력한다.
② 작업구분과 접수번호 칼럼을 두고, 작업구분이 '1'일 때는 개통신청번호를 입력하고 '2'일 때는 장애접수번호를 입력한다.

①번처럼 설계할 때는 아래와 같이 Outer 조인으로 간단하게 쿼리를 작성할 수 있다.

Oracle SQL Server
SELECT
       /*+ ordered use_nl(b) use_nl(c) */
       a.작업일련번호,
       a.작업자ID,
       a.작업상태코드 ,
       nvl(b.고객번호, c.고객번호) 고객번호 ,
       nvl(b.주소, c.주소) 주소,
       ......
  FROM 작업지시 a,
       개통신청 b,
       장애접수 c
 WHERE a.방문예정일시 BETWEEN :방문예정일시1 AND :방문예정일시2
       AND b.개통신청번호(+) = a.개통신청번호
       AND c.장애접수번호(+) = a.장애접수번호
SELECT a.작업일련번호,
       a.작업자ID,
       a.작업상태코드 ,
       isnull(b.고객번호, c.고객번호) 고객번호 ,
       isnull(b.주소, c.주소) 주소,
       ......
  FROM 작업지시 a
   LEFT OUTER JOIN 개통신청 b ON b.개통신청번호 = a.개통신청번호
   LEFT OUTER JOIN 장애접수 c ON c.장애접수번호 = a.장애접수번호
 WHERE a.방문예정일시 BETWEEN :방문예정일시1 AND :방문예정일시2
OPTION(FORCE ORDER, loop JOIN)

②번처럼 설계했을 경우는 UNION ALL을 이용한다.

Oracle
SELECT x.작업일련번호,
       x.작업자ID,
       x.작업상태코드,
       y.고객번호,
       y.주소,
       ......
  FROM 작업지시 x, 개통신청 y
 WHERE x.방문예정일시 BETWEEN :방문예정일시1 AND :방문예정일시2
   AND x.작업구분 = '1'
   AND y.개통신청번호 = x.접수번호
UNION ALL
SELECT x.작업일련번호,
       x.작업자ID,
       x.작업상태코드,
       y.고객번호,
       y.주소,
       ......
  FROM 작업지시 x, 장애접수 y
 WHERE x.방문예정일시 BETWEEN :방문예정일시1 AND :방문예정일시2
   AND x.작업구분 = '2'
   AND y.장애접수번호 = x.접수번호
  • <작업구분+방문예정일시> 결합 인덱스 이용 시 중복 읽는 범위는 없음.
  • <방문예정일시+작업구분> 결합 인덱스 이용 시 인덱스 스캔 범위 중복 발생.
  • <방문예정일시> 인덱스 이용 시 작업 구문을 필터링하기 위한 테이블 RANDOM ACCESS까지 중복 발생.
    이럴 경우 아래와 같이 수정함으로써 중복 액세스에 의한 비효율 해소
Oracle SQL Server
SELECT
       /*+ ordered use_nl(b) use_nl(c) */
       a.작업일련번호,
       a.작업자ID,
       a.작업상태코드 ,
       nvl(b.고객번호, c.고객번호) 고객번호 ,
       nvl(b.주소, c.주소) 주소,
       ......
  FROM 작업지시 a,
       개통신청 b,
       장애접수 c
 WHERE a.방문예정일시 BETWEEN :방문예정일시1 AND :방문예정일시2
   AND b.개통신청번호(+) = decode(a.작업구분, '1', a.접수번호)
   AND c.장애접수번호(+) = decode(a.작업구분, '2', a.접수번호)
SELECT a.작업일련번호, a.작업자ID, a.작업상태코드 ,
       isnull(b.고객번호, c.고객번호) 고객번호 ,
       isnull(b.주소, c.주소) 주소,
       ......
  FROM 작업지시 a
   LEFT OUTER JOIN 개통신청 b
       ON b.개통신청번호 =
       (
           CASE WHEN a.작업구분 = '1' THEN a.접수번호 END
       )
   LEFT OUTER JOIN 장애접수 c
       ON c.장애접수번호 =
       (
           CASE WHEN a.작업구분 = '2' THEN a.접수번호 END
       )
 WHERE a.방문예정일시 BETWEEN :방문예정일시1 AND :방문예정일시2
OPTION(FORCE ORDER, loop JOIN)

3. 부등호 조인

'=' 연산자 조인 외에 BETWEEN, LIKE, 부등호 같은 연산자로 조인 가능.

아래 형태의 테이블에서 우측과 같은 형태의 누적매출을 구해보자.
각 지점별로 판매월과 함께 증가하는 누적매출(running total)을 구하려는 것이다.

윈도우 함수(Oracle에서는 분석 함수(Analytic Function)라고 함)를 이용하면 아래와 같이 간단히 원하는 결과를 얻을 수 있다.

SELECT 지점,
       판매월,
       매출 ,
       sum(매출) over (partition BY 지점 ORDER BY 판매월 range BETWEEN unbounded preceding AND CURRENT ROW) 누적매출
  FROM 월별지점매출

윈도우 함수가 지원되지 않는 DBMS를 사용하고 있다면 아래와 같이 부등호 조인을 이용해 같은 결과를 얻을 수 있다

SELECT t1.지점, t1.판매월, min(t1.매출) 매출, sum(t2.매출) 누적매출
  FROM 월별지점매출 t1, 월별지점매출 t2
 WHERE t2.지점 = t1.지점
   AND t2.판매월 <= t1.판매월
 GROUP BY t1.지점, t1.판매월
ORDER BY t1.지점, t1.판매월;

4. Between 조인

가. 선분이력이란?
  • 고객별연체금액 변경이력을 관리할 때 이력의 시작시점만을 관리하는 것을 '점이력' 모델이라함.
  • 시작시점과 종료시점을 함께 관리하는 것을 '선분이력' 모델이라고 함.
  • 선분이력 모델에서 가장 마지막 이력의 종료일자는 항상 '99991231'(시간까지 관리할 때는 '99991231235959')로 입력해 두어야 함.

<그림 Ⅲ-4-33>은 <표 Ⅲ-4-3>에 있는 123번 고객에 대한 3개의 선분이력 레코드를 일직선 상에 펼쳐서 그려본 것이다.

  • 이력을 선분형태로 관리하면 쿼리가 간단해지는 장점이 있음.
    EX)123번 고객의 2004년 8월 15일 시점 이력을 조회하고자 할 때 아래 처럼 between 조인을 이용해 간편하게 조회할 수 있다.
SELECT 고객번호, 
       연체금액, 
       연체개월수 
  FROM 고객별연체금액 
 WHERE 고객번호 = '123' 
       AND '20040815' BETWEEN b.시작일자 AND b.종료일자;

데이터를 점이력으로 관리할 때는 아래 처럼 서브쿼리를 이용해 복잡하게 쿼리를 해야한다.

SELECT 고객번호, 
       연체금액, 
       연체개월수 
  FROM 고객별연체금액 a 
 WHERE 고객번호 = '123' 
   AND 연체변경일자 = 
       (SELECT max(연체변경일자) 
          FROM 고객별연체금액 
          WHERE 고객번호 = a.고객번호 
            AND 변경일자 <= '20040815'
       ) ;
  • 위와 같은 설계 시 PK를 일반적으로 <마스터 키 + 종료일자 + 시작일자> 순으로 결합 인덱스 구성
  • 이 경우 이력을 변경 시마다 PK 값을 변경하여 RDBMS 설계 사상에 맞지 않음
  • 개체 무결성을 완벽히 보장하기 어렵다는 것이 가장 큰 단점이다.
나. 선분이력 기본 조회 패턴

기본적인 패턴으로 과거, 현재, 미래 임의 시점을 모두 조회할 수 있게 하려면 아래 처럼 쿼리를 작성하면 된다.

SELECT 연체개월수, 
       연체금액 
  FROM 고객별연체금액 
 WHERE 고객번호 = :cust_num 
   AND :dt BETWEEN 시작일자 AND 종료일자

현시점을 조회 시 '99991231' 상수 조건을 이용해 아래와 같이 '=' 조건으로 검색하는 것이 성능상 유리

SELECT 연체개월수, 
       연체금액 
  FROM 고객별연체금액 
 WHERE 고객번호 = :cust_num 
   AND 종료일자 = '99991231'
다. 선분이력 조인

1) 과거/현재/미래의 임의 시점 조회

고객과 두 선분이력 테이블을 조인해서 2004년8월 15일 시점 데이터를 조회할 때는 아래와 같이 쿼리하면 된다.
물론 :dt 변수에는 '20040815'(시작일자, 종료일자가 문자열 칼럼일 때)를 입력한다.

SELECT c.고객번호, 
       c.고객명, 
       c1.고객등급, 
       c2.전화번호 
  FROM 고객 c, 
       고객등급변경이력 c1, 
       전화번호변경이력 c2 
 WHERE c.고객번호 = :cust_num 
       AND c1.고객번호 = c.고객번호 
       AND c2.고객번호 = c.고객번호 
       AND :dt BETWEEN c1.시작일자 AND c1.종료일자 
       AND :dt BETWEEN c2.시작일자 AND c2.종료일자

2) 현재 시점 조회
위 쿼리를 이용해 과거, 현재, 미래 어느 시점이든 조회할 수 있지만, 만약 미래 시점 데이터를 미리 입력하는 예약 기능이 없다면
"현재 시점(즉, 현재 유효한 시점)" 조회는 아래와 같이 '=' 조건으로 만들어 주는 것이 효과적이다.

SELECT c.고객번호, 
       c.고객명, 
       c1.고객등급, 
       c2.전화번호 
  FROM 고객 c, 
       고객등급변경이력 c1, 
       전화번호변경이력 c2 
 WHERE c.고객번호 = :cust_num 
       AND c1.고객번호 = c.고객번호 
       AND c2.고객번호 = c.고객번호 
       AND c1.종료일자 = '99991231' 
       AND c2.종료일자 = '99991231'

미래 시점 데이터를 미리 입력하는 예약 기능이 있다면, 현재 시점을 조회할 때 아래와 같이 조회해야 한다.

SELECT c.고객번호, 
       c.고객명, 
       c1.고객등급, 
       c2.전화번호 
  FROM 고객 c, 
       고객등급변경이력 c1, 
       전화번호변경이력 c2 
 WHERE c.고객번호 = :cust_num 
       AND c1.고객번호 = c.고객번호 
       AND c2.고객번호 = c.고객번호 
       AND to_char(sysdate, 'yyyymmdd') BETWEEN c1.시작일자 AND c1.종료일자 
       AND to_char(sysdate, 'yyyymmdd') BETWEEN c2.시작일자 AND c2.종료일자
...... 
and convert(varchar(8), getdate(), 112) between c1.시작일자 and c1.종료일자 
and convert(varchar(8), getdate(), 112) between c2.시작일자 and c2.종료일자 
라. Between 조인

지금까지는 선분이력 조회 시점이 정해져 있었다. [그림 Ⅲ-4-35]에서 만약 우측(일별종목거래및시세)과 같은
일별 거래 테이블로부터 읽히는 미지의 거래일자 시점으로 선분이력(종목이력)을 조회할 때는 between 조인을 이용한다.

아래는 주식시장에서 과거 20년 동안 당일 최고가로 장을 마친(종가=최고가) 종목을 조회하는 쿼리다.
<그림 Ⅲ-4-35>의 일별종목거래및시세 테이블로부터 시가, 종가, 거래 데이터를 읽고 그 당시 종목명과 상장주식수는 종목이력으로부터 가져오는데,
조인 연산자가 '='이 아니라 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.종료일자 

이런 식으로 조회하면 현재(=최종) 시점의 종목명을 가져오는 것이 아니라
<그림 Ⅲ-4-36>에서 보는 것처럼 거래가 일어난 바로 그 시점의 종목명을 읽게 된다.

거래 시점이 아니라 현재(=최종) 시점의 종목명과 상장주식수를 출력하려면 between 조인 대신 아래와 같이 상수 조건으로 입력해야 한다.(<그림 Ⅲ-4-37> 참조)

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 to_char(sysdate, 'yyyymmdd') BETWEEN b.시작일자 AND b.종료일자  

위 쿼리는 종목 테이블을 종목이력과 통합해 하나로 설계했을 때 사용하는 방식이며 [그림 Ⅲ-4-35]처럼 종목과 종목이력을
따로 설계했을 때는 최종 시점을 위해 종목 테이블과 조인하면 된다.

5. ROWID 활용

선분이력과 대비해, 데이터 변경이 발생할 때마다 변경일자와 함께 새로운 이력 레코드를 쌓는 방식을 '점이력'이라고 한다.

이력 모델에선 일반적으로 서브쿼리를 이용해 찾고자 하는 시점(서비스만료일)보다 앞선 변경일자 중
가장 마지막 레코드를 찾기 위해 아래와 같이 조회한다.

SELECT a.고객명,
       a.거주지역,
       a.주소,
       a.연락처,
       b.연체금액,
       b.연체개월수
  FROM 고객 a,
       고객별연체이력 b
 WHERE a.가입회사 = 'C70'
       AND b.고객번호 = a.고객번호
       AND b.변경일자 =
       (SELECT max(변경일자)
         FROM 고객별연체이력
        WHERE 고객번호 = a.고객번호
              AND 변경일자 <= a.서비스만료일
       )

Execution Plan
-------------------------------------------------------------
 0   SELECT STATEMENT Optimizer=CHOOSE (Cost=845 Card=10 Bytes=600)
 1 0  TABLE ACCESS (BY INDEX ROWID) OF '고객별연체이력' (Cost=2 Card=1 Bytes=19)
 2 1   NESTED LOOPS (Cost=845 Card=10 Bytes=600)
 3 2    TABLE ACCESS (BY INDEX ROWID) OF '고객' (Cost=825 Card=10 Bytes=410)
 4 3     INDEX (RANGE SCAN) OF '고객_IDX01' (NON-UNIQUE) (Cost=25 Card=10)
 5 2    INDEX (RANGE SCAN) OF '고객별연체이력_IDX01' (NON-UNIQUE) (Cost=1 Card=1)
 6 5     SORT (AGGREGATE) (Card=1 Bytes=13)
 7 6      FIRST ROW (Cost=2 Card=5K Bytes=63K)
 8 7       INDEX (RANGE SCAN (MIN/MAX)) OF '고객별연체이력_IDX01' (NON-UNIQUE) (... )
  • 고객별연체이력 테이블 두 번 액세스
  • 서브쿼리 내에서 서비스만료일보다 작은 레코드를 모두 스캔하지 않고 인덱스를 거꾸로 스캔하면서 가장 큰 값 하나만을 찾는
    실행계획 수립
  • 한 블록이라도 액세스를 줄여야 하는 상황이라면 ROWID를 이용해 아래와 같이 튜닝할 수 있다.
SELECT 
       /*+ ordered use_nl(b) rowid(b) */ 
       a.고객명, 
       a.거주지역, 
       a.주소, 
       a.연락처, 
       b.연체금액, 
       b.연체개월수 
  FROM 고객 a, 고객별연체이력 b 
 WHERE a.가입회사 = 'C70' 
   AND b.rowid = 
       (SELECT 
              /*+ index_desc(c 고객별연체이력_idx01) */ 
              rowid 
         FROM 고객별연체이력 c 
        WHERE c.고객번호 = a.고객번호 
              AND c.변경일자 <= a.서비스만료일 
              AND ROWNUM <= 1
       )

Execution Plan 
------------------------------------------------------------- 
0   SELECT STATEMENT Optimizer=CHOOSE (Cost=835 Card=100K Bytes=5M) 
1 0  NESTED LOOPS (Cost=835 Card=100K Bytes=5M) 
2 1   TABLE ACCESS (BY INDEX ROWID) OF '고객' (Cost=825 Card=10 Bytes=410) 
3 2    INDEX (RANGE SCAN) OF '고객_IDX01' (NON-UNIQUE) (Cost=25 Card=10) 
4 1   TABLE ACCESS (BY USER ROWID) OF '고객별연체이력' (Cost=1 Card=10K Bytes=137K) 
5 4    COUNT (STOPKEY) 
6 5     INDEX (RANGE SCAN DESCENDING) OF '고객별연체이력_IDX01' (NON-UNIQUE) (Cost=2 Card=5K... ) 
  • 고객(a)에서 읽은 고객번호로 서브쿼리 쪽 고객별연체이력(c)과 조인하여 얻은 rowid 값으로 고객별연체이력(b) 액세스
  • 고객별연체이력을 두 번 참조했지만, 실행계획 상에는 한 번만 조인한 것과 일량이 같다.
  • 일반적인 NL Join과 같은 프로세스(Outer 인덱스 → Outer 테이블 → Inner 인덱스 → Inner 테이블)로 진행되는 것에 주목하기 바란다.
  • 위 쿼리가 제대로 작동하려면 고객별연체이력_idx01 인덱스가 반드시 [ 고객번호 + 변경일자 ] 순으로 구성돼 있어야 하며
    인덱스 구성이 변경되면 쿼리 결과가 틀려진다.
  • first row(min/max) 알고리즘이 작동한다면 일반적으로 그것만으로도 충분한 성능을 내므로 굳이 위와 같은 기법을 적용하지 않는 것이 좋다.
  • SQL Server는 Oracle처럼 사용자가 직접 ROWID를 이용해 테이블을 액세스(Table Access By User Rowid)하는 방식을 지원하지 않는다.

문서정보

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. 5월 06, 2013

    기민용 says:

    마지막 예제의 힌트가 잘못되었네. INDEX 가 아니라 INDEX_DESC 해야 맞을 듯.

    마지막 예제의 힌트가 잘못되었네.

    • INDEX 가 아니라 INDEX_DESC 해야 맞을 듯.
    1. 5월 06, 2013

      유승현 says:

      서브쿼리 내에서 서비스만료일보다 작은 레코드를 모두 스캔하지 않고 인덱스를 거꾸로 스캔하면서 가장 큰 값 하나만을 찾는 실행계획 수립 이 말...
      • 서브쿼리 내에서 서비스만료일보다 작은 레코드를 모두 스캔하지 않고 인덱스를 거꾸로 스캔하면서 가장 큰 값 하나만을 찾는 실행계획 수립
        이 말이 있는데 예제는 저렇게 나왔네요. 수정하겠습니다.
  2. 5월 09, 2013

    이재현 says:

    실기 2문제에서 분석함수을 사용하지않고 올림 SUM을 하라고 나왔었어요. SELECT t1.지점, t1.판매월, min(t1.매출) 매출, s...

    실기 2문제에서 분석함수을 사용하지않고 올림 SUM을 하라고 나왔었어요.

    SELECT t1.지점, t1.판매월, min(t1.매출) 매출, sum(t2.매출) 누적매출
    FROM 월별지점매출 t1, 월별지점매출 t2
    WHERE t2.지점 = t1.지점
    AND t2.판매월 <= t1.판매월
    GROUP BY t1.지점, t1.판매월
    ORDER BY t1.지점, t1.판매월;

    1. 5월 10, 2013

      이가혜 says:

      역시. 문제왕이야~!!! 역시 빠지지 말고 나와야 겠어~!!!

      역시. 문제왕이야~!!!
      역시 빠지지 말고 나와야 겠어~!!!

    2. 5월 10, 2013

      오정희 says:

      재현씨가 나오면 항상 시험문제 나왔던거를 알려줘~^^

      재현씨가 나오면 항상 시험문제 나왔던거를 알려줘~^^

  3. 5월 10, 2013

    안종식 says:

    잘봤습니다. 조인은 정말 볼 수록 색다르네요 ㅎㅎㅎ

    잘봤습니다. 조인은 정말 볼 수록 색다르네요 ㅎㅎㅎ