本文へスキップ
BecomeCoder

SQL データ操作 (DML)コース · 第4章 データを変える · レッスン19

UPSERT ― 「あれば更新、なければ追加」

ブラウザで完結

導入

「そのIDの行がすでにあれば更新、なければ新規に追加」――この「あれば更新・なければ挿入」は、在庫の取り込みや設定の保存など、実務でものすごくよく出てきます。素直に書くと「まず SELECT して、あるか調べて、INSERTUPDATE を選ぶ」と3手かかりますが、これを1文でやるのが UPSERT(update + insert)です。

説明

SQLite では INSERT ... ON CONFLICT(...) DO UPDATE で書きます。「主キーや UNIQUE 制約とぶつかったら(=すでに同じ行がいたら)、代わりに UPDATE する」という意味です。

flowchart TB
    I["INSERT を試みる"] --> Q{"同じ id が<br/>すでにある?"}
    Q -->|"ない"| A["そのまま挿入される"]
    Q -->|"ある(衝突)"| U["ON CONFLICT で指定した<br/>UPDATE が実行される"]
INSERT INTO users (id, name, age, city) VALUES (1, '佐藤', 28, '那覇')
ON CONFLICT(id) DO UPDATE SET city = excluded.city;

ポイントは excluded という特別な名前です。これは「いま挿入しようとした(が弾かれた)値」を指します。上の例なら excluded.city'那覇'。つまり「id=1 がすでにあるので、その人の city を、挿入しようとした '那覇' に更新する」という動きになります。id=1 は既存なので、結果は UPDATE。存在しない id を指定すれば、ふつうに INSERT されます。

DO NOTHING を使うと「衝突したら何もしない(重複はそっと無視)」にもできます: INSERT ... ON CONFLICT(id) DO NOTHING;

やってみよう

初期表示の UPSERT を実行し、続く SELECT で佐藤さん(id=1)の city が「那覇」に更新されたことを確認しましょう(id=1 は既存なので更新側が動きます)。次に VALUES の id を存在しない 99 に変えて実行すると、今度は衝突が起きず新規追加されることを見比べてください。

演習

UPSERT を使って、categories の id=1 の行について「同じ id があれば name'食品・飲料' に更新する」1文を書いてください(ON CONFLICT(id) DO UPDATE を使い、更新値は excluded.name を使います)。

ヒント1を見る

INSERT INTO categories (id, name) VALUES (1, '食品・飲料') ON CONFLICT(id) DO UPDATE SET name = excluded.name;

ヒント2を見る

excluded.name が「挿入しようとした name(=‘食品・飲料’)」を指します。

実際に動かしてみよう

下のエディタにクエリを書いて「実行」を押すと、ブラウザ内のSQLiteで結果が表示されます。本文の例をそのまま試したり、書き換えたりしてみましょう。

SQL — ブラウザ内で実行(SQLite)

ブラウザ内でSQLを動かす環境(SQLite)を読み込みます(初回のみ一瞬)。
スクロールして表示された時点でも自動で読み込まれます。