/*+ ALL_ROWS */
explicitly chooses the cost-based approach to optimize a statement
block with a goal of best throughput (that is, minimum
total resource consumption)

전체 RESOURCE 소비를 최소화 시키기 위한 힌트.
             Cost-Based 접근방식.


 


/*+ CHOOSE */
causes the optimizer to choose between the rule-based
approach and the cost-based approach for a SQL statement
based on the presence of statistics for the tables accessed by
the statement
             Acess되는 테이블에 통계치 존재여부에 따라
             Optimizer로 하여금 Rule-Based Approach와 Cost-Based Approach
             중 하나를 선택할수 있게 한다.
             Data Dictionary가 해당테이블에 대해 통계정보를 가지고 있다면
             Optimizer는 Cost-Based Approach를 선택하고,
             그렇지 않다면 Rule-Based Approach를 선택한다.



/*+ FIRST_ROWS */
explicitly chooses the cost-based approach to optimize a statement
block with a goal of best response time (minimum
resource usage to return first row)
가장 좋은 응답 시간의 목표로 문 블록을 최적화하기 위해 cost-based 접근
방법을 선택합니다. (첫번째 행을 되돌려 주는 최소의 자원 사용)

/*+ RULE */
explicitly chooses rule-based optimization for a statement
block
  Rule-Based 최적화를 사용하기위해.

/*+ AND_EQUAL(table index) */
explicitly chooses an execution plan that uses an access path
that merges the scans on several single-column indexes

 single-column index의 merge를 이용한 access path 선택.
             적어도 두개이상의 index가 지정되어야한다.


/*+ CLUSTER(table) */
explicitly chooses a cluster scan to access the specified table
  지정된 테이블Access에 Cluster Scan 유도.
             Cluster된 Objects에만 적용가능.


/*+ FULL(table) */
explicitly chooses a full table scan for the specified table
해당테이블의 Full Table Scan을 유도.


/*+ HASH(table) */
explicitly chooses a hash scan to access the specified table
지정된 테이블Access에 HASH Scan 유도


/*+ HASH_AJ(table) */
transforms a NOT IN subquery into a hash antijoin to access
the specified table

NOT IN SubQuery 를 HASH anti-join으로 변형


/*+ HASH_SJ (table) */
transforms a NOT IN subquery into a hash anti-join to access
the specified table

 correlated Exists SubQuery 를 HASH semi-join으로 변형



/*+ INDEX(table index) */
explicitly chooses an index scan for the specified table
그 명시된 테이블을 위하여, 색인 scan을 고르는

/*+ INDEX_ASC(table index) */
explicitly chooses an ascending-range index scan for the specified
table

INDEX HINT와 동일 단,ASCENDING 으로 SCAN함을 확실히 하기위함.



/*+ INDEX_COMBINE(table index) */
If no indexes are given as arguments for the INDEX_COMBINE
hint, the optimizer uses whatever Boolean combination
of bitmap indexes has the best cost estimate. If particular
indexes are given as arguments, the optimizer tries to use
some Boolean combination of those particular bitmap indexes.

 INDEX명이 주어지지 않으면 OPTIMIZER는 해당 테이블의
             best cost 로 선택된 Boolean combination index 를 사용한다.
             index 명이 주어지면 주어진 특정 bitmap index 의
             boolean combination 의 사용을 시도한다.



 



/*+ INDEX_DESC(table index) */
explicitly chooses a descending-range index scan for the specified
table
 지정된 테이블의 지정된 index를 이용 descending으로 scan
             하고자할때 사용.


/*+ INDEX_FFS(table index) */
causes a fast full index scan to be performed rather than a full
table scan

 full table scan보다 빠른 full index scan을 유도.


/*+ MERGE_AJ (table) */
transforms a NOT IN subquery into a merge anti-join to access
the specified table

 not in subquery를 merge anti-join으로 변형



/*+ MERGE_SJ (table) */
transforms a correlated EXISTS subquery into a merge semi-join
to access the specified table

correalted EXISTS subquery를 merge semi-join으로 변형



/*+ ROWID(table) */
explicitly chooses a table scan by ROWID for the specified
table

지정된 테이블의 ROWID를 이용한 Scan 유도


/*+ USE_CONCAT */
forces combined OR conditions in the WHERE clause of a
query to be transformed into a compound query using the
UNION ALL set operator

 조건절의 OR 를 Union ALL 형식으로 변형한다.
             일반적으로 변형은 비용측면에서 효율적일때만 일어난다.




/*+ ORDERED */
causes Oracle to join tables in the order in which they appear
in the FROM clause

from절에 기술된 테이블 순서대로 join이 일어나도록 유도.



/*+ STAR */
forces the large table to be joined last using a nested-loops join
on the index

 STAR QUERY PLAN이 사용가능하다면 이를 이용하기위한 HINT.
             STAR PLAN은 규모가 가장큰 테이블이 QUERY에서 JOIN ORDER상
             마지막으로 위치하게 하고 NESTED LOOP 으로 JOIN이 일어나도록
             유도한다.
             적어도 3개 테이블 이상이 조인에 참여해야하며 LARGE TABLE의
             CONCATENATED INDEX는 최소 3컬럼 이상을 INDEX에 포함해야한다.
             테이블이 ANALYZE 되어 있다면 OPTIMIZER가 가장효율적인 STAR PLAN을
             선택한다.    





/*+ DRIVING_SITE (table) */
forces query execution to be done at a different site from that
selected by Oracle

QUERY의 실행이 ORACLE에 의해 선택된 SITE가 아닌 다른 SITE에서
             일어나도록 유도.


/*+ USE_HASH (table) */
causes Oracle to join each specified table with another row
source with a hash join

각 테이블간 HASH JOIN이 일어나도록 유도.



/*+ USE_MERGE (table) */
causes Oracle to join each specified table with another row
source with a sort-merge join

지정된 테이블들의 조인이 SORT-MERGE형식으로 일어나도록 유도.



/*+ USE_NL (table) */
causes Oracle to join each specified table to another row
source with a nested-loops join using the specified table as the
inner table

테이블의 JOIN 시 테이블의 각 ROW가 INNER 테이블을 NESTED LOOP
             형식으로 JOIN 한다.

.



/*+ APPEND */ , /*+ NOAPPEND */
specifies that data is simply appended (or not) to a table; existing
free space is not used. Use these hints only following the
INSERT keyword.
데이타가 테이블로 단순히 덧붙여진다는 (or not)것 명시합니다; 무료인
현존하는 영역은 사용되지 않습니다.
단지 그 삽입 키 핵심어를 따르는 이 암시를 사용하시오.

/*+ NOPARALLEL(table) */
disables parallel scanning of a table, even if the table was created
with a PARALLEL clause
그 테이블이 PARALLEL 문절로 새로 만들어졌다면 테이블의 평행  순차 검색을
사용하지 않게 함


/*+ PARALLEL(table, instances) */
allows you to specify the desired number of concurrent slave
processes that can be used for the operation.
DELETE, INSERT, and UPDATE operations are considered for
parallelization only if the session is in a PARALLEL DML
enabled mode. (Use ALTER SESSION PARALLEL DML to
enter this mode.)
PARALLEL hint를 사용하면 query에 포함된 table의 degree를 설정할 수 있다.
예를 들어, 다음과 같이 hint를 적어 degree 4로 parallel query option을
실행하도록 할 수 있다.
이 때 parallel이란 글자와 괄호( '(' )사이에 blank를 넣지 않도록 한다.
select /*+ PARALLEL(emp, 4) */ * from emp;
 
 

/*+ PARALLEL_INDEX
allows you to parallelize fast full index scan for partitioned
and nonpartitioned indexes that have the PARALLEL attribute
parallelize에 당신에게 빠른 가득한 색인 scan을 허락합니다. 그런데,
그것은 PARALLEL 속성을 가지고 있는 색인을 분할했고 nonpartitioned했습니다.

/*+ NOPARALLEL_INDEX */
overrides a PARALLEL attribute setting on an index
병렬이 색인을 나아가는 것을 속하게 하는 대체


/*+ CACHE */
specifies that the blocks retrieved for the table in the hint are
placed at the most recently used end of the LRU list in the
buffer cache when a full table scan is performed
그 블록이 찾아서 가져왔다는 것을 명시합니다. 그리고 그 테이블을 위해
그 암시에 놓여집니다. 그런데, 그것은 가장 요즈음 사용된 언제 그 버퍼 캐쉬,
가득한 테이블 scan에 있는 LRU 리스트의 끝입니다. 수행됩니다.

/*+ NOCACHE */
specifies that the blocks retrieved for this table are placed at
the least recently used end of the LRU list in the buffer cache
when a full table scan is performed
그 명시합니다. 그리고, 그 블록은 이 테이블을 위해 검색되면서 요즈음 사용된
언제 그 버퍼 캐쉬, 가득한 테이블 scan에 있는 LRU 리스트의 가장 작은 끝에
놓여집니다. 수행됩니다.

/*+ MERGE (table) */
causes Oracle to evaluate complex views or subqueries before
the surrounding query
오라클이 그 둘러싸는 질의 전에 복잡한 뷰나 부속 조회를 평가하게 합니다.

/*+ NO_MERGE (table) */
causes Oracle not to merge mergeable views
오라클이 mergeable 뷰를 합병하지 않게 하지 않습니다

/*+ PUSH_JOIN_PRED (table) */
causes the optimizer to evaluate, on a cost basis, whether or
not to push individual join predicates into the view
개개 접합을 미는 것이 그 뷰 안으로 단정 하든 간에 비용 방식으로 최적자가
평가하게 합니다.

/*+ NO_PUSH_JOIN_PRED (table) */
Prevents pushing of a join predicate into the view
접합 술부 중에서 그 뷰로 밀면서, 막는

/*+ PUSH_SUBQ */
causes nonmerged subqueries to be evaluated at the earliest
possible place in the execution plan
원인은 그 실행 계획에서의 가장 이른 가능한 장소에 평가되는 부속 조회를
nonmerged했습니다.

/*+ STAR_TRANSFORMATION */
makes the optimizer use the best plan in which the transformation
has been used.
최적자가 그 변형이 사용된 가장 좋은 계획을 사용하는 제작 

[출처] ORACLE HINT 정리|작성자 다나끔

Posted by trust
,
멋진 토론이네요

From : http://database.sarang.net/?inc=read&aid=22145&criteria=oracle&subcrit=&id=&limit=20&keyword=cube&page=1

위 쿼리에서 그룹별로 소계를 내야하는데요..
작성자
서상서
작성일
2005-03-27 13:52:24
조회수
4,588

안녕하세요? 위의 쿼리에서 그룹별로 소계를 내야하는데,,,,좋은 방법이 있으면 알려주세요..

rollup말고 복제를이용해서 하는 방법이요.

---------쿼리는 다음과 같습니다.-------------------------

select a.org_id,f.maker_group,
f.maker_code,
DECODE('D','D',a.w_date,
'W',e.week_no,
'M',substr(a.w_date,1,6),
'Q',substr(a.w_date,1,4)||' '||TO_CHAR(TO_DATE(a.w_date,'YYYYMMDD'),'Q')) as w_date,
sum(a.input_qty) as input_qty
from (SELECT * FROM intf_modis_item_pol
WHERE W_DATE BETWEEN '20050301' AND '20050326'
AND ORG_ID = 1) x,
intf_modis_item_pol a,
mqas_date e,
mqas_maker f
where x.org_id = 1
and x.org_id = a.org_id
and x.w_date = a.w_date
and x.model_id = a.model_id
and x.p_factory = a.p_factory
and x.layer = a.layer
and x.line = a.line
and x.item_id = a.item_id
and x.top_bottom = a.top_bottom
and x.maker_id = a.maker_id
AND x.maker_lot_no = a.maker_lot_no
and x.w_date = e.d_date
and x.org_id = f.org_id
and x.maker_id = f.maker_id
and 'POL' = f.item_type
group by a.org_id,
DECODE('D','D',a.w_date,
'W',e.week_no,
'M',substr(a.w_date,1,6),
'Q',substr(a.w_date,1,4)||' '||TO_CHAR(TO_DATE(a.w_date,'YYYYMMDD'),'Q')),
f.maker_group,f.maker_code
---------------------------------------------------------------

위쿼리 결과는 다음과 같습니다.

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

org_id maker_group maker_code w_date input_qty

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

1 LGC aaaa 20050101 3334544

1 LGC BBBB 20050101 44444544

1 NITTO XXXXX 20050101 66666

1 NITTO HHHHH 20050101 777777

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

위 결과와 같이 업체코드의 업체그룹별로 일자별 혹은 주기,분기별로 투입량이 나옵니다.

문제는 그룹별로 소계가 나와야 합니다.

좋은 방법있으시면 알려주세요!!


그럼 수고하세요!!

이 글에 대한 댓글이 총 10건 있습니다.

rollup을 못 쓰시는 버전이신가요?

아니면 rollup이 원하는 결과와 다르게 나오기 때문인가요?

복제(?)를 사용하는 방법이라는 것이 어떤 방법이신지?


김흥수(protokhs)님이 2005-03-27 15:06:47에 작성한 댓글입니다.

sql이 좀 이상합니다만...

암튼 "복제를 이용한 소계내기"의 원리는 다음과 같습니다.

예를 들어,


그룹ID, 판매자, 판매수량

  A       홍길동     5

  A       이순신     10

  A       강감찬     1

  B       윤봉길     10

  B       김유신      5

  C       변학도     50


위와 같이 데이터가 있을때...

그룹별 중간중간에 소계를 넣으려면...

각 row를 두개씩 복제를 합니다. 그런다음 복제된것중 한개는 원래 값을 보존하고 나머지 한개는 그룹ID는 보존하고 판매자의 값을 식별할수 있는 값 (혹은 null)로 바꿔서 그룹ID, 판매자 로 grouping을 하면 됩니다.

전체합계까지 내고 싶다면 세개씩 복제해서 세개중 한개는 원래값 보존, 나머지 두개중 한개는 판매자의 값을 통일, 나머지 한개는 그룹ID 및 판매자 모두 값을 통일시켜서 그룹ID, 판매자로 grouping하면 됩니다.

예제) 그룹별로 소계내기

--테스트용 테이블 스크립트

create table tmp_sales(
 group_id varchar2(1),
 sales_emp varchar2(10),
 sales_qty number(7))
 
 --데이터 입력
 insert into tmp_sales
 select 'A', '홍길동', 5 from dual
 union all
select   'A',       '이순신',     10 from dual
union all
select   'A',       '강감찬',     1 from dual
union all
select  'B',       '윤봉길',     10 from dual
union all
select  'B',       '김유신',      5 from dual
union all
select  'C',       '변학도',     50 from dual

commit;


--복제를 이용한 소계
select group_id, nvl(sales_emp,'소계') sales_emp, sales_qty
from (
 select group_id,
        decode(no, 1, sales_emp) sales_emp,
        sum(sales_qty) sales_qty
 from tmp_sales,
         (select 1 no from dual
          union all
          select 2 no from dual) copy_t
 group by group_id,
        decode(no, 1, sales_emp)
 )--정렬이 흐트러 지지 않게 하기 위해 인라인뷰로 한번더 감쌌습니다.

결과)

  GROUP_ID SALES_EMP SALES_QTY

A 강감찬 1
A 이순신 10
A 홍길동 5
A 소계 16
B 김유신 5
B 윤봉길 10
B 소계 15
C 변학도 50
C 소계 50



지나가다님이 2005-03-30 10:46:49에 작성한 댓글입니다. Edit

아!

카티션 프로덕트를 이용한 반복적인 select(내부적)와 그의 집계를 말하는 것이군요!


알려주셔서 감사합니다....

한가지 의문점이 있는데요....

제 생각에는 rollup이 없던 시기에

소계내기는


select ...

union all

select ...

union  all

select ...


이런 형태의 분할 정복 방법을 주로 사용하였는데요...

혹시 이 복제를 사용한 방법과 분할 정복을 사용한 방법 중 어떠한 방법이 더 성능이 우수한지 테스트해보셨는지....

단지 직관적으로는 두 방법이 거의 성능상의 차이가 없거나 있어도 근소할 것 같은데요...(근거는 둘다 어차피 처리해야 하는 record의 건수가 논리적으로는 동일 하며 복제가 실제 디스크를 읽는 것은 더 적을 것으로 생각되지만 반면에 한번에 sort할 데이타의 양이 늘어나므로 논리적 읽기의 회피가 결국은 디스크상의 소트로 나타나서 물리적 읽기를 다시 증가 시킬 것 같거든요?)


김흥수(protokhs)님이 2005-03-31 09:04:26에 작성한 댓글입니다.

맞습니다.

님께서 말씀하신대로 일수도 있으나 제가 보는 관점은 조금 다릅니다.

"분할정복방법"이라는 용어가 공식용어인지는 모르겠으나...

이 방법을 사용할때 주의할점은 임의의 데이터가 두번이상 읽혀졌는가하는 점이라고 봅니다.

가령, 월별로 현재월의 수치와 이전월의 수치를 보고자 할때,

select sum(현재월), sum(이전월)

from (

        select sum(수치) 현재월, 0 이전월

         from 테스트

         where 년월 = '200503'

         union all

         select 0 현재월, sum(수치) 이전월

         from 테스트

          where 년월 = '200502'

        )

이렇게 했을경우, 문제가 없습니다. 단, 년월로 인덱스가 있고 년월조건이 아주 똑똑해서 해당년월에 걸리는 놈들만 정확하게 엑세스 해낸다는 조건하에서만 그렇습니다. 이경우, 임의의 데이터는 union all로 묶은 위아래 쿼리중 무조건 한곳에서만 엑세스 됩니다.

이런 경우라면 님께서 말씀하신 분할정복방법을 사용하셔도 좋습니다.

그러나 소계나, 합계등은 좀 다릅니다.

임의의 데이터는 소계에도 들어가야되고 합계에도 들어가야 됩니다.

두번 엑세스 된다는 말이죠.

이런 경우는 한번만 (디스크)엑세스해서 메모리상에서 복제하는것이 훨씬 이득입니다.

물론 메모리가 부족하거나 대량의 데이터를 소팅하기 위해 디스크 IO가 발생할수 있지만, 이런 디스크IO는 옵티마이저가 최적화된 실행계획으로 데이터를 엑세스 해내는 일련의 과정과는 개념이 다른 듯 싶습니다.

요지는 옵티마이저가 실행계획을 근거로 최초로 데이터를 엑세스 하는 데 드는 비용을 줄이는것이 더 중요한 점이라 할수 있겠습니다.


지나가다님이 2005-03-31 11:27:17에 작성한 댓글입니다. Edit

상세한 설명 감사합니다.

제가 호기심이 많은지라...

테스트 해보았습니다.

(아! 분할 정복법은 제 이야기가 아니라 일반적인 알고리즘 상의 용어입니다.-> 크고 복잡한 문제를 작고 단순한 문제로 만든 다음 다시 병합하는 모든 방법을 분할 정복법이라고 합니다.)

님께서 말씀하신대로 복제를 이용한 방법이 논리적 읽기의 수가 작았습니다.물론 이것은 예측 대로이고요...

약 30000건을 대상으로 한 테스트 결과 제 컴퓨터의 메모리가 충분하지 않은 관계로(1기가) 디스크에서 소트가 발생하였고 그 결과 복제의 방법이 미미하게나마 오히려 느려지더군요. 그렇지만 차이가 날 정도는 아니었습니다.

제가 임의로 만든 테이블 (all_objects 와 all_tab_columns 를 가지고 만들었습니다.)로 테스트 한 결과는

SQL> desc t_repl
 이름                                      널?      유형
 ----------------------------------------- -------- ----------------------------
 OWNER                                     NOT NULL VARCHAR2(30)
 OBJECT_TYPE                               NOT NULL VARCHAR2(18)
 OBJECT_NAME                               NOT NULL VARCHAR2(30)
 CNT                                                NUMBER


SQL> set timing on
SQL> set autotrace traceonly
SQL> select owner,no,object_type,object_name,cnt
  2  from
  3  (
  4  select
  5   a.owner,
  6   b.no,
  7   decode(b.no,1,a.object_type,2,a.object_type) object_type,
  8   decode(b.no,1,a.object_name) object_name,
  9   sum(a.cnt) cnt
 10  from t_Repl a,
 11    (select 1 no from dual union all select 2 from dual union all select 3 from dual) b
 12  group by
 13   a.owner,
 14   b.no,
 15   decode(b.no,1,a.object_type,2,a.object_type),
 16   decode(b.no,1,a.object_name)
 17  )
 18  /

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

경   과: 00:00:01.04

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=86764815 Card=152461
          98 Bytes=686078910)

   1    0   SORT (GROUP BY) (Cost=86764815 Card=15246198 Bytes=6860789
          10)

   2    1     MERGE JOIN (CARTESIAN) (Cost=637137 Card=976435392 Bytes
          =43939592640)

   3    2       VIEW (Cost=33 Card=24504 Bytes=73512)
   4    3         UNION-ALL
   5    4           TABLE ACCESS (FULL) OF 'DUAL' (Cost=11 Card=8168)
   6    4           TABLE ACCESS (FULL) OF 'DUAL' (Cost=11 Card=8168)
   7    4           TABLE ACCESS (FULL) OF 'DUAL' (Cost=11 Card=8168)
   8    2       BUFFER (SORT) (Cost=86764815 Card=39848 Bytes=1673616)
   9    8         TABLE ACCESS (FULL) OF 'T_REPL' (Cost=26 Card=39848
          Bytes=1673616)



Statistics
----------------------------------------------------------
          0  recursive calls
          6  db block gets
        257  consistent gets
       1003  physical reads
          0  redo size
    1441224  bytes sent via SQL*Net to client
      29983  bytes received via SQL*Net from client
       2682  SQL*Net roundtrips to/from client
          0  sorts (memory)
          2  sorts (disk)
      40215  rows processed

SQL> select
  2   owner,
  3   1 no,
  4   object_type,
  5   object_name,
  6   cnt
  7  from t_repl
  8  union all
  9  select
 10   owner,
 11   2 no,
 12   object_type,
 13   '타입소계',
 14   sum(cnt)
 15  from t_repl
 16  group by
 17   owner,
 18   object_type
 19  union all
 20  select
 21   owner,
 22   3 no,
 23   '소계',
 24   '',
 25   sum(cnt)
 26  from t_repl
 27  group by
 28   owner
 29  order by
 30   1,2,3
 31  /

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

경   과: 00:00:00.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=523 Card=40733 Bytes
          =1696274)

   1    0   SORT (ORDER BY) (Cost=335 Card=40733 Bytes=1696274)
   2    1     UNION-ALL
   3    2       TABLE ACCESS (FULL) OF 'T_REPL' (Cost=26 Card=39848 By
          tes=1673616)

   4    2       SORT (GROUP BY) (Cost=172 Card=841 Bytes=21866)
   5    4         TABLE ACCESS (FULL) OF 'T_REPL' (Cost=26 Card=39848
          Bytes=1036048)

   6    2       SORT (GROUP BY) (Cost=137 Card=44 Bytes=792)
   7    6         TABLE ACCESS (FULL) OF 'T_REPL' (Cost=26 Card=39848
          Bytes=717264)



Statistics
----------------------------------------------------------
          0  recursive calls
          6  db block gets
        744  consistent gets
        264  physical reads
          0  redo size
    1441631  bytes sent via SQL*Net to client
      29983  bytes received via SQL*Net from client
       2682  SQL*Net roundtrips to/from client
          2  sorts (memory)
          1  sorts (disk)
      40215  rows processed

SQL>



위의 결과를 보시면 아시겠구요...


제 생각은 이렇습니다.

* 복제나 union all을 사용한 소계 방식이나 크게 성능상의 차이가 없다.(그 이유는 위에서 제가 예측한 이유->1.논리적 대상의 크기가 차이가 없다.2. 대량의 집계의 경우 디스크 상의 정렬로 오히려 복제가 오버헤드를 발생시킨다.=>즉 테이블을 한번만 읽어서 번 시간을 소트하는데 다시 쓴다는...  3. 소량의 자료의 경우 어차피 차이가 없다.)


김흥수(protokhs)님이 2005-03-31 13:08:25에 작성한 댓글입니다.
이 댓글은 2005-03-31 13:11:31에 마지막으로 수정되었습니다.

약 1000만건이 들어 있는 현업 데이터에서 일자조건으로 10만건만 뽑아내서 테스트했습니다.


먼저 복제를 이용한 소계...

참고로 저는 복제를 위해 copy_t테이블을 만들어 둡니다.


select decode(no, 1, ma15vcod, 2, ma15vcod) ma15vcod,
    decode(no, 1, ma15icod, 2, '소계') ma15icod,
    sum(ma15tqty)
from (
 select ma15vcod, ma15icod, ma15tqty
 from ma_ma1500pf
 where ma15date between '2004-01-01' and sysdate and
    ma15seqn > 0
  ), copy_t
where no <= 3
group by decode(no, 1, ma15vcod, 2, ma15vcod),
    decode(no, 1, ma15icod, 2, '소계')


SELECT STATEMENT Optimizer Mode=CHOOSE                     
  SORT GROUP BY                     
    NESTED LOOPS                     
      TABLE ACCESS BY INDEX ROWID ACCOUNT.MA_MA1500PF                    
        INDEX RANGE SCAN ACCOUNT.PK_MA1500PF                    
      INDEX RANGE SCAN ACCOUNT.PK_COPY_T                    


여기서 저는 주관테이블이 먼저 엑세스 되고 복제용 테이블이 두번째로 엑세스 되면서 Lested Loops로 풀렸는데 님은 상수값으로 된 더미테이블을 만들어서 그런지 더미가 먼저 만들어져서 주관테이블이 두번째로 엑세스 되면서 Merge Join으로 풀렸더군요. 아마 여기서 오버헤드가 발생했을 가능성이 커 보입니다.


recursive calls  0
db block gets 0
consistent gets 198652
physical reads 0
redo entries 0
execute count 2
bytes sent via SQL*Net to client 144161
bytes received via SQL*Net from client 18152
SQL*Net roundtrips to/from dblink 0
sorts (memory) 3


다음은 union all로 여러번 읽은 방법입니다.

select ma15vcod, ma15icod, ma15tqty
from (
 select ma15vcod, 1 no, ma15icod, ma15tqty
 from ma_ma1500pf
 where ma15date between '2004-01-01' and sysdate and
    ma15seqn > 0  
 union all
 select ma15vcod, 2 no, '소계', sum(ma15tqty)
 from ma_ma1500pf
 where ma15date between '2004-01-01' and sysdate and
    ma15seqn > 0 
 group by ma15vcod  
 union all 
 select '총계', 3 no, '총계', sum(ma15tqty)
 from ma_ma1500pf
 where ma15date between '2004-01-01' and sysdate and
    ma15seqn > 0
 )
order by ma15vcod, no  


SELECT STATEMENT Optimizer Mode=CHOOSE                     
  SORT ORDER BY                     
    VIEW                     
      UNION-ALL                     
        TABLE ACCESS BY INDEX ROWID ACCOUNT.MA_MA1500PF                    
          INDEX RANGE SCAN ACCOUNT.PK_MA1500PF                    
        SORT GROUP BY                     
          TABLE ACCESS BY INDEX ROWID ACCOUNT.MA_MA1500PF                    
            INDEX RANGE SCAN ACCOUNT.PK_MA1500PF                    
        SORT AGGREGATE                     
          TABLE ACCESS BY INDEX ROWID ACCOUNT.MA_MA1500PF                    
            INDEX RANGE SCAN ACCOUNT.PK_MA1500PF                    

recursive calls  1610
db block gets 3440
consistent gets 301782
physical reads 3179
redo entries 171
execute count 159
bytes sent via SQL*Net to client 3609375
bytes received via SQL*Net from client 438106
SQL*Net roundtrips to/from dblink 0
sorts (memory) 3


참고로 H/W사양은 P-3 550 Dual, Memory 2G입니다.

오라클 버전은 8.0.5이고요

관리를 거의 안해서 튜닝이 거의 안되있는 상탭니다.

이런 환경을 감안하더라도...

수행시간은 약 7초 10초로 3초정도 차이로 복제방법이 빠릅니다만...

흥수님의 논리도 일리는 있어 보입니다.

하지만 제 상식이나 경험에서는 한번읽어서 메모리복제가 훨씬 좋아 보입니다.

모코아 컨설팅의 조광모님께서도 적극 권장하시더군요.

물론 그렇다고 다 맞는것은 아닙니다만.


암튼 간만에 저도 여러가지 부분을 보게 되어서 기뻤습니다.

감사하단 말씀 전하면서...화이팅 ^^


지나가다님이 2005-03-31 15:57:03에 작성한 댓글입니다.
이 댓글은 2005-03-31 18:14:52에 마지막으로 수정되었습니다. Edit

넷상이 아니라면 많은 토론을 해보고 싶군요...

테스트하시고 올려 주셔서 감사하구요

한가지 말씀드릴 사항이 있는데요..

두번째 질의의 union all 방법에 보면 recursive call 이 1610 이되어 있는데요

이 경우 제 경험상 필수적인 recursive call일 수도 있지만 parsing 과정의 부산물일 수도 있습니다.


그래서 저는 가능한 공정한 테스트를 원할 경우

1. 먼저 두 SQL을 한번씩 수행하여 질의 계획을 메모리에 상주시킨다.

2. 한번은 A를 먼저 실행하고 다른 한번은 B를 먼저 실행하여 각각의 physical 읽기를 확인 하여 수행 순서에 따라 큰 변화가 없으면 인정한다.


주로 이렇게 하거든요


물론 제가 올린 테스트도 그런 과정을 거친 것입니다.

저 역시 테스트때 사전에 SQL의 질의 계획을 메모리에 올리는 과정에서 recursive call이 (제 경우는 복제가 그랬습니다만...) 올라가는 것을 보고 순서를 바꾸어 계속 테스트한 다음 결과를 올린 것입니다만...


님께서도 한번 동일한 질의를 연속으로 해보시고 recursive call이 제거되는지 확인해보심이 어떠실런지....

그리고 복제의 경우 db block get 이 없다는 점은 이 질의가 이미 실행되어 해당 테이블의 데이타가 케시에 올라와 있는 상황이라는 점과

질의계획상에 코스트가 없는 것으로 보아 규칙기반인 것으로 보이는데요...


제 테스트에서 merge join이 나온것은 사실 제가 먼저 규칙기반으로 테스트 한 다음 별다른 차이가 없어서 analyze 한 후 다시 테스트 한 결과 그렇게 나왔으며 사실 그로인해 테스트가 규칙 기반의 경우보다 약 두배 빨라졌고 그 결과를 올린 것입니다.


그리구 전부 메모리 소트 되었군요...(음 메모리의 충분함이 좋군요..)


등등의 점등을 고려하여 한번 더 해보시고 결과를 알려주시면 ....

더 없이 고맙겠습니다.

억지같은 제 주장에 관심 갖고 대답을 올려주셔서 감사합니다.


많은 토론! 많은 실험!

개발자 화이팅!


아! 참고로 저는 두번 읽으면 느리다는 의견에는 웬지 반대라는 입장입니다....

 한가지 더...

제 테스트를 보시면 아시겠지만 저의 경우는 union all 이 두번 읽기가 아니라 사실은 3번 읽기입니다.

그렇게 한 이유는 만약 실제 데이타의 읽기 횟수가 늘어날 수록 union all 방법이 불리하다면 3번 읽기의 테스트는 아주 비관적으로 나와야 맞습니다..

그러나 결과를 보시면 3번 읽기임에도 큰 차이를 보이지 않습니다.

실무에서도 1단계의 소계만을 보기보다는 다단계의 소계를 보는 경우가 더 많습니다.


그리구 사실 이런 논의 자체가 이미 무의미할 수도 있습니다.

왜냐면 이미 rollup등이 있으니까요.

그리구 rollup이 없던 시대에도 대부분 소계처리는 서버에서 수행하지 않았었습니다.

이런 일(소계를 서버에서 처리하는 일)은 대부분 보고서 툴이 후져서 소계를 제대로 지원 못하는 경우 말고는 없었습니다.

소계를 서버에서 처리하는 것은 union all이던 복제를 이용한 방법이던 제 생각에는 모두 일종의 꽁수입니다.

그 이유는 client로 전달되어야 할 데이타의 양이 늘어나고.

두 방법 모두 논리적 처리 대상 레코드 수가 소계의 레벨만큼 증가하여 서버에 부담을 주며

클라이언트에서 처리할 경우 데이타가 정렬되어 있음을 전제로 하면 데이타 소계를 구하는 알고리즘은 데이타 양에 선형적인 속성을 가지고 있습니다.즉 그냥 소계없이 데이타를 구성하는 것과 비교하여 거의 비용이 들지를 않는다는 말이구요...

오라클 서버에서 제공하는 cube를 제외한 rollup 이나 grouping sets 같은 방법 역시 정렬의 부산물로 결과를 얻는 (그전에 client에서 하던것과 동일한 알고리즘) 방법을 사용하므로 과거버전의 꽁수들(union all이나 복제방법)보다는 좋은 성능을 얻을 수 있는 것이라 생각합니다.


단 카티션 프로덕트를 피해야하는 질의로 간주하지 않고 최대한 활용한 점.

제경우처럼 full-scan이 유도되는 빠른 디스크 읽기를 사용하는 경우가 아닌 행의 부분접근의 경우 고비용이되는 디스크 읽기를 줄인다는 점 등이 님께서 말씀하신 복제를 이용한 질의의 우수성이라 생각됩니다.


반대로 뒤집어서 말하면 단지 데이타에 대한 N번의 중복 접근이 좋지 않으며 이를 피하기 위해 카티션 프로덕트를 사용한다.

라는 착안점은 좀 더 깊이 생각을 해봐야 하는 점이라 생각합니다.(왜냐면 이것은 상황에 따라 달라지는 부분이지 알고리즘 측면에서 결코 본질적으로 우수한 방법은 아닙니다.)


한가지 더 말씀을 드리면 사실 저는 이 복제를 이용한 방법이 있다는 것을 이번에 알았구요

이점 감사드리구요

누구인지는 모르지만 이러한 방법(아이디어)을 최초에 생각해내신 분께 존경의 말씀을 드리고 싶습니다.

단 이러한 방법은 카티션 프로덕트의 사용의 연장선상에 있으며 특별히 복제를 사용한 방법이라는 이름이 붙었다는 것은 아마도 최초로 만드신 분이나 혹은 그 후에 이 방법을 주로 사용하셨던 분들이 이름을 붙이신 것이리라 생각합니다. 그러나 우리가 알아야 할 점은 우리가 무언가 특별한 방법에 특별한 의미를 두어 이를 기리고 의미를 부여하는 순간부터 사실은 이러한 방법이 화석화 된다는 점입니다.

뒤집어 말하면 도전 정신과 실험 정신으로 이 SQL을 처음 만드셨던 분의 정신을 배우는 것이 이 SQL을 활용하여 문제를 해결하고 이를 단순히 받아들이는 것보다 더 중요하다는 점을 말씀드리고 싶습니다.


! 항상 선배가 하는 말은 일단 틀렸다고 생각하고 뒤집어라 !

아마 이 정신이 아닐까 싶습니다.

음... 너무 장황하게 떠들면 난척해서 죄송합니다...

꾸뻑!

그럼 즐근하십시오.

언제 시간되시면 술이라도 한잔...

김흥수(protokhs)님이 2005-03-31 22:35:55에 작성한 댓글입니다.
이 댓글은 2005-04-01 02:30:23에 마지막으로 수정되었습니다.

음...

간만에 댓글을 여러번 달게 되네요..^^

테스트는 흥수님 말씀대로 여러번 번갈아 가며 해본 결과고요.

8.0.5버전이라 rollup이나 analytic function이 지원되지 않습니다. 흑흑..

그래서 copy_t라는 복제용 테이블을 만들어 놓고 쓰고 있고요.

복제를 활용한 방법은 굳이 "소계"가 아니더라도 활용범위가 많습니다.

Join횟수를 줄이거나 배타적관계로 조인해야할 경우, 무조건 outer join을 해야하지만 복제를 적절히 이용하면 이런 부분에 대한 문제를 어느정도 해결은 할수 있습니다.

근원적인 문제는 디비 디자인이고 데이터 모델링이지만 발등에 떨어진 급한불 끄는 데는 어느 정도 활용하는 편입니다.

analytic function으로 해결하던것을 전부는 아니지만 복제로 어느정도 해결은 하고 있습니다.

그리고 예전에 MS-SQL로 테스트해본 것이 생각나는데 내부적으로는 rollup도 메모리상의 데이터복제가 이루어 지고 있다는 점입니다.

이는 오라클에서도 마찬가질거라 추측되며...

그리고 옵티마이저 모드는 비용기반입니다.

SGA튜닝이 거의 안되있는 상황이고요.

10만건 이상짜리 table full scan하는 SQL이 중간중간에 많이 들어있어서..

이놈들 다고치려면 거의 프로그램80%을 손대야 하는 상황이라서 엄두는 못내고 있지만...

참고로 유지보수를 뒤늦게 맡게 되서 전임자들이 해논짓에 완전 독박쓰고 있습니다.


저는 두가지관점으로 봅니다.

말씀처럼 한번 읽어 복제하는데 양이 많고 메모리 부족해서 똑같이 디스크 소트가 발생한다면 결국 비용차이는 없는것 아닌가..하는 말도 맞다고 생각합니다.

그래서 메모리가 부족하니 증설합시다.. 해서 늘렸습니다.

두 방법다 메모리상에서 소트되고 아주 잘 돌아 갑니다.

그러나 데이터를 엑세스 하는 방법만 놓고 볼때는 효율과 비효율은 반드시 존재합니다.

옵티마이저가 똑똑하게, 아주 효율적으로 움직이게 해주는것은 메모리 증설이나 하드웨어가 아니라는 거죠.

가장 중요한 점은 어떻게 얼마나 효율적으로 목적 데이터를 엑세스해오느냐...일단 엑세스 해온 데이터를 디스크 상에서 소팅하든 메모리 상에서 소팅하든 그건 하드웨어 성능에 달린것이지 데이터의 분포에 따른 인덱스사용결정, 테이블 엑세스 우선순위 결정, 조인 방법결정 등과 같은 옵티마이저의 핵심기능은 아니라는 점입니다.

데이터 모델링에서 부터 출발한 디비디자인, 그리고 그에 맞게 잘 작성된 SQL문장이 가장 우선입니다.

나머지는 하드웨어 성능에 맡기는 것입니다.

각설하고...

짧은 지식에 여러가지 댓글다느라 머리가 아픕니다..^^

암튼 많이 배우고 갑니다.

행복하세요...


지나가다님이 2005-04-01 10:36:06에 작성한 댓글입니다. Edit

대화 즐거웠습니다.

김흥수(protokhs)님이 2005-04-01 14:33:14에 작성한 댓글입니다.

저 제가 집에와서 다시 자세히 님의 테스트를 보고 약간 이상한 점이 발견되어 말씀드립니다.


님의 테스트에 보면


TABLE ACCESS BY INDEX ROWID ACCOUNT.MA_MA1500PF                    
          INDEX RANGE SCAN ACCOUNT.PK_MA1500PF            


이 부분이 있는데요...

이 것으로 미루어보아 님의 테이블은 키가 ma15vcod,ma15icod

이외에도 적어도 ma15date 나 ma15seqn 이거 둘중의 하나 또는 둘다가 primary key에 포함되어 있다는 것을 추측할 수 있습니다.


그렇다면 님께서 하신 테스트의 두 SQL은 그 결과값이 다른 별개의 SQL이 됩니다.

그 이유는


님의 복제를 이용한 방법은  ma15vcod 별 ma15icod 별 계와

ma15icod 별 소계 및 총계로 출력되지만


union all을 이용한 방법은


제일 첫 SQL. 부분이

select ma15vcod, 1 no, ma15icod, ma15tqty
 from ma_ma1500pf
 where ma15date between '2004-01-01' and sysdate and
    ma15seqn > 0  

이렇게 되어 있으므로


select ma15vcod, 1 no, ma15icod, sum(ma15tqty )
 from ma_ma1500pf
 where ma15date between '2004-01-01' and sysdate and
    ma15seqn > 0  
group by ma15vcod,ma15icod


이렇게 해 주어야 동일한 결과가 나옵니다.


물론 저도 불리한 점을 감안해서 한 것이지만..

Order by 부분이 님의 것에는 빠져있으므로


union all의 거에도 inline-view로 감싸고 order by 한 부분을 빼주어야 공정한 테스트가 됩니다...

Posted by trust
,
select ceil((to_char(sysdate,'dd') + to_char(last_day(add_months(sysdate,-1)),'d')) / 7) || '번째 주'  from dual
Posted by trust
,