別の表からデータを移行INSERT … SELECT解説

Oracle Master Bronze

Oracle で別の表を参照してデータを一度に移す・加工して取り込むなら insert select を使用すると便利です。本記事では select insert の正しい書き方、性能チューニング、ロックとログの注意点を、Oracle Database 19cで“そのまま動く例”+SQLの意味の解説つきで紹介します。

テーブルの作成についてはコチラ。

💰 【PR】Oracleエンジニアの市場価値、調べてみませんか?

Oracleのスキルは需要が高く、特定の資格や経験を持っていると年収が大幅にアップするケースがあります。まずはIT専門のエージェントで非公開求人をチェックしてみませんか?


基本:構文と考え方(まずはここから)

INSERT ... SELECT は、SELECTの結果をそのまま別表にINSERTする文です。大切なのは「列の数・順序・データ型」を合わせることです。

-- 基本構文(列名は明示するのが安全)
INSERT INTO 目標表 (列A, 列B, 列C, ...)
SELECT 式A, 式B, 式C, ...
  FROM 参照表
 WHERE 条件;

SQLの意味(やさしく)

  • INSERT INTO 目標表 (列...) …… 「どの表のどの列に入れるか」を先に宣言
  • SELECT 式... FROM 参照表 …… 入れる“材料”をSELECTで作る(列名・計算式・関数OK)
  • WHERE 条件 …… 取り込む行を選別(フィルタ)
  • ポイント:INSERTで列を省略すると DEFAULT(なければ NULL)。明示的に NULL を入れてもOK

テキスト図:INSERT … SELECT の流れ(イメージ)

┌──────────── SELECT 側(材料づくり) ────────────┐
│ 参照表やビューを読み、必要なら計算・置換・集計を行う │
└───────────────┬───────────────────┘
                    │ できた行セット(結果)
┌───────────────▼───────────────────┐
│           INSERT 受け側の表(保存先)           │
│ 制約チェック → 索引更新 → UNDO/REDO(ログ)生成 │
└────────────────────────────────┘

まずは準備(ユーザー作成・権限・テーブル定義)

※CDB環境では対象PDBへ切り替えてから行います。

-- (SYSDBA) ユーザーと権限
CREATE USER appuser IDENTIFIED BY "Str0ng#Pass"
  DEFAULT TABLESPACE users QUOTA UNLIMITED ON users;
GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE TO appuser;

SQLの意味

  • CREATE USER …… ログイン口(スキーマ)を作成
  • DEFAULT TABLESPACE …… そのユーザーが標準で使う表領域
  • QUOTA UNLIMITED …… users表領域を容量制限なしで使える
  • GRANT ... …… ログイン(CREATE SESSION)、表作成、シーケンス作成を許可
-- (APPUSER) ソース表・ターゲット表
CREATE TABLE emp_src (
  id        NUMBER        PRIMARY KEY,
  name      VARCHAR2(50)  NOT NULL,
  dept_id   NUMBER,
  sal       NUMBER(10,2),
  hired_on  DATE          DEFAULT SYSDATE
);

-- サンプルデータ
INSERT INTO emp_src VALUES (1,'Sato',   10, 350000, TO_DATE('2024-04-01','YYYY-MM-DD'));
INSERT INTO emp_src VALUES (2,'Suzuki', 20, 420000, SYSDATE);
INSERT INTO emp_src VALUES (3,'Tanaka', 10, 500000, SYSDATE);
COMMIT;

-- 受け側(IDはIDENTITYで自動採番:“新しいIDを振り直す”設計)
CREATE TABLE emp_tgt (
  id        NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  name      VARCHAR2(50) NOT NULL,
  dept_id   NUMBER,
  sal       NUMBER(10,2),
  hired_on  DATE DEFAULT SYSDATE,
  status    CHAR(1) DEFAULT 'A' CHECK (status IN ('A','I'))
);

SQLの意味

  • PRIMARY KEY …… 一意+NOT NULLの主キー制約
  • DEFAULT SYSDATE …… 行を入れる時刻が自動で入る
  • IDENTITY …… 受け側で 自動採番(IDを自分で入れなくてよい)
  • CHECK (status IN ('A','I')) …… AIしか入らない制約

代表パターンと書き方(例と“SQLの意味”つき)

1) そのままコピー(列並びが同じ)

INSERT INTO emp_tgt (name, dept_id, sal, hired_on)
SELECT name, dept_id, sal, hired_on
  FROM emp_src;
COMMIT;
コマンド実行例

SQL> col name for a10
SQL> SELECT * FROM emp_src;

ID NAME DEPT_ID SAL HIRED_ON
---------- ---------- ---------- ---------- ---------
1 Sato 10 350000 01-APR-24
2 Suzuki 20 420000 23-AUG-25
3 Tanaka 10 500000 23-AUG-25

SQL> SELECT * FROM emp_tgt;

no rows selected

SQL> INSERT INTO emp_tgt (name, sal, dept_id, hired_on)
2 SELECT name, sal, dept_id, hired_on
3 FROM emp_src
4 WHERE dept_id IS NOT NULL;

3 rows created.

SQL> SELECT * FROM emp_tgt;

ID NAME DEPT_ID SAL HIRED_ON S
---------- ---------- ---------- ---------- --------- -
1 Sato 10 350000 01-APR-24 A
2 Suzuki 20 420000 23-AUG-25 A
3 Tanaka 10 500000 23-AUG-25 A

SQLの意味

  • SELECT ... FROM emp_src …… 元の列をそのまま取り出す
  • 受け側は IDENTITY なので id は書かない(DBが自動採番)
  • COMMIT …… ここで確定。ロールバックされなくなる

2) 列の並べ替え・名称差異を合わせる

INSERT INTO emp_tgt (name, sal, dept_id, hired_on)
SELECT name, sal, dept_id, hired_on
  FROM emp_src
 WHERE dept_id IS NOT NULL;

SQLの意味

  • 受け側の列リスト順に合わせてSELECT側の並びも合わせる
  • WHERE dept_id IS NOT NULL …… 不正データの混入を防止

3) 定数・式・関数を混ぜる(取り込み時に加工)

INSERT INTO emp_tgt (name, dept_id, sal, hired_on, status)
SELECT name,
       dept_id,
       ROUND(sal * 1.05, 2),               -- 5%上乗せ&小数2桁
       NVL(hired_on, TRUNC(SYSDATE)),      -- nullなら今日に置き換え
       'A'                                  -- 取り込み時に一律'A'
  FROM emp_src
 WHERE sal >= 400000;
コマンド実行例

SQL> INSERT INTO emp_tgt (name, dept_id, sal, hired_on, status)
2 SELECT name,
3 dept_id,
4 ROUND(sal * 1.05, 2),
5 NVL(hired_on, TRUNC(SYSDATE)),
6 'A'
7 FROM emp_src
8 WHERE sal >= 400000;

2 rows created.

SQL> SELECT * FROM emp_tgt;

ID NAME DEPT_ID SAL HIRED_ON S
---------- ---------- ---------- ---------- --------- -
1 Sato 10 350000 01-APR-24 A
2 Suzuki 20 420000 23-AUG-25 A
3 Tanaka 10 500000 23-AUG-25 A
4 Suzuki 20 441000 23-AUG-25 A ★
5 Tanaka 10 525000 23-AUG-25 A ★

SQLの意味

  • ROUND/NVL …… 取り込みのその場で必要な加工
  • status='A'定数で入れる
  • WHERE sal >= ... …… 条件で取り込む対象を絞る

💰 【PR】Oracleエンジニアの市場価値、調べてみませんか?

Oracleのスキルは需要が高く、特定の資格や経験を持っていると年収が大幅にアップするケースがあります。まずはIT専門のエージェントで非公開求人をチェックしてみませんか?

4) 重複を除いて取り込む(DISTINCT)

INSERT INTO emp_tgt (name, dept_id, sal, hired_on)
SELECT DISTINCT name, dept_id, sal, hired_on
  FROM emp_src;

SQLの意味

  • DISTINCT …… 完全同一の行は1回だけ取り込む

5) 集計してサマリ表へ

CREATE TABLE sales_summary (
  dept_id NUMBER,
  y       NUMBER,
  m       NUMBER,
  total_amt NUMBER
);

INSERT INTO sales_summary (dept_id, y, m, total_amt)
SELECT dept_id,
       EXTRACT(YEAR  FROM hired_on),
       EXTRACT(MONTH FROM hired_on),
       SUM(sal)
  FROM emp_src
 GROUP BY dept_id,
          EXTRACT(YEAR  FROM hired_on),
          EXTRACT(MONTH FROM hired_on);

SQLの意味

  • EXTRACT(YEAR/MONTH ...) …… 年・月を切り出す
  • SUMGROUP BY …… 部門×年月の合計を作る → それをINSERT

ID採番との付き合い方(IDENTITY / SEQUENCE)

受け側が IDENTITY のとき

  • GENERATED ALWAYS …… 常にDB採番(列に値を指定するとエラー)
  • BY DEFAULT …… 通常はDB採番、必要なら手動指定も可
-- 新規採番させる(ID列は書かない)
INSERT INTO emp_tgt (name, dept_id, sal, hired_on)
SELECT name, dept_id, sal, hired_on FROM emp_src;

-- 既存IDを維持して移行(BY DEFAULT の性質を利用)
INSERT INTO emp_tgt (id, name, dept_id, sal, hired_on)
SELECT id, name, dept_id, sal, hired_on FROM emp_src;
コマンド実行例

SQL> INSERT INTO emp_tgt (name, dept_id, sal, hired_on)
2 SELECT name, dept_id, sal, hired_on FROM emp_src;

3 rows created.

SQL> SELECT * FROM emp_tgt order by id;

ID NAME DEPT_ID SAL HIRED_ON S
---------- ---------- ---------- ---------- --------- -
1 Sato 10 350000 01-APR-24 A
2 Suzuki 20 420000 23-AUG-25 A
3 Tanaka 10 500000 23-AUG-25 A
4 Suzuki 20 441000 23-AUG-25 A
5 Tanaka 10 525000 23-AUG-25 A
6 Sato 10 350000 01-APR-24 A
7 Suzuki 20 420000 23-AUG-25 A
8 Tanaka 10 500000 23-AUG-25 A
9 Sato 10 350000 01-APR-24 A
10 Suzuki 20 420000 23-AUG-25 A
11 Tanaka 10 500000 23-AUG-25 A

11 rows selected.

SQL> INSERT INTO emp_tgt (id, name, dept_id, sal, hired_on)
2 SELECT id, name, dept_id, sal, hired_on FROM emp_src;
INSERT INTO emp_tgt (id, name, dept_id, sal, hired_on)
*
ERROR at line 1:
ORA-00001: unique constraint (APPUSER.SYS_C007542) violated ★エラーになる

SQLの意味

  • 前者:IDは自動採番。ソースのidは使わない
  • 後者:既存IDを持ち込む(重複や一意制約に注意)

受け側が SEQUENCE のとき

CREATE SEQUENCE emp_seq START WITH 1000 INCREMENT BY 1;

INSERT INTO emp_tgt (id, name, dept_id, sal, hired_on)
SELECT emp_seq.NEXTVAL, name, dept_id, sal, hired_on
  FROM emp_src;

SQLの意味

  • NEXTVAL …… 次の番号を発行して id に入れる
  • 既存データと番号帯がぶつからないよう START WITH を調整


シーケンスについてはコチラ。


select insert の注意点(性能・ロック・ログ)

通常パス vs ダイレクトパス(APPEND)

-- 大量ロード向け(HWMの上にまとめて書く)
INSERT /*+ APPEND */ INTO emp_tgt (name, dept_id, sal, hired_on)
SELECT name, dept_id, sal, hired_on FROM emp_src WHERE dept_id IS NOT NULL;
COMMIT;

SQLの意味と注意

  • /*+ APPEND */ …… ダイレクトパスで高速化(新規エクステントへ直書き)
  • 目標表に強めのロックが必要になり、他トランザクションと競合しやすい
  • 既存ブロックを詰めない分、後で表が大きくなることがある

並列DML

ALTER SESSION ENABLE PARALLEL DML;

INSERT /*+ APPEND PARALLEL(emp_tgt,4) */ INTO emp_tgt (name, dept_id, sal, hired_on)
SELECT /*+ PARALLEL(emp_src,4) */ name, dept_id, sal, hired_on
  FROM emp_src;
COMMIT;

SQLの意味と注意

  • ENABLE PARALLEL DML …… 並列挿入を許可
  • PARALLEL(emp_tgt,4) …… 受け側/読み側の並列度を指定
  • 同トランザクション内で直後に対象表を読むと制限があるためコミット境界を意識

ログ最小化(NOLOGGING)の使いどころ

ALTER TABLE emp_tgt NOLOGGING;      -- REDO縮小(完全に0ではない)
INSERT /*+ APPEND */ INTO emp_tgt (name, dept_id, sal, hired_on)
SELECT name, dept_id, sal, hired_on FROM emp_src;
ALTER TABLE emp_tgt LOGGING;        -- 終了後に戻す

SQLの意味と注意

  • NOLOGGING …… REDOログを減らして高速化。ただし媒体障害時の復旧で再現不能部分が出る恐れ
  • 適用範囲を限定し、事前/事後バックアップを計画

取り込み結果の確認(検算・サンプルチェック)

-- 件数が一致するか(ざっくり検算)
SELECT COUNT(*) FROM emp_src;
SELECT COUNT(*) FROM emp_tgt;

-- サンプル表示(内容に違和感がないか)
SELECT id, name, dept_id, sal, hired_on, status
  FROM emp_tgt
 ORDER BY id
 FETCH FIRST 10 ROWS ONLY;

SQLの意味

  • 件数一致は最低限のチェック
  • ランダムサンプル(FETCH FIRST)で目視確認
  • 必要なら MIN/MAX/AVG、部門別件数など集計で突合する

よくあるエラーと直し方(逆引き)

ORA-00913: 値の個数が多すぎます。
→ SELECT列が多すぎ。INSERT側の列リストと列数を合わせる。

ORA-00947: 値の個数が不足しています。
→ SELECT列が足りない。INSERT側の列リストと列数を合わせる。

ORA-00001: 一意制約(string.string)に反しています
→ 一意制約の重複。重複排除・ID採番の見直し・事前チェック。

ORA-02291: 整合性制約(string.string)に違反しました - 親キーがありません→ 外部キーの親が存在しない。ロード順序や参照整合の調整。

ORA-01536: 表領域stringに対する領域割当て制限を使い果たしました。
→ クォータ不足。表領域の割当(QUOTA)やディスク残量を確認。

まとめ

  • INSERT … SELECT列数・型・順序の整合が最重要。列名は明示が安全。
  • ID採番は IDENTITY/SEQUENCE を要件で使い分け、重複や制約違反を設計で防ぐ。
  • 大量ロードは APPEND/並列DML/NOLOGGING を適切に使い、ロックと可用性のトレードオフを理解。
  • ロード後は 件数検証+サンプル確認、必要なら統計更新や索引メンテも検討。

本記事は Oracle Database 19c を対象に解説します(他バージョンは画面や既定値が異なる場合があります)。


[参考]
INSERT

💰 【PR】Oracleエンジニアの市場価値、調べてみませんか?

Oracleのスキルは需要が高く、特定の資格や経験を持っていると年収が大幅にアップするケースがあります。まずはIT専門のエージェントで非公開求人をチェックしてみませんか?

コメント

タイトルとURLをコピーしました