"기준 테이블 B 에 있는 키에 해당하는 행을 대상 테이블 A 에서 지운다" 는 작업을 Impala 에서 쓰려 할 때, 다른 DBMS 의 문법을 그대로 옮기면 거의 다 막힌다. 어떤 문법이 되고 어떤 문법이 안 되는지부터 정리한다.
DML(DELETE · UPDATE · UPSERT)은 Kudu 테이블에서만 동작한다. Iceberg 테이블은 별도의 지원 범위가 있고, 일반 HDFS · Parquet 테이블에서는 아예 쓸 수 없다.
| 문법 | Impala | 비고 |
|---|---|---|
DELETE FROM t WHERE ... |
가능 | 기본형 |
DELETE t FROM t JOIN b ON ... WHERE ... |
가능 | 조인 기반 삭제의 정식 문법 |
DELETE FROM t WHERE EXISTS (SELECT 1 FROM b WHERE ...) |
가능 | 상관 서브쿼리 |
DELETE FROM t WHERE c IN (SELECT c FROM b) |
가능 | 단일 컬럼 |
DELETE FROM t USING b WHERE ... |
불가 | PostgreSQL 문법 |
DELETE FROM t alias WHERE ... |
불가 | 단일 테이블 형태에는 별칭을 못 붙인다 |
WHERE (c1, c2) IN (SELECT c1, c2 FROM b) |
불가 | 다중 컬럼 튜플 IN 미지원 |
MERGE INTO ... |
Iceberg V2 테이블에 한해 가능 | Kudu 테이블은 불가 |
USING 을 쓰면 파서에서 막히고, 단일 테이블 DELETE 에 별칭을 붙여도 문법 오류가 난다. 조인이 필요하면 삭제 대상을 앞에 적는 두 번째 형태를 쓴다. 이 형태에서는 별칭을 쓸 수 있다.
DELETE a
FROM erp_mfgplan a
JOIN ext_mfgplan x
ON a.plant = x.plant
AND a.onhanddate = x.onhanddate;
상관 서브쿼리를 쓴다면 삭제 대상에 별칭이 없으므로 테이블 이름을 그대로 적어 참조한다.
DELETE FROM erp_mfgplan
WHERE EXISTS (
SELECT 1
FROM ext_mfgplan x
WHERE erp_mfgplan.plant = x.plant
AND erp_mfgplan.onhanddate = x.onhanddate
);
기준 테이블에 중복이 있어도 EXISTS 는 존재 여부만 보므로 결과가 달라지지 않는다. 반면 조인 형태는 중복만큼 같은 행을 여러 번 매칭하므로, 계획이 커지는 것을 피하려면 서브쿼리에서 DISTINCT 로 줄여 주는 편이 낫다.
DELETE a
FROM erp_mfgplan a
JOIN (SELECT DISTINCT plant, onhanddate FROM ext_mfgplan) x
ON a.plant = x.plant AND a.onhanddate = x.onhanddate;
Kudu 테이블에 MERGE INTO 를 쓰면 파서 단계에서 막힌다.
AnalysisException: Syntax error ... encountered: unknown last token with id: 229
Impala 의 MERGE 는 Iceberg V2 테이블을 대상으로 도입된 기능이라 Kudu 테이블에서는 쓸 수 없다. Kudu 에서 "있으면 갱신, 없으면 삽입" 은 UPSERT 로 한다.
UPSERT INTO nscm.sales
SELECT gbm, section, amount, load_dt
FROM ext_sales;
UPSERT 는 기본 키가 일치하면 행 전체를 덮어쓴다. 삽입할 때와 갱신할 때 채워야 할 컬럼이 다르다면 UPSERT 로는 표현할 수 없고, INSERT 와 UPDATE 두 문장으로 나눠야 한다.
확인 필요 — MERGE 지원 여부와 대상 테이블 형식은 배포판 버전에 따라 다르다. 도입 전에 쓰는 클러스터에서 간단한 문장으로 직접 시험한다.
INSERT ... SELECT ... WHERE NOT EXISTS (...) 는 문법상 되지만, 대상 테이블 전체를 읽는 계획이 잡혀 느려지는 경우가 많다. Kudu 테이블이라면 더 단순한 방법이 있다.
기본 키가 겹치는 행을 그냥 무시하고 싶으면 INSERT 대신 UPSERT 를 쓴다. 기존 행을 유지해야 한다면 NOT EXISTS 대신 안티 조인으로 쓰는 편이 계획이 안정적이다.
INSERT INTO nscm.sales
SELECT c.*
FROM ext_sales c
LEFT ANTI JOIN nscm.sales k
ON c.gbm = k.gbm AND c.section = k.section;
기본 키 컬럼으로 조인하면 Kudu 쪽에 키 조건을 밀어 넣을 수 있어 전체 스캔을 피할 여지가 생긴다. 다만 실제로 밀렸는지는 계획으로 확인해야 한다.
EXPLAIN INSERT INTO ... ;
계획에 kudu predicates: 가 보이면 술어가 스토리지로 내려간 것이고, 보이지 않으면 Impala 가 전체를 읽어 필터링하고 있다는 뜻이다. 조인 대상이 작으면 브로드캐스트 조인이 잡혀야 유리하므로 COMPUTE STATS 를 먼저 돌려 통계를 갖춰 둔다.
생성된 SQL 에 값을 나열하는 방식은 어느 선을 넘으면 실패한다.
IN 목록이나 자동 생성된 거대한 문장은 이 상한에 걸려 거부된다.확인 필요 — 대화 기록에는 "쿼리 길이 1MB" 라는 값이 나오지만 근거 문서가 제시되지 않았다. 버전 · 배포판마다 다를 수 있으므로 상한을 설계 전제로 삼지 말고, 아래처럼 목록을 문장에 박지 않는 구조로 바꾸는 편이 안전하다.
-- 값 목록을 임시 테이블에 넣고 조인한다
CREATE TABLE tmp_keys (k STRING PRIMARY KEY) STORED AS KUDU;
-- (적재 후)
DELETE a FROM target a JOIN tmp_keys t ON a.k = t.k;