원본에 없는 행만 대상 테이블에 넣으려고 LEFT JOIN 을 쓰는데, 비교 대상 컬럼에 NULL 이 섞이면 조건이 맞지 않아 같은 행이 계속 다시 들어간다.
INSERT INTO TBL_A (col1, col2, col3)
SELECT src.col1, src.col2, src.col3
FROM TBL_B src
LEFT JOIN TBL_A tgt ON src.col1 = tgt.col1
WHERE tgt.col1 IS NULL
OR src.col2 = tgt.col2
OR src.col3 = tgt.col3;
첫째, WHERE 의 OR 조건이 의도와 반대로 동작한다. tgt.col1 IS NULL 은 "대상에 없는 행" 을 고르는 조건인데, 여기에 OR src.col2 = tgt.col2 를 붙이면 이미 존재하고 값까지 같은 행까지 대상에 포함된다. 신규 행과 기존 동일 행이 모두 들어가므로 실행할 때마다 중복이 불어난다.
둘째, = 비교는 한쪽이 NULL 이면 참이 아니라 UNKNOWN 을 돌려준다. NULL = NULL 도 참이 아니다. 그래서 NULL 을 담은 컬럼으로 존재 여부를 판정하면 "없다" 로 판정되어 매번 다시 삽입된다.
Trino 는 IS NOT DISTINCT FROM 을 지원한다. 양쪽이 모두 NULL 이면 참, 한쪽만 NULL 이면 거짓으로 판정한다.
SELECT a IS NOT DISTINCT FROM b;
COALESCE 로 기본값을 씌워 비교하는 방법도 있지만, 실제 데이터에 그 기본값이 나타나면 서로 다른 행을 같다고 판정하게 된다. 가능하면 IS NOT DISTINCT FROM 을 쓴다.
INSERT INTO TBL_A (col1, col2, col3)
SELECT src.col1, src.col2, src.col3
FROM TBL_B src
WHERE NOT EXISTS (
SELECT 1
FROM TBL_A tgt
WHERE tgt.col1 = src.col1
AND tgt.col2 IS NOT DISTINCT FROM src.col2
AND tgt.col3 IS NOT DISTINCT FROM src.col3
);
LEFT JOIN 형태를 유지하려면 NULL 안전 비교를 JOIN 조건에 넣고 WHERE 에는 미매칭 판정만 남긴다.
INSERT INTO TBL_A (col1, col2, col3)
SELECT src.col1, src.col2, src.col3
FROM TBL_B src
LEFT JOIN TBL_A tgt
ON src.col1 = tgt.col1
AND src.col2 IS NOT DISTINCT FROM tgt.col2
AND src.col3 IS NOT DISTINCT FROM tgt.col3
WHERE tgt.col1 IS NULL;
원본 자체에 중복이 있으면 이 방법으로도 중복이 들어간다. 넣기 전에 원본을 한 번 줄인다.
WITH dedup AS (
SELECT col1, col2, col3,
ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY updated_at DESC) AS rn
FROM TBL_B
)
SELECT col1, col2, col3 FROM dedup WHERE rn = 1;
Iceberg · Delta Lake 처럼 행 수준 갱신을 지원하는 커넥터에서는 MERGE 가 가장 깔끔하다.
MERGE INTO TBL_A AS t
USING TBL_B AS s
ON t.col1 = s.col1
WHEN MATCHED THEN UPDATE SET col2 = s.col2, col3 = s.col3
WHEN NOT MATCHED THEN INSERT (col1, col2, col3) VALUES (s.col1, s.col2, s.col3);
MERGE 지원 여부는 커넥터마다 다르다. Hive 커넥터의 일반 테이블처럼 행 수준 갱신을 지원하지 않는 대상에서는 실행되지 않는다. 그런 경우는 대상 테이블을 새로 만들어 통째로 바꾸는 방식이 현실적이다.
ON CONFLICT ... DO NOTHING 은 PostgreSQL 문법이며 Trino 에는 없다. RDBMS 커넥터로 붙였더라도 Trino 가 해석하므로 쓸 수 없다.
MERGE 의 ON 조건이 원본에서 여러 행과 매칭되면 실행 중 오류가 난다. 원본을 먼저 유일하게 만든다.IS NOT DISTINCT FROM 은 인덱스나 푸시다운을 활용하지 못하는 경우가 있다. 대량 비교에서 느려지면 키 컬럼은 = 로 두고 NULL 이 실제로 나오는 컬럼에만 적용한다.