MERGE [타겟 테이블] AS T
USING [소스 테이블] AS S
ON T.[컬럼] = S.[컬럼]
WHEN MATCHED THEN
UPDATE SET ~~~
WHEN NOT MATCHED BY TARGET THEN
INSERT ~~~
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
지난 번에 다룬 UPSERT에 한 가지 동작이 추가되었습니다.
<타겟 테이블에만 있고 소스 테이블에 없는 행을 삭제하라!>
이 동작을 수행하되 타겟 테이블의 일부만 가져와서 MERGE하는 경우를 생각해 봅시다.
IF OBJECT_ID(N'dbo.MergeTest', N'U') IS NOT NULL
DROP TABLE dbo.MergeTest;
GO
CREATE TABLE dbo.MergeTest (
userSN int NOT NULL,
col1 int NOT NULL,
col2 char(100) NOT NULL,
CONSTRAINT PK_MergeTest PRIMARY KEY CLUSTERED (userSN, col1)
);
GO
INSERT dbo.MergeTest (userSN, col1, col2)
SELECT TOP (10000) ROW_NUMBER() OVER(ORDER BY A.number), 1, ''
FROM master.dbo.spt_values A
CROSS JOIN master.dbo.spt_values B;
GO
INSERT dbo.MergeTest (userSN, col1, col2)
SELECT userSN, 2, '' FROM dbo.MergeTest
UNION ALL
SELECT userSN, 3, '' FROM dbo.MergeTest;
GO
SELECT COUNT(*) FROM dbo.MergeTest;
Results :
30000
SELECT * FROM dbo.MergeTest WHERE userSN = 10;
Results :
userSN |
col1 |
col2 |
10 |
1 |
|
10 |
2 |
|
10 |
3 |
|
SELECT * FROM dbo.MergeTest WHERE userSN = 100;
Results :
userSN |
col1 |
col2 |
100 |
1 |
|
100 |
2 |
|
100 |
3 |
|
- [타겟 테이블]인 dbo.MergeTest 테이블에는 30,000개의 행이 있습니다.
- 이 테이블 중 userSN = 10 인 레코드만 대상으로 MERGE 문을 실행하려면 어떻게해야 할까요?
1. ON 절에 T.userSN = 10을 넣을까요?
아니오. 이렇게하면 NOT MATCHED BY SOURCE THEN DELETE 가 실행될 때 29,997개의 레코드가 삭제됩니다.
2. 그럼 NOT MATCHED BY SOURCE AND T.userSN = 10 THEN DELETE 라고 하면 어떨까요?
네. 원하는 결과를 얻을 수 있습니다.
하지만, [타겟 테이블]을 full scan하므로 성능이 매우 떨어집니다. (물론 userSN 컬럼에 인덱스가 있습니다.)
3. ON 절에도 T.userSN = 10을 넣고, NOT MATCHED BY SOURCE AND T.userSN = 10 THEN DELETE 라고 작성해도 full scan이 일어납니다.
즉, [타겟 테이블]을 효과적으로 필터링하기 위한 무언가가 필요합니다.
제가 찾은 방법은 [타겟 테이블]을 CTE로 대체하는 것입니다.
위의 방법을 사용했을 때와 CTE를 사용했을 때의 차이는 아래 예와 같습니다.
MERGE dbo.MergeTest AS T
USING (
SELECT 10, 2, ''
UNION ALL
SELECT 10, 3, 'A'
UNION ALL
SELECT 10, 4, 'B'
) AS S (userSN, col1, col2)
ON T.userSN = S.userSN AND T.col1 = S.col1
WHEN MATCHED AND T.col2 <> S.col2 THEN
UPDATE SET col2 = S.col2
WHEN NOT MATCHED BY TARGET THEN
INSERT (userSN, col1, col2) VALUES (S.userSN, S.col1, S.col2)
WHEN NOT MATCHED BY SOURCE AND T.userSN = 10 THEN
DELETE;
GO
SELECT * FROM dbo.MergeTest WHERE userSN = 10;
Results :
userSN |
col1 |
col2 |
10 |
2 |
|
10 |
3 |
A |
10 |
4 |
B |
WITH CTE AS (
SELECT userSN, col1, col2
FROM dbo.MergeTest
WHERE userSN = 100
)
MERGE CTE AS T
USING (
SELECT 100, 2, ''
UNION ALL
SELECT 100, 3, 'A'
UNION ALL
SELECT 100, 4, 'B'
) AS S (userSN, col1, col2)
ON T.userSN = S.userSN AND T.col1 = S.col1
WHEN MATCHED AND T.col2 <> S.col2 THEN
UPDATE SET col2 = S.col2
WHEN NOT MATCHED BY TARGET THEN
INSERT (userSN, col1, col2) VALUES (S.userSN, S.col1, S.col2)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
GO
SELECT * FROM dbo.MergeTest WHERE userSN = 100;
Results :
userSN |
col1 |
col2 |
100 |
2 |
|
100 |
3 |
A |
100 |
4 |
B |
CTE를 사용하면 [타겟 테이블]의 크기를 미리 제한할 수 있습니다.
단, CTE를 [타겟 테이블]로 사용하는 경우 예상하지 않은 동작을 일으키지 않는지 충분히 테스트하셔야 합니다.
'SQL Server > SQL Server Tip & Tech' 카테고리의 다른 글
RDB가 왜 관계형 데이터베이스? (5) | 2009.11.10 |
---|---|
How to read SQL Server graphical query execution plans (0) | 2009.11.05 |
MERGE 응용 - 1. UPSERT (0) | 2009.10.09 |
12 Essential Steps After Installing SQL Server (0) | 2009.08.03 |
SPARSE 컬럼 (0) | 2009.07.30 |