#title 가로를 세로로 [[TableOfContents]] ==== 개요 ==== 다음과 같은 테이블에서 ikey별 a, b, c 중 가장 작은 값을 찾는 것이 문제다. 단, 값은 0보다 커야 한다. ikey는 고유하지 않으며, 값에 따라서 1건이 될 수 있고, 1만 건이 될 수도 있다. {{{ ikey a b c ----------- ----------- ----------- ----------- 244 382 961 368 275 623 372 947 212 148 744 466 783 75 479 678 301 931 631 998 569 872 154 435 363 654 257 388 437 655 109 327 382 149 534 725 846 471 106 187 }}} 이런 컬럼, 컬럼, 컬럼는 다중값 속성을 표현한 테이블로써 설계상 그리 좋지 않은 구조이다. 비정규화 된 테이블로 볼 수 있으며, 컬럼이 추가된다면 테이블의 구조를 변경해야 하는 단점이 있다. 기존의 업무는 그대로이고, 단지 컬럼이 더 필요해졌을 뿐이라면 테이블 구조는 좋지 않은 구조이다. ==== 테스트 데이터 ==== {{{ --예제 데이터 생성 스크립트 set nocount on set statistics io on if object_id('temp..#temp') is not null drop table #temp create table #temp( ikey int , a int , b int , c int ) go declare @i int , @i_key int , @a int , @b int , @c int set @i = 1 while(@i <= 100000) begin set @i_key = replace(right(cast(rand() as varchar), 3) , '.', '') set @a = replace(right(cast(rand() as varchar), 3), '.', '') set @b = replace(right(cast(rand() as varchar), 3), '.', '') set @c = replace(right(cast(rand() as varchar), 3), '.', '') insert #temp values(@i_key, @a, @b, @c) set @i = @i + 1 end create index idx on #temp(ikey) go }}} ==== 접근 방법1 ==== 예제 데이터 생성 스크립트를 실행하고, ikey의 카디널리티가 가장 많은 것을 보자. {{{ select top 100 ikey, count(*) cnt from #temp group by ikey order by 2 desc /* ikey cnt ----------- ----------- 804 145 935 141 687 140 918 140 754 138 207 137 588 137 */ }}} 필자는 ikey = 804가 145건으로 가장 많았다. 쉽게 생각하면 다음과 같은 SQL을 작성할 것이다. 그러나 아래와 같은 SQL은 똑같은 데이터를 3번이나 읽어야 한다. {{{ set statistics profile on --SQL1: UNION ALL을이용 select ikey, min(val) as min_val from( select ikey, min(a) as val from #temp where a > 0 and ikey = 804 group by ikey union all select ikey, min(b) from #temp where b > 0 and ikey = 804 group by ikey union all select ikey,min(c) from #temp where c > 0 and ikey = 804 group by ikey ) a group by ikey --검색수3, 논리적읽기수444, 물리적읽기수0 }}} ==== 접근 방법2 ==== {{{ --SQL2: Cross Join을이용 select ikey , min( case when id = 1 and a > 0 then a when id = 2 and b > 0 then b when id = 3 and c > 0 then c end) min_val from ( select * from #temp where ikey = 804 ) a cross join (select 1 id union all select 2 union all select 3) b group by ikey --검색수1, 논리적읽기수148 }}} ==== 결론 ==== SQL1의 논리적 읽기 수는 444이고, SQL2는 148이다. 즉, 148 * 3 = 444이므로 3배의 논리적 읽기 수 차이가 나는 것이다. 당연하다. 테이블 반복하여 3회 읽었으니까 말이다. 또한 두 쿼리를 동시에 실행시켜 실제 실행계획에서 비용의 비교를 해보면 필자의 경우는 SQL1이 81%이고, SQL2는 19%이다. 약 4배 차이다. 만일 단일 사용자 환경이 아닌 매우 많은 사용자가 위와 같이 쿼리를 DBMS에 날린다면 부하는 좀 될 것이다. 실제 1만 명이 이 쿼리를 동시에 날린다고 생각해 보자. SQL1과 SQL2가 어떤 차이를 부를까? 또 다른 예제는 다음의 첨부 파일을 참고하기 바란다. * attachment:Column을_Row로_전환.doc