MERGE INTO 문
MERGE INTO 문 (MERGE INTO Statement)
MERGE INTO 문은 INSERT INTO ... ON CONFLICT의 대안으로, 기본 키가 필요 없고 커스텀 매치 조건을 허용해요. 대상 테이블에 primary key 제약이 없는 경우 upsert(INSERT + UPDATE)에 매우 유용한 대안이에요.
출처: 문서
본문
예시 (Examples)
먼저 간단한 테이블을 만들어 볼게요.
CREATE TABLE people (id INTEGER, name VARCHAR, salary FLOAT);
INSERT INTO people VALUES (1, 'John', 92_000.0), (2, 'Anna', 100_000.0);
가장 간단한 upsert는 USING 절에 전체 행을 사용하는 거예요. 이렇게 하면 매치가 있으면 더 이상 지시 없이 행을 새 행으로 업데이트할 수 있고(WHEN MATCHED THEN UPDATE), 매치가 없으면 행을 테이블에 간단히 삽입할 수 있어요(WHEN NOT MATCHED THEN INSERT).
MERGE INTO people
USING (
SELECT
unnest([3, 1]) AS id,
unnest(['Sarah', 'John']) AS name,
unnest([95_000.0, 105_000.0]) AS salary
) AS upserts
ON (upserts.id = people.id)
WHEN MATCHED THEN UPDATE
WHEN NOT MATCHED THEN INSERT;
FROM people
ORDER BY id;
| id | name | salary |
|---|---|---|
| 1 | John | 105000.0 |
| 2 | Anna | 100000.0 |
| 3 | Sarah | 95000.0 |
이전 예시에서는 id가 일치하면 전체 행을 업데이트했어요. 하지만 몇몇 키와 변경된 값이 있는 변경 집합(change set) 을 받는 것도 일반적인 패턴이에요. 여기에 SET이 좋은 용도예요. 매치 조건이 소스와 대상에서 같은 이름을 가진 컬럼을 사용한다면 매치 조건에서 USING 키워드를 사용할 수 있어요.
MERGE INTO people
USING (
SELECT
1 AS id,
98_000.0 AS salary
) AS salary_updates
USING (id)
WHEN MATCHED THEN UPDATE SET salary = salary_updates.salary;
FROM people
ORDER BY id;
| id | name | salary |
|---|---|---|
| 1 | John | 98000.0 |
| 2 | Anna | 100000.0 |
| 3 | Sarah | 95000.0 |
또 다른 일반적인 패턴은 삭제할 행의 id만 포함할 수 있는 삭제 집합(delete set) 을 받는 거예요.
MERGE INTO people
USING (
SELECT
1 AS id,
) AS deletes
USING (id)
WHEN MATCHED THEN DELETE;
FROM people
ORDER BY id;
| id | name | salary |
|---|---|---|
| 2 | Anna | 100000.0 |
| 3 | Sarah | 95000.0 |
MERGE INTO는 더 복잡한 조건도 지원해요. 예를 들어 주어진 삭제 집합 에 대해 특정 금액 이상의 salary를 가진 행만 제거하도록 결정할 수 있어요.
MERGE INTO people
USING (
SELECT
unnest([3, 2]) AS id,
) AS deletes
USING (id)
WHEN MATCHED AND people.salary >= 100_000.0 THEN DELETE;
FROM people
ORDER BY id;
| id | name | salary |
|---|---|---|
| 3 | Sarah | 95000.0 |
필요하다면 DuckDB는 여러 UPDATE와 DELETE 조건도 지원해요. RETURNING 절은 MERGE 문이 어떤 행에 영향을 주었는지 나타내는 데 사용할 수 있어요.
-- Let's get John back in!
INSERT INTO people VALUES (1, 'John', 105_000.0);
MERGE INTO people
USING (
SELECT
unnest([3, 1]) AS id,
unnest([89_000.0, 70_000.0]) AS salary
) AS upserts
USING (id)
WHEN MATCHED AND people.salary < 100_000.0 THEN UPDATE SET salary = upserts.salary
-- Second update or delete condition
WHEN MATCHED AND people.salary > 100_000.0 THEN DELETE
WHEN NOT MATCHED THEN INSERT BY NAME
RETURNING merge_action, *;
| merge_action | id | name | salary |
|---|---|---|---|
| UPDATE | 3 | Sarah | 89000.0 |
| DELETE | 1 | John | 105000.0 |
경우에 따라 소스가 조건을 충족하지 않을 때 특별히 다른 동작을 수행하고 싶을 수 있어요. 예를 들어 소스에 없는 데이터가 대상에 없어야 한다고 기대한다면:
CREATE TABLE target AS
SELECT unnest([1,2]) AS id;
MERGE INTO target
USING (SELECT 1 AS id) source
USING (id)
WHEN MATCHED THEN UPDATE
WHEN NOT MATCHED BY SOURCE THEN DELETE
RETURNING merge_action, *;
| merge_action | id |
|---|---|
| UPDATE | 1 |
| DELETE | 2 |
WHEN NOT MATCHED BY TARGET을 지정할 수도 있어요. 하지만 그 동작은 예상대로 WHEN NOT MATCHED와 동일해요. 조건을 지정할 때 기본적으로 대상을 보기 때문이에요.