3

単一の SQL ステートメントを使用したい

insert into T (...) select ... from T where ...

大量のデータセットをコピーします。私の問題は、テーブルTから他のテーブルへの N:M 関係があり、これらもコピーする必要があることです。どの元のデータセットがどのコピーされたデータセットに属するかわからない場合、どうすればよいですか? 例を挙げて説明しましょう。

前のデータベースの内容:

T:

ID  | COL1 | COL2    
-----------------
1   | A    | B
2   | C    | D

N:M テーブルは、テーブル T からテーブル U を参照します (テーブル U は表示されていません):

T   | U              
---------
1   | 100
1   | 101
2   | 100
2   | 102

[???] が不明な部分である私のコピー操作:

insert into T (COL1, COL2) select COL1, COL2 from T
insert into NM (T, U) select [???]

後のデータベースの内容:

T:

ID  | COL1 | COL2
-----------------
1   | A    | B
2   | C    | D
3   | A    | B
4   | C    | D

N:M テーブル:

T   | U
---------
1   | 100
1   | 101
2   | 100
2   | 102
3   | 100
3   | 101
4   | 100
4   | 102

知らせ:

  • 何千ものデータセットがあります (2 つだけではありません)
  • 「挿入...選択」を使用してパフォーマンスを向上させたい
4

2 に答える 2

4

幸運にも現在のPostgreSQL 9.1を実行できる場合は、新しいデータ変更 CTEを使用した1 つのコマンドによるエレガントで高速なソリューションがあります。

データ変更 CTE は言うまでもなく、Common Table Expressions (CTE)をサポートしていないMySQLでは、そのような運はありません。

(col1, col2)最初は一意であると仮定します。

クエリ 1

  • この場合、テーブルから任意のスライスを簡単に選択できます。
  • のシーケンス番号はt.id無駄になりません。

WITH s AS (
    SELECT id, col1, col2
    FROM   t
--  WHERE  some condition
    )
    ,i AS (
    INSERT INTO t (col1, col2)
    SELECT col1, col2   -- I gather from comments that id is a serial column
    FROM   s
    RETURNING id, col1, col2
    )
INSERT INTO tu (t, u)
SELECT i.id, tu.u
FROM   tu
JOIN   s ON tu.t = s.id
JOIN   i USING (col1, col2);

(col1, col2)unique でない場合、他に 2 つの方法があります。

クエリ 2

  • ウィンドウ関数row_number()を使用して、一意でない行を一意にします。
  • INSERTt.id上記のクエリと同様に、スペースに穴のない行。

WITH s AS (
    SELECT id, col1, col2
         , row_number() OVER (PARTITION BY col1, col2) AS rn
    FROM   t
--  WHERE some condition
    )
    ,i AS (
    INSERT INTO t (col1, col2)
    SELECT col1, col2
    FROM   s
    RETURNING id, col1, col2
    )
    ,r AS (
    SELECT *
         , row_number() OVER (PARTITION BY col1, col2) AS rn
    FROM   i
    )
INSERT INTO tu (t, u)
SELECT r.id, tu.u
FROM   r
JOIN   s USING (col1, col2, rn)    -- match exactly one id per row
JOIN   tu ON tu.t = s.id;

クエリ 3

  • これは、@ypercube が既に提供したのと同じアイデアに基づいていますが、すべて 1 つのクエリです。
  • current の数値スペースに穴がある場合t.id、それに応じてシーケンス番号が新しい行に焼き付けられます。
  • 新しい最大値を超えてシーケンスをリセットすることを忘れないでください。そうしないと、シーケンスからtデフォルトを描画する新しい挿入に対して重複キーエラーが発生しidます。これをコマンドの最終ステップとして統合しました。この方法が最も速く安全です。

WITH s AS (
    SELECT max(id) AS max_id
    FROM   t
    )
    ,i AS (
    INSERT INTO t (id, col1, col2)
    SELECT id + s.max_id, col1, col2
    FROM   t, s
    )
    ,j AS (
    INSERT INTO tu (t, u)
    SELECT tu.t + s.max_id, tu.u
    FROM   tu, s
    )
SELECT setval('t_id_seq', s.max_id + s.max_id)
FROM   s;

マニュアルのsetval()に関する詳細。

テスト設定

簡単なテスト用。

CREATE TEMP TABLE t (id serial primary key, col1 text, col2 text);
INSERT INTO t (col1, col2) VALUES 
 ('A', 'B')
,('C', 'D');

CREATE TEMP TABLE tu (t int, u int);
INSERT INTO tu VALUES
 (1, 100)
,(1, 101)
,(2, 100)
,(2, 102);

SELECT * FROM t;
SELECT * FROM tu;

最近、やや似たような質問があり、やや似た回答を提供しました。CTE とウィンドウ関数のないバージョン8.3のプラスの代替。

于 2012-04-18T00:59:27.363 に答える
1

手順1.(両方の)テーブルをロックするか、このスクリプトのみが実行されていることを確認します。FKチェックを無効にします。

ステップ2.次の2つのINSERTステートメントをこの順序で使用します。

INSERT INTO NM 
    (T, U) 
  SELECT 
      T + maxID, U
  FROM 
      NM
    CROSS JOIN
      ( SELECT MAX(ID) AS maxID 
        FROM T
      ) AS m

INSERT INTO T 
    (ID, COL1, COL2) 
  SELECT 
      ID+maxID, COL1, COL2 
  FROM 
      T
    CROSS JOIN
      ( SELECT MAX(ID) AS maxID 
        FROM T
      ) AS m

手順3.FKを再度有効にします。

于 2012-04-17T12:31:41.593 に答える