Contents

[-]
1 개요
2 테스트 데이터
3 접근 방법1
4 접근 방법2
5 결론



1 개요 #

다음과 같은 테이블에서 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

이런 컬럼, 컬럼, 컬럼는 다중값 속성을 표현한 테이블로써 설계상 그리 좋지 않은 구조이다. 비정규화 된 테이블로 볼 수 있으며, 컬럼이 추가된다면 테이블의 구조를 변경해야 하는 단점이 있다. 기존의 업무는 그대로이고, 단지 컬럼이 더 필요해졌을 뿐이라면 테이블 구조는 좋지 않은 구조이다.

2 테스트 데이터 #

--예제 데이터 생성 스크립트
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

3 접근 방법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

4 접근 방법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

5 결론 #

SQL1의 논리적 읽기 수는 444이고, SQL2는 148이다. 즉, 148 * 3 = 444이므로 3배의 논리적 읽기 수 차이가 나는 것이다. 당연하다. 테이블 반복하여 3회 읽었으니까 말이다. 또한 두 쿼리를 동시에 실행시켜 실제 실행계획에서 비용의 비교를 해보면 필자의 경우는 SQL1이 81%이고, SQL2는 19%이다. 약 4배 차이다. 만일 단일 사용자 환경이 아닌 매우 많은 사용자가 위와 같이 쿼리를 DBMS에 날린다면 부하는 좀 될 것이다. 실제 1만 명이 이 쿼리를 동시에 날린다고 생각해 보자. SQL1과 SQL2가 어떤 차이를 부를까? 또 다른 예제는 다음의 첨부 파일을 참고하기 바란다.