Как я могу выполнить пакетную вставку в базу данных Oracle с помощью Python?

У меня есть несколько ежемесячных данных о погоде, которые я хочу вставить в таблицу базы данных Oracle, но я хочу вставить соответствующие записи в пакет, чтобы быть более эффективным. Может ли кто-нибудь посоветовать, как я это сделаю на Python?

Например, предположим, что в моей таблице четыре поля: идентификатор станции, дата и два поля значений. Записи однозначно идентифицируются по полям идентификатора станции и даты (составной ключ). Значения, которые мне нужно будет вставить для каждой станции, будут храниться в списке с X числами данных за полные годы, поэтому, например, если есть значения за два года, тогда списки значений будут содержать 24 значения.

Я предполагаю, что ниже я бы сделал это, если бы я хотел вставлять записи по одной за раз:

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 (), как описано здесь?

Заранее благодарим за любые предложения, комментарии и т. Д.


person James Adams    schedule 15.02.2013    source источник
comment
Использовать вместо этого вызов .executemany()?   -  person Martijn Pieters    schedule 16.02.2013
comment
Да, Мартейн, я только что нашел это (и обновил / отредактировал вопрос, чтобы включить его), и это действительно похоже на решение этой проблемы. Спасибо за ваш вклад!   -  person James Adams    schedule 16.02.2013
comment
Используйте один запрос с INSERT ALL для одновременной вставки нескольких строк.   -  person Jesse    schedule 16.02.2013
comment
.executemany() не пакетное обновление; вы будете выполнять столько операторов DML, сколько у вас есть курсор, который вы выполняете ... Это делает ваш код Python более чистым, но не более снисходительным к базе данных. Как выглядят ваши исходные данные; вводится ли он пользователем и таким образом сохраняется в переменных в вашем коде или в текстовом файле и т. д.?   -  person Ben    schedule 16.02.2013
comment
Спасибо, Бен. Данные вычисляются и сохраняются в переменных списка. Итак, в простом примере, приведенном выше, списки темпов и осадков фактически будут вычисляться в моем коде Python до того, как мы дойдем до этого раздела, а не назначены, как в примере кода.   -  person James Adams    schedule 16.02.2013
comment
@ Бен, согласно кому? Код C для .executemany() делает только один вызов OCIStmtExecute()   -  person alldayremix    schedule 24.02.2013
comment
Да @alldayremix, но OCIstmtExecute не выполняет пакетную обработку : Для операторов, отличных от SELECT, количество раз, которое этот оператор выполняется, равно iters - rowoff. В источнике c Cursor_ExecuteMany передает количество строк в Cursor_InternalExecute, который затем передает то же самое в OCIStmtExecute .   -  person Ben    schedule 24.02.2013
comment
Эта страница противоречит этому утверждению: Для больших операций вставки не требуется много отдельных вставок, поскольку Python полностью поддерживает вставку нескольких строк одновременно с помощью метода cx_Oracle.Cursor.executemany. Ограничение количества выполняемых операций значительно улучшает производительность программы, и о чем следует думать в первую очередь при написании приложений, использующих INSERT. Так что же правильно? Я нигде не могу найти никаких тестов cx_Oracle в Интернете.   -  person alldayremix    schedule 24.02.2013
comment
Я собираюсь доверять исходному коду и документации, а не сообщению в блоге @alldayremix. Я, конечно, предполагаю, что правильно понял. Обратите внимание, что я не утверждаю, что executemany () не быстрее, чем несколько execute (), просто это не пакетная операция с базой данных. Таким образом, тесты вам не помогут.   -  person Ben    schedule 25.02.2013
comment
Для меня это звучит разумно. Кажется, .executemany() просто исключает время приема-передачи, которое потребовалось бы для повторных вызовов .execute(), а сообщение в блоге сформулировано неуклюже. Итак, единственный способ выполнить пакетную операцию - вызвать .execute() с помощью оператора типа BULK INSERT... или INSERT ALL....   -  person alldayremix    schedule 25.02.2013


Ответы (5)


Вот что я придумал и который, похоже, работает хорошо (но, пожалуйста, прокомментируйте, есть ли способ улучшить это):

# 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()
person James Adams    schedule 16.02.2013

Используйте Cursor.prepare() и Cursor.executemany().

Из документации cx_Oracle:

Cursor.prepare (инструкция [, тег])

Это можно использовать перед вызовом execute () для определения оператора, который будет выполняться. Когда это будет сделано, этап подготовки не будет выполняться, если вызов execute () выполняется с параметром None или тем же строковым объектом, что и оператор. [...]

Cursor.executemany (инструкция, параметры)

Подготовьте оператор для выполнения в базе данных, а затем выполните его для всех сопоставлений параметров или последовательностей, найденных в параметрах последовательности. Оператор управляется так же, как и метод execute ().

Таким образом, используя две указанные выше функции, ваш код становится:

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 Освоение Oracle + Python статьи.

person alldayremix    schedule 15.02.2013
comment
Спасибо за это. Я пробовал описанное выше, но это не сработало из-за того, что executemany () принимает только два аргумента. И кажется, что вам все еще нужно создать список строк для использования в качестве аргумента параметров последовательности, как описано в разделе «Многие сразу» здесь: oracle.com/technetwork/articles/dsl/ и разделы, описывающие использование executemany () здесь: oracle.com/technetwork/articles/dsl/ - person James Adams; 16.02.2013
comment
Также непонятно, как использование prepare () дает вам что-либо, поскольку, согласно документации cx_Oracle.Cursor.execute (): ссылка на оператор будет сохранена курсором. Если None или тот же строковый объект передается снова, курсор снова выполнит этот оператор без выполнения подготовки или повторного связывания и переопределения. Но, возможно, я что-то не понимаю ... - person James Adams; 16.02.2013
comment
Использование prepare() ничего вам не даст, оно просто сделает ваш код более понятным; базовый код C для обоих методов одинаков. Что касается вашего первого комментария, я обновил свой ответ, указав, что: executemany() принимает последовательность словарей в качестве второго аргумента. - person alldayremix; 20.02.2013

fyi мой результат теста:

Вставляю в 5000 рядов. 3 столбца в строке.

  1. запустить insert 5000 раз, это стоит 1,24 минуты.
  2. запустить с executemany, это стоит 0,125 секунды.
  3. запустить со вставкой всего кода: это стоит 4,08 минуты.

код python, который настраивает sql как вставить все в t (a, b, c) select: 1,: 2,: 3 from dual union all select: 4,: 5:: 6 from daul ...

Код python для установки этого длинного sql стоит 0,145329 секунды.

Я тестирую свой код на очень старой солнечной машине. процессор: 1415 MH.

в третьем случае я проверил сторону базы данных, событие ожидания - «SQL * Net more data from client». Это означает, что сервер ожидает дополнительных данных от клиента.

Результат третьего метода для меня без теста невероятен.

так что короткое предложение от меня - просто использовать executemany.

person zhihuifan    schedule 26.05.2016

Как говорится в одном из комментариев, подумайте об использовании 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

person interestedparty333    schedule 17.03.2016
comment
Это правильный ответ. Множественные вставки обрабатываются одним обращением к базе данных. - person YoYo; 01.03.2018

Я бы создал большой оператор вставки SQL с помощью 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

Вы можете построить строку в Python и передать ее оракулу как один оператор для выполнения.

person Derrick    schedule 08.02.2016
comment
Это правильный ответ. Множественные вставки обрабатываются одним обращением к базе данных. Одна проблема - вы, вероятно, захотите сделать UNION ALL вместо обычного UNION. - person YoYo; 01.03.2018