우선 해당 실행계획을 보면 다음과 같습니다.
처음에 튜닝 할 당시에 이게 뭐하는 실행계획인지 잘 몰랐습니다. 조인 시에 Miss? / 인덱스 오류? 이런 저런 생각을 하였는데 정말 이유는 간단했습니다.
조인의 조건자가 정말 없습니다. 그런데 상식적으로 조인을 걸고 ON 절을 해주지 않으면 조인이 안되지 않나? 이런 생각을 했는데... 잘 됩니다. 음...없다기 보다는
같은걸 2번 쓰거나 엉뚱한(?)것을 입력 했을때 쿼리가 잘 실행됩니다.
그렇다면 뭘로 조인을 할까요? 그래서 테스트를 해봤습니다.
준비한 테이블은 골키퍼 축구 선수들의 트레이닝 강의에 관한 내용입니다. 발 설계 및 발 쿼리니 이 자료는 참고만 부탁드립니다.
테이블은 간단합니다. 4개의 테이블이 존재하며, 각각 선수정보(info) / 트레이닝항목(Class) / 수강현황(Register_info) / 코치(Teacher)
개인적으로 수강현황은 N:N의 내용을 해소하기 위한 테이블로 만들었습니다. 한 트레이닝을 여러명이 들을수 있으며, 여러명이 여러개의 트레이닝을 받을 수 있기에...
Step 1 - 테이블 만들기
- 인덱스를 보고 이건 뭐야?라는 분들이 계실텐데 실행계획에서 NL조인에 X표기를 보기위한 것이니 너무 신경 쓰지 않으시길 바랍니다. ^^....
- 테이블은 위에 언급처럼 총 4개를 만듭니다. 선수정보/트레이닝항목/수강현황/코치 입니다.
USE StudyDB
GO
create table info
(
User_key int identity(1,1)
,ID varchar(10)
,Name varchar(8)
,Hp char(11)
constraint PK_info primary key clustered (User_key)
)
go
create table Class
(
Class_key int identity(1,1)
,Class_name varchar(20)
,Class_code int
,teacher_key int
constraint PK_Class primary key clustered (Class_key)
)
go
create table Register_info
(
register_key int identity(1,1)
,User_key int
,Class_key int
,register_regdate datetime
,flag char(1)
constraint PK_Register_info primary key clustered (register_key)
)
go
create nonclustered index NC_1 on Register_info(User_key)
create nonclustered index NC_2 on Register_info(Class_key)
go
create table Teacher
(
teacher_key int
,teacher_name varchar(8)
,Hp char(11)
,teacher_class varchar(30)
constraint PK_Teacher primary key clustered (teacher_key)
)
go
Step 2 - 데이터 입력
- 이제 몇건의 데이터를 넣어보겠습니다.
insert into [dbo].[info]( ID, Name, Hp) values ('AAAA','데헤아','01012341234')
insert into [dbo].[info]( ID, Name, Hp) values ('BBBB','노이어','01023143532')
insert into [dbo].[info]( ID, Name, Hp) values ('CCCC','카시야스','01033313643')
insert into [dbo].[info]( ID, Name, Hp) values ('DDDD','체흐','01012346643')
insert into [dbo].[info]( ID, Name, Hp) values ('EEEE','부폰','01023535036')
go
select * from [info]
go
insert into [dbo].[Class](Class_name, Class_code, teacher_key) values ('피지컬',1001,1)
insert into [dbo].[Class](Class_name, Class_code, teacher_key) values ('골키퍼',1002,2)
go
select * from [Class]
go
insert into [dbo].[Teacher] ([teacher_key],teacher_name, Hp, teacher_class) values(1,'무리뉴','01035530455','골키퍼 전문코치')
insert into [dbo].[Teacher] ([teacher_key],teacher_name, Hp, teacher_class) values(2,'클롭','01035340455','체력 전문코치')
go
select * from [Teacher]
go
insert into [dbo].[Register_info] ( User_key, Class_key, register_regdate, flag) values (1,1,getdate(),'Y')
insert into [dbo].[Register_info] ( User_key, Class_key, register_regdate, flag) values (1,2,getdate(),'Y')
insert into [dbo].[Register_info] ( User_key, Class_key, register_regdate, flag) values (2,1,getdate(),'Y')
insert into [dbo].[Register_info] ( User_key, Class_key, register_regdate, flag) values (3,2,getdate(),'Y')
go
select * from [Register_info]
go
Step 3 - 정상적인 조인
- 여기서 우리는 Select를 통해서 어떤 선수가 어떠한 트레이닝을 받고 있는지 확인 하고자 합니다.
select I.User_key,I.Name,C.Class_name
from info as I
inner join Register_info as R
on I.User_key = R.User_key
inner join Class as C
on R.Class_key = C.Class_key
order by User_key asc
- 실행계획에 아무런 지장이 없습니다.
- 왜? on절도 정확히 명시되어있고, 정확한 Key로 묶었습니다.
- 그럼 선수가 받는 트레이닝은 무엇이며, 그 트레이닝의 담당자는 누구인지도 확인해 보겠습니다.
select I.User_key,I.Name,C.Class_name,T.teacher_name
from info as I
inner join Register_info as R
on I.User_key = R.User_key
inner join Class as C
on R.Class_key = C.Class_key
inner join Teacher as T
on C.teacher_key = T.teacher_key
order by User_key asc
- 역시 별다른 특이점이 없습니다. 왜? ON절을 정확히 명시했으니...
- 그럼이제 NL 조인 부분에 X를 나타나게 해보겠습니다.
Step 4 - 조인 조건자 없음
- 아까랑 똑같이 하돼 약간의 ON의 변화를 주겠습니다.
- 쿼리에서 표시한 부분의 ON절을 보면 2번째 Inner Join부분과 ON절이 똑같습니다. 그럼 Teacher 테이블은 어디갔어? ON절에 없잖아?
뭐랑 조인하지? 하겠지만 조인이 됩니다.
select I.User_key,I.Name,C.Class_name,T.teacher_name
from info as I
inner join Register_info as R
on I.User_key = R.User_key
inner join Class as C
on R.Class_key = C.Class_key
inner join Teacher as T
on R.Class_key = C.Class_key -- 문제가 되는 부분
order by User_key asc
- NL 조인 부분에 X표시가 생겼습니다.
이 경고는 말그대로 조건자가 없음을 말합니다. 그렇다면 이것이 왜 문제가 되는가? 이것이 핵심입니다.
바로 데이터가 틀리게 나오기 때문입니다. 우리가 바로 위에서 했던 정상 쿼리문과 결과를 비교해보겠습니다.
- 비교된 결과를 보면 다음과 같습니다.
- 그림을 통해서 확실히 보면 다음과 같습니다.
- 결과를 보면 이상한 점을 볼 수 있습니다.
각각의 데이터가 2배로 들어났습니다. 데이터도 이상합니다. 데헤아는 골키퍼,피지컬 1번씩 듣는데 2번씩 듣도록 되어있고, 노이어 / 카시야스는 수업이 총 1회씩인데
골키퍼 2번 / 피지컬 2번씩 되어 있습니다. 이것은 무엇을 의미 할 까요?
- 3번째 Inner 조인까지 정확히 되었으나, 4번째 Teacher 테이블을 조인 시 Cross 조인을 하는 것임을 알 수 있습니다.
- 결과적으로 이것은 잘못된 데이터죠.
그렇다면 언제 이러한 실수를 많이 저지를 까요?
Step 5 - 우리는 언제 이러한 실수를 많이 하는가?
- 에이 누가 쿼리를 그렇게 짜요? 할 수 있습니다. 그런데 제가 현재 회사에서 튜닝을 하면 20건 중에 1건 정도는 이러한 결과를 보여줍니다. 왜 일까요?
- 개발자 분들은 주로 Where 조건을 이용해서 Join을 합니다. ON절을 잘 안쓰더군요.(물론 캐바캐입니다.)
Where 조건에 넣음으로써 쿼리에서 결과가 맞다고 판단하고 그냥 넘어가는 것이죠. Where 조건으로 위의 내용을 재현하면 다음과 같습니다.
select I.User_key,I.Name,C.Class_name
from info as I
, Register_info as R
, Class as C
, Teacher as T
where I.User_key = R.User_key
and R.Class_key = C.Class_key
and R.Class_key = C.Class_key
order by User_key asc
흔히들 Where 조건에 기재하면서 Where 조건에 조건을 잘못 입력하는 경우가 있습니다.
위의 조건 처럼 사용하면 실수를 많이 범합니다. 하지만... 조인이 잘되니 아 ! 이상이 없구나 하고 넘기는 경우가 많죠.
이러한 실수를 방지하기 위해서 꼭 ON절을 이용한 조인을 해야합니다. 뭐.. 꼭은 아니지만 권고하는 것이죠.
감사합니다.
'Database > SQL Server' 카테고리의 다른 글
데이터 대량 변경 작업 시 체크해야 할 리스트 (0) | 2020.08.27 |
---|---|
INDEX 인덱스 삭제 시 체크해야 할 리스트 (0) | 2020.08.27 |
DB 보안 (0) | 2020.08.27 |
Missing Index (0) | 2020.08.27 |
SQL Server의 동적 데이터 마스킹 (0) | 2020.08.27 |