web-dev-qa-db-ja.com

テーブル内のすべての行を更新する効率的な方法

多くのレコードを含むテーブルがあります(500 000または1 000 000を超える場合があります)。このテーブルに新しい列を追加し、このテーブルの別の列の対応する行の値を使用して、列のすべての行に値を入力する必要があります。

100レコードの次のチャンクを選択するために個別のトランザクションを使用し、それらの値を更新しようとしましたが、それでも、たとえばOracle10のすべてのレコードを更新するには数時間かかります。

方言固有の機能を使用せずにSQLでこれを行う最も効率的な方法は何ですか?それでどこでも動作します(Oracle、MSSQL、MySQL、PostGreなど)?

追加情報:計算フィールドはありません。インデックスがあります。行ごとにテーブルを更新する生成されたSQLステートメントを使用しました。

34
m_pGladiator

通常の方法は、UPDATEを使用することです。

UPDATE mytable
   SET new_column = <expr containing old_column>

これは単一のトランザクションで実行できるはずです。

53
Marcelo Cantos

マルセロが示唆するように:

UPDATE mytable
SET new_column = <expr containing old_column>;

「スナップショットが古すぎる」エラーが原因でこれに時間がかかりすぎて失敗する場合(式が別の非常にアクティブなテーブルをクエリする場合など)、列の新しい値が常にNOT NULLである場合、バッチでテーブルを更新できます:

UPDATE mytable
SET new_column = <expr containing old_column>
WHERE new_column IS NULL
AND ROWNUM <= 100000;

このステートメントCOMMITを実行してから、もう一度実行してください。すすぎ、「0行が更新されました」と報告されるまで繰り返します。時間がかかりますが、各更新が失敗する可能性は低くなります。

編集:

より効率的なはずのより良い代替手段は、DBMS_PARALLEL_EXECUTE AP​​I。

サンプルコード(Oracleドキュメントから):

DECLARE
  l_sql_stmt VARCHAR2(1000);
  l_try NUMBER;
  l_status NUMBER;
BEGIN

  -- Create the TASK
  DBMS_PARALLEL_EXECUTE.CREATE_TASK ('mytask');

  -- Chunk the table by ROWID
  DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_ROWID('mytask', 'HR', 'EMPLOYEES', true, 100);

  -- Execute the DML in parallel
  l_sql_stmt := 'update EMPLOYEES e 
      SET e.salary = e.salary + 10
      WHERE rowid BETWEEN :start_id AND :end_id';
  DBMS_PARALLEL_EXECUTE.RUN_TASK('mytask', l_sql_stmt, DBMS_SQL.NATIVE,
                                 parallel_level => 10);

  -- If there is an error, RESUME it for at most 2 times.
  l_try := 0;
  l_status := DBMS_PARALLEL_EXECUTE.TASK_STATUS('mytask');
  WHILE(l_try < 2 and l_status != DBMS_PARALLEL_EXECUTE.FINISHED) 
  LOOP
    l_try := l_try + 1;
    DBMS_PARALLEL_EXECUTE.RESUME_TASK('mytask');
    l_status := DBMS_PARALLEL_EXECUTE.TASK_STATUS('mytask');
  END LOOP;

  -- Done with processing; drop the task
  DBMS_PARALLEL_EXECUTE.DROP_TASK('mytask');

END;
/

Oracle Docs: https://docs.Oracle.com/database/121/ARPLS/d_parallel_ex.htm#ARPLS673

8
Jeffrey Kemp

テーブルのインデックスを削除し、挿入してからインデックスを再作成できます。

2
Timothy

ホテルセットの割引= 30で、Hotelid> = 1およびHotelid <= 5504

0
user3098137

うまくいかないかもしれませんが、似たような状況で過去に何度か使ったテクニックです。

updated_ {table_name}を作成し、バッチでこのテーブルに挿入を選択します。終了したら、これはOracle(私は知らないか使用していません)に依存し、アトミックな方法でテーブルの名前を変更する機能をサポートします。 updated_ {table_name}は{table_name}になり、{table_name}はoriginal_ {table_name}になります。

前回これをしなければならなかったのは、数百万行のインデックスが大量に作成されたテーブルで、重大な変更を行うために必要な期間、確実にロックできなかった場合です。

0
David

データベースのバージョンは何ですか? 11gの仮想列をチェックアウトします。

デフォルト値を持つ列の追加 http://www.Oracle.com/technology/pub/articles/Oracle-database-11g-top-features/11g-schemamanagement.html

0
Stellios