web-dev-qa-db-ja.com

Pythonを使用してOracleデータベースに一括挿入するにはどうすればよいですか?

Oracleデータベーステーブルに挿入する月間気象データがいくつかありますが、より効率的にするために、対応するレコードをバッチで挿入したいと考えています。 Pythonでこれを行う方法について誰かがアドバイスできますか?

たとえば、テーブルに駅ID、日付、2つの値フィールドの4つのフィールドがあるとします。レコードは、ステーションIDと日付フィールド(複合キー)によって一意に識別されます。各測点に挿入する必要のある値は、X年分のデータに相当するリストに保持されます。たとえば、2年の値がある場合、値リストには24個の値が含まれます。

一度に1つずつレコードを挿入したい場合は、以下のようにすると思います。

connection_string = "scott/tiger@testdb"
connection = cx_Oracle.Connection(connection_string)
cursor = cx_Oracle.Cursor(connection)
station_id = 'STATION_1'
start_year = 2000

temps = [ 1, 3, 5, 7, 9, 1, 3, 5, 7, 9, 1, 3 ]
precips = [ 2, 4, 6, 8, 2, 4, 6, 8, 2, 4, 6, 8 ]
number_of_years = len(temps) / 12
for i in range(number_of_years):
    for j in range(12):
        # make a date for the first day of the month
        date_value = datetime.date(start_year + i, j + 1, 1)
        index = (i * 12) + j
        sql_insert = 'insert into my_table (id, date_column, temp, precip) values (%s, %s, %s, %s)', (station_id, date_value, temps[index], precips[index]))
        cursor.execute(sql_insert)
connection.commit()

上記で行っていることを行う方法はありますが、効率を上げるためにバッチ挿入を実行する方法はありますか?ところで私の経験はJava/JDBC/Hibernateを使用しているので、誰かがJavaアプローチに匹敵する説明/例を与えることができるなら、それは特に役に立ちます。

編集:おそらく、私は説明されているようにcursor.executemany()を使用する必要があります ここ

提案、コメントなどをよろしくお願いします。

14
James Adams

私が思いついたものはこれでうまくいくようです(しかしこれを改善する方法がある場合はコメントしてください):

# build rows for each date and add to a list of rows we'll use to insert as a batch 
rows = [] 
numberOfYears = endYear - startYear + 1
for i in range(numberOfYears):
    for j in range(12):
        # make a date for the first day of the month
        dateValue = datetime.date(startYear + i, j + 1, 1)
        index = (i * 12) + j
        row = (stationId, dateValue, temps[index], precips[index])
        rows.append(row)

# insert all of the rows as a batch and commit
ip = '192.1.2.3' 
port = 1521
SID = 'my_sid'
dsn = cx_Oracle.makedsn(ip, port, SID)
connection = cx_Oracle.connect('username', 'password', dsn)
cursor = cx_Oracle.Cursor(connection)
cursor.prepare('insert into ' + database_table_name + ' (id, record_date, temp, precip) values (:1, :2, :3, :4)')
cursor.executemany(None, rows)
connection.commit()
cursor.close()
connection.close()
17
James Adams

Cursor.prepare()およびCursor.executemany()を使用します。

cx_Oracleドキュメント から:

Cursor.prepareステートメント[、タグ])

これは、execute()を呼び出す前に使用して、実行されるステートメントを定義できます。これが行われると、execute()への呼び出しがNoneまたはステートメントと同じ文字列オブジェクトで行われた場合、準備フェーズは実行されません。 [...]

Cursor.executemanyステートメントパラメータ

データベースに対して実行するステートメントを準備し、シーケンスパラメータで見つかったすべてのパラメータマッピングまたはシーケンスに対して実行します。ステートメントは、execute()メソッドが管理するのと同じ方法で管理されます。

したがって、上記の2つの関数を使用すると、コードは次のようになります。

connection_string = "scott/tiger@testdb"
connection = cx_Oracle.Connection(connection_string)
cursor = cx_Oracle.Cursor(connection)
station_id = 'STATION_1'
start_year = 2000

temps = [ 1, 3, 5, 7, 9, 1, 3, 5, 7, 9, 1, 3 ]
precips = [ 2, 4, 6, 8, 2, 4, 6, 8, 2, 4, 6, 8 ]
number_of_years = len(temps) / 12

# list comprehension of dates for the first day of the month
date_values = [datetime.date(start_year + i, j + 1, 1) for i in range(number_of_years) for j in range(12)]

# second argument to executemany() should be of the form:
# [{'1': value_a1, '2': value_a2}, {'1': value_b1, '2': value_b2}]
dict_sequence = [{'1': date_values[i], '2': temps[i], '3': precips[i]} for i in range(1, len(temps))]

sql_insert = 'insert into my_table (id, date_column, temp, precip) values (%s, :1, :2, :3)', station_id)
cursor.prepare(sql_insert)
cursor.executemany(None, dict_sequence)
connection.commit()

Oracleの Mastering Oracle + Python シリーズの記事も参照してください。

8
alldayremix

私のテスト結果:

5000行に挿入します。行ごとに3列。

  1. インサートを5000回実行すると、1.24分かかります。
  2. executemanyで実行すると、0.125秒かかります。
  3. 全コードを挿入して実行:4.08分かかります。

すべてをt(a、b、c)に挿入するようにsqlを設定するPythonコードselect:1、:2、:3 from dual union all select:4、:5::6 from daul ...

pythonこの長いSQLをセットアップするコードです。0.145329秒かかります。

非常に古いSunマシンでコードをテストします。 cpu:1415 MH。

3番目のケースでは、データベース側を確認しました。待機イベントは「SQL * Net more client from data from client」です。つまり、サーバーはクライアントからのデータを待っています。

3番目の方法の結果は、テストなしでは信じられないほどです。

したがって、私からの短い提案は、executemanyを使用することです。

4
zhihuifan

コメントの1つにあるように、_INSERT ALL_の使用を検討してください。おそらくexecutemany()を使用するよりも大幅に高速になります。

例えば:

_INSERT ALL
  INTO mytable (column1, column2, column_n) VALUES (expr1, expr2, expr_n)
  INTO mytable (column1, column2, column_n) VALUES (expr1, expr2, expr_n)
  INTO mytable (column1, column2, column_n) VALUES (expr1, expr2, expr_n)
SELECT * FROM dual;
_

http://www.techonthenet.com/Oracle/questions/insert_rows.php

3
ragerdl

Unionを使用して大きなSQL挿入ステートメントを作成します。

insert into mytable(col1, col2, col3)
select a, b, c from dual union
select d, e, f from dual union
select g, h, i from dual

pythonで文字列を作成し、実行する1つのステートメントとしてOracleに渡すことができます。

3
Derrick