0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【BigQuery】WITH句+DELETEでsyntax errorになる理由と解決策(MERGEで高速・安全に置き換える)

Posted at

はじめに

BigQueryを使っていると、データの更新や再登録処理で「一度DELETEしてからINSERTしたい」というケース、よくありますよね。

たとえば、「キーが重複する行を削除してから最新データを登録する」ようなパターンです。
ところが、そんな時に次のようなエラーを見たことはないでしょうか。

syntax error: Unexpected keyword DELETE at [xx:xx]

今回は、このエラーが出る理由と、正しい解決策を3通り紹介します。
さらに後半では、MERGEを使うことでなぜ高速化できるのか、その内部的な仕組みも解説します。

WITH句+DELETEがエラーになる理由

BigQueryでは、共通テーブル式(CTE)である WITH句 は SELECT / INSERT / UPDATE / MERGE の直前には書けますが、DELETE文の直前には書けません。

つまり、以下のようなSQLは構文エラーになります。

WITH tmp AS (
  SELECT id FROM my_table WHERE condition = TRUE
)
DELETE FROM my_table
WHERE id IN (SELECT id FROM tmp);

これを実行すると、次のようなメッセージが返ってきます。

syntax error: Unexpected keyword DELETE at [xx:xx]

BigQueryのSQLパーサーは「WITH句のあとにはSELECTなどが来るはず」と思っているのに、DELETEが登場したために構文エラーを出しているわけです。

対処法1:サブクエリで書く

一番シンプルな方法は、「WITH句をやめて、サブクエリとして書く」ことです。

DELETE FROM my_table
WHERE id IN (
  SELECT id
  FROM my_table
  WHERE condition = TRUE
);

WITH句 を使っていた部分をそのままサブクエリ化すればOK。
SQLとしては少しネストが深くなりますが、動作は同じです。

対処法2:一時テーブル(TEMP TABLE)を使う

もし WITH句 の中で複雑なJOINや集計処理をしている場合、サブクエリだと読みにくくなります。
そんな時は 一時テーブル を使うこともできます。

CREATE TEMP TABLE tmp AS
SELECT id
FROM my_table
WHERE condition = TRUE;

DELETE FROM my_table
WHERE id IN (SELECT id FROM tmp);

これなら、WITH句と同じように「一時的なデータセット」を作って参照できます。
CREATE TEMP TABLE はセッション中だけ存在するので、他のジョブやユーザーには影響しません。

対処法3:MERGE文でまとめて処理(おすすめ)

「既存データを削除して再登録する」目的であれば、実は MERGE文 を使う方がスマートです。

MERGEは、既存データと新データを突き合わせて、条件に応じて

  • UPDATE(上書き)
  • INSERT(新規追加)
  • DELETE(削除)

を1文で制御できる構文です。

例:重複キーを削除して再登録する処理

MERGE `project.dataset.target` AS t
USING (
  SELECT key, col1, col2
  FROM `project.dataset.source`
) AS s
ON t.key = s.key
WHEN MATCHED THEN
  UPDATE SET
    t.col1 = s.col1,
    t.col2 = s.col2
WHEN NOT MATCHED THEN
  INSERT (key, col1, col2)
  VALUES (s.key, s.col1, s.col2);

これで、「キーが一致すれば上書き、なければ新規挿入」が1文で完結します。
DELETE → INSERT の2ステップを1文に置き換えられるため、処理も高速・安全です。

DELETEを直接書くこともできる?

MERGEには「WHEN MATCHED THEN DELETE」という構文もあります。

MERGE `project.dataset.target` t
USING (
  SELECT key FROM `project.dataset.source`
) s
ON t.key = s.key
WHEN MATCHED THEN DELETE;

ただし、この場合は「DELETEしたあとにINSERT」まではできません。
同一MERGE内で“削除して再挿入”はサポートされていないため、上書き更新(UPDATE) が現実的です。

MERGEがDELETE+INSERTより速い理由

実は、BigQueryのMERGEは内部的にストレージI/Oを最小化するよう最適化されています。
以下のような違いがあります👇

処理方法 実行回数 スキャン範囲 書き込み回数 コスト(概算)
DELETE→ INSERT 2回 2回(全スキャン) 2回 約2倍
MERGE 1回 1回(JOIN結果のみ) 1回 約1/2〜1/3

DELETEとINSERTを別々に書くと、テーブルを2回スキャンし、書き込みも2回発生します。
一方、MERGEは内部でターゲットテーブルを1回スキャンし、JOIN後に必要な部分だけ更新または挿入します。

つまり、

  • 処理回数が少ない
  • データシャッフル(shuffle)も減る
  • コストも時間も削減できる

という3拍子そろったアプローチになります。

特に日次バッチや大量データの再取り込みでは、MERGEを使うだけでクエリ実行コストが30〜50%減ることも珍しくありません。

どの方法を使うべき?

方法 特徴 向いているケース
サブクエリ シンプルで早い 単純な削除
TEMP TABLE 可読性が高い 複雑なJOINや加工がある場合
MERGE 1文で完結&高速 DELETE+INSERTを一括で処理したい場合

実務でバッチ更新や差分取り込みを行うなら、MERGE一択。
シンプルに見えて、最も堅牢でスケーラブルな方法です。

まとめ

BigQueryでは WITH句+DELETEは構文エラーとなり、DELETE文の前にWITHを置けるのはPostgreSQLやMySQL8以降のみ

BigQueryで同等処理をしたい場合は以下のいずれかで対処する

-- ✅ サブクエリで書く

DELETE FROM table WHERE id IN (SELECT id FROM ...);

-- ✅ TEMP TABLEで中継
CREATE TEMP TABLE tmp AS SELECT id FROM ...;
DELETE FROM table WHERE id IN (SELECT id FROM tmp);

-- ✅ MERGEで一括処理(推奨)
MERGE table USING source ON key ... 
WHEN MATCHED THEN UPDATE ...
WHEN NOT MATCHED THEN INSERT ...;

MERGEはDELETE+INSERTよりもI/O効率が高く、コストも下がる。
「再登録」や「差分更新」処理を行うなら、まずMERGEを検討してみましょう。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?