web-dev-qa-db-fra.com

Comment puis-je effectuer une insertion par lots dans une base de données Oracle à l'aide de Python?

J'ai des données météorologiques mensuelles que je veux insérer dans une table de base de données Oracle mais je veux insérer les enregistrements correspondants dans un lot afin d'être plus efficace. Quelqu'un peut-il me dire comment je procéderais en Python?

Par exemple, supposons que ma table comporte quatre champs: un ID de station, une date et deux champs de valeur. Les enregistrements sont identifiés de manière unique par les champs ID de station et date (clé composite). Les valeurs que je devrai insérer pour chaque station seront conservées dans une liste avec X nombre d'années complètes de données, donc par exemple s'il y a deux années de valeurs, alors les listes de valeurs contiendront 24 valeurs.

Je suppose que ci-dessous est la façon dont je ferais cela si je voulais insérer les enregistrements un par un:

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()

Existe-t-il un moyen de faire ce que je fais ci-dessus mais d'une manière qui effectue une insertion par lots afin d'augmenter l'efficacité? BTW mon expérience est avec Java/JDBC/Hibernate donc si quelqu'un peut donner une explication/exemple qui se compare à l'approche Java alors ce serait particulièrement utile.

EDIT: J'ai peut-être besoin d'utiliser cursor.executemany () comme décrit ici ?

Merci d'avance pour toutes suggestions, commentaires, etc.

14
James Adams

Voici ce que j'ai trouvé qui semble bien fonctionner (mais veuillez commenter s'il y a un moyen d'améliorer cela):

# 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

Utilisez Cursor.prepare() et Cursor.executemany().

De la documentation cx_Oracle :

Cursor.prepare (( instruction [ balise ]))

Ceci peut être utilisé avant un appel à execute () pour définir l'instruction qui sera exécutée. Lorsque cela est fait, la phase de préparation ne sera pas effectuée lorsque l'appel à execute () est effectué avec None ou le même objet chaîne que l'instruction. [...]

Cursor.executemany (( instruction , paramètres )

Préparez une instruction à exécuter sur une base de données, puis exécutez-la sur tous les mappages de paramètres ou séquences trouvés dans les paramètres de séquence. L'instruction est gérée de la même manière que la méthode execute () la gère.

Ainsi, en utilisant les deux fonctions ci-dessus, votre code devient:

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()

Voir également la série d'articles Mastering Oracle + Python d'Oracle.

8
alldayremix

fyi mon résultat de test:

J'insère dans 5000 rangées. 3 colonnes par ligne.

  1. exécuter l'insertion 5000 fois, cela coûte 1,24 minutes.
  2. exécuté avec executemany, il en coûte 0,125 secondes.
  3. exécuter avec un insert tout le code: cela coûte 4,08 minutes.

code python, qui configure le sql comme insérer tout dans t (a, b, c) sélectionnez: 1,: 2,: 3 à partir de l'union double, sélectionnez tous: 4,: 5:: 6 à partir de daul ...

Le code python pour configurer ce long sql, il a coûté 0.145329 secondes.

Je teste mon code sur une très vieille machine Sun. CPU: 1415 MH.

dans le troisième cas, j'ai vérifié le côté base de données, l'événement wait est "SQL * Net plus de données du client". ce qui signifie que le serveur attend plus de données du client.

Le résultat de la troisième méthode est incroyable pour moi sans le test.

donc la courte suggestion de moi est d'utiliser simplement beaucoup d'exécutifs.

4
zhihuifan

Comme le dit l'un des commentaires, pensez à utiliser INSERT ALL. Soi-disant, ce sera beaucoup plus rapide que d'utiliser executemany().

Par exemple:

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

Je créerais une grande instruction d'insertion SQL en utilisant l'union:

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

Vous pouvez construire la chaîne dans python et la donner à Oracle comme une seule instruction à exécuter.

3
Derrick