Мне нужно вставить несколько строк одним запросом (количество строк непостоянно), поэтому мне нужно выполнить такой запрос:
INSERT INTO t (a, b) VALUES (1, 2), (3, 4), (5, 6);
Я знаю только один способ
args = [(1,2), (3,4), (5,6)]
args_str = ','.join(cursor.mogrify("%s", (x, )) for x in args)
cursor.execute("INSERT INTO t (a, b) VALUES "+args_str)
но я хочу более простой способ.
python
postgresql
psycopg2
Сергей Федосеев
источник
источник
execute
стратегию. Благодаря этому я увидел ускорение примерно в 100 раз!executemany
запускает фиксацию после каждой вставки. Если вместо этого вы оберните все это в транзакцию, может быть, это ускорит процесс?executemany
не делает ничего оптимального, просто зацикливается и выполняет множествоexecute
операторов. Используя этот метод, время вставки 700 строк на удаленный сервер уменьшилось с 60 до <2 с.+
кажется, что он может открыться для SQL-инъекции, я чувствую, чтоexecute_values()
решение @Clodoaldo Neto безопаснее.Новый
execute_values
метод в Psycopg 2.7:data = [(1,'x'), (2,'y')] insert_query = 'insert into t (a, b) values %s' psycopg2.extras.execute_values ( cursor, insert_query, data, template=None, page_size=100 )
Питонический способ сделать это в Psycopg 2.6:
data = [(1,'x'), (2,'y')] records_list_template = ','.join(['%s'] * len(data)) insert_query = 'insert into t (a, b) values {}'.format(records_list_template) cursor.execute(insert_query, data)
Объяснение: Если данные, которые должны быть вставлены, представлены в виде списка кортежей, как в
data = [(1,'x'), (2,'y')]
тогда он уже находится в точном требуемом формате, как
values
синтаксисinsert
пункта ожидает список записей , как вinsert into t (a, b) values (1, 'x'),(2, 'y')
Psycopg
адаптирует Pythontuple
к Postgresqlrecord
.Единственная необходимая работа - предоставить шаблон списка записей, который будет заполнен psycopg.
# We use the data list to be sure of the template length records_list_template = ','.join(['%s'] * len(data))
и поместите его в
insert
запросinsert_query = 'insert into t (a, b) values {}'.format(records_list_template)
Печать
insert_query
выходовinsert into t (a, b) values %s,%s
Теперь к обычной
Psycopg
подстановке аргументовcursor.execute(insert_query, data)
Или просто тестируем, что будет отправлено на сервер
print (cursor.mogrify(insert_query, data).decode('utf8'))
Выход:
insert into t (a, b) values (1, 'x'),(2, 'y')
источник
execute_values
я смог заставить мою систему работать со скоростью от 1k записей в минуту до 128k записей в минуту,connection.commit()
послеexecute_values(...)
.Обновление с psycopg2 2.7:
Классический
executemany()
вариант примерно в 60 раз медленнее, чем реализация @ ant32 (называемая «свернутой»), как описано в этой теме: https://www.postgresql.org/message-id/20170130215151.GA7081%40deb76.aryehleib.comЭта реализация была добавлена в psycopg2 в версии 2.7 и называется
execute_values()
:from psycopg2.extras import execute_values execute_values(cur, "INSERT INTO test (id, v1, v2) VALUES %s", [(1, 2, 3), (4, 5, 6), (7, 8, 9)])
Предыдущий ответ:
Чтобы вставить несколько строк, используйте многострочный
VALUES
синтаксис сexecute()
примерно в 10 раз быстрее, чем с помощью psycopg2executemany()
. Действительно,executemany()
сразу проходит множество отдельныхINSERT
заявлений.Код @ ant32 отлично работает в Python 2. Но в Python 3
cursor.mogrify()
возвращает байты,cursor.execute()
принимает байты или строки и','.join()
ожидаетstr
экземпляра.Итак, в Python 3 вам может потребоваться изменить код @ ant32, добавив
.decode('utf-8')
:args_str = ','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x).decode('utf-8') for x in tup) cur.execute("INSERT INTO table VALUES " + args_str)
Или используя только байты (с
b''
илиb""
):args_bytes = b','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup) cur.execute(b"INSERT INTO table VALUES " + args_bytes)
источник
cursor.copy_from - это самое быстрое решение, которое я нашел для массовых вставок. Вот суть, которую я сделал, содержащий класс с именем IteratorFile, который позволяет итератору, выдающему строки, читаться как файл. Мы можем преобразовать каждую входную запись в строку, используя выражение генератора. Итак, решение было бы
args = [(1,2), (3,4), (5,6)] f = IteratorFile(("{}\t{}".format(x[0], x[1]) for x in args)) cursor.copy_from(f, 'table_name', columns=('a', 'b'))
Для такого тривиального размера аргументов это не сильно повлияет на скорость, но я вижу большое ускорение при работе с тысячами + строк. Это также будет более эффективным с точки зрения памяти, чем создание гигантской строки запроса. Итератор всегда будет хранить в памяти только одну входную запись, и в какой-то момент у вас закончится память в вашем процессе Python или в Postgres, построив строку запроса.
источник
Фрагмент страницы руководства Psycopg2 на Postgresql.org (см. Внизу) :
namedict = ({"first_name":"Joshua", "last_name":"Drake"}, {"first_name":"Steven", "last_name":"Foo"}, {"first_name":"David", "last_name":"Bar"})
cur = conn.cursor() cur.executemany("""INSERT INTO bar(first_name,last_name) VALUES (%(first_name)s, %(last_name)s)""", namedict)
Это не экономит много кода, но определенно выглядит лучше.
источник
INSERT
операторов. Полезно, но не то же самое, что одинарная многослойнаяVALUE
пластина.Все эти методы называются «расширенными вставками» в терминологии Postgres, и по состоянию на 24 ноября 2016 года они все еще на тонну быстрее, чем executemany () психопг2 () и все другие методы, перечисленные в этой ветке (которые я пробовал, прежде чем прийти к этому ответ).
Вот код, который не использует cur.mogrify, приятный и простой, чтобы вы могли разобраться:
valueSQL = [ '%s', '%s', '%s', ... ] # as many as you have columns. sqlrows = [] rowsPerInsert = 3 # more means faster, but with diminishing returns.. for row in getSomeData: # row == [1, 'a', 'yolo', ... ] sqlrows += row if ( len(sqlrows)/len(valueSQL) ) % rowsPerInsert == 0: # sqlrows == [ 1, 'a', 'yolo', 2, 'b', 'swag', 3, 'c', 'selfie' ] insertSQL = 'INSERT INTO "twitter" VALUES ' + ','.join(['(' + ','.join(valueSQL) + ')']*rowsPerInsert) cur.execute(insertSQL, sqlrows) con.commit() sqlrows = [] insertSQL = 'INSERT INTO "twitter" VALUES ' + ','.join(['(' + ','.join(valueSQL) + ')']*len(sqlrows)) cur.execute(insertSQL, sqlrows) con.commit()
Но следует отметить, что если вы можете использовать copy_from (), вы должны использовать copy_from;)
источник
Я использую ответ ant32 выше несколько лет. Однако я обнаружил, что это ошибка в python 3, потому что
mogrify
возвращает байтовую строку.Явное преобразование в строки bytse - простое решение для обеспечения совместимости кода с Python 3.
args_str = b','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup) cur.execute(b"INSERT INTO table VALUES " + args_str)
источник
cur.mogrify()
Еще один приятный и эффективный подход - передать строки для вставки в качестве 1 аргумента, который представляет собой массив объектов json.
Например, вы передаете аргумент:
[ {id: 18, score: 1}, { id: 19, score: 5} ]
Это массив, внутри которого может быть любое количество объектов. Тогда ваш SQL выглядит так:
INSERT INTO links (parent_id, child_id, score) SELECT 123, (r->>'id')::int, (r->>'score')::int FROM unnest($1::json[]) as r
Примечание: ваш postgress должен быть достаточно новым, чтобы поддерживать json
источник
Если вы используете SQLAlchemy, вам не нужно возиться с ручной обработкой строки, потому что SQLAlchemy поддерживает создание многострочного
VALUES
предложения для одногоINSERT
оператора :rows = [] for i, name in enumerate(rawdata): row = { 'id': i, 'name': name, 'valid': True, } rows.append(row) if len(rows) > 0: # INSERT fails if no rows insert_query = SQLAlchemyModelName.__table__.insert().values(rows) session.execute(insert_query)
источник
insert_query
строке. Затемsession.execute()
просто вызывает оператор psycopg2execute()
с одной массивной строкой. Таким образом, «трюк» состоит в том, чтобы сначала построить весь объект оператора вставки. Я использую это для вставки 200 000 строк за раз, и увидел значительное увеличение производительности с использованием этого кода по сравнению с обычнымexecutemany()
.Executemany принимает массив кортежей
https://www.postgresqltutorial.com/postgresql-python/insert/
""" array of tuples """ vendor_list = [(value1,)] """ insert multiple vendors into the vendors table """ sql = "INSERT INTO vendors(vendor_name) VALUES(%s)" conn = None try: # read database configuration params = config() # connect to the PostgreSQL database conn = psycopg2.connect(**params) # create a new cursor cur = conn.cursor() # execute the INSERT statement cur.executemany(sql,vendor_list) # commit the changes to the database conn.commit() # close communication with the database cur.close() except (Exception, psycopg2.DatabaseError) as error: print(error) finally: if conn is not None: conn.close()
источник
Решение cursor.copyfrom, предоставленное @ jopseph.sheedy ( https://stackoverflow.com/users/958118/joseph-sheedy ) выше ( https://stackoverflow.com/a/30721460/11100064 ), действительно работает молниеносно.
Однако приведенный им пример в общем случае нельзя использовать для записи с любым количеством полей, и мне потребовалось время, чтобы понять, как его правильно использовать.
IteratorFile должен быть создан с полями, разделенными табуляцией, как это (
r
это список dicts, где каждый dict является записью):f = IteratorFile("{0}\t{1}\t{2}\t{3}\t{4}".format(r["id"], r["type"], r["item"], r["month"], r["revenue"]) for r in records)
Чтобы обобщить произвольное количество полей, мы сначала создадим строку строки с правильным количеством вкладок и заполнителей полей:
"{}\t{}\t{}....\t{}"
а затем воспользуемся.format()
для заполнения значений полей*list(r.values())) for r in records
:line = "\t".join(["{}"] * len(records[0])) f = IteratorFile(line.format(*list(r.values())) for r in records)
полная функция здесь .
источник
execute_batch был добавлен в psycopg2 с момента публикации этого вопроса.
Он медленнее, чем execute_values, но его проще использовать.
источник
execute_values
это быстрее , чемexecute_batch
Если вы хотите вставить несколько строк в одно состояние вставки (при условии, что вы не используете ORM), то для меня самым простым способом было бы использовать список словарей. Вот пример:
t = [{'id':1, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 6}, {'id':2, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 7}, {'id':3, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 8}] conn.execute("insert into campaign_dates (id, start_date, end_date, campaignid) values (%(id)s, %(start_date)s, %(end_date)s, %(campaignid)s);", t)
Как видите, будет выполнен только один запрос:
INFO sqlalchemy.engine.base.Engine insert into campaign_dates (id, start_date, end_date, campaignid) values (%(id)s, %(start_date)s, %(end_date)s, %(campaignid)s); INFO sqlalchemy.engine.base.Engine [{'campaignid': 6, 'id': 1, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}, {'campaignid': 7, 'id': 2, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}, {'campaignid': 8, 'id': 3, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}] INFO sqlalchemy.engine.base.Engine COMMIT
источник
Использование aiopg - приведенный ниже фрагмент отлично работает
# items = [10, 11, 12, 13] # group = 1 tup = [(gid, pid) for pid in items] args_str = ",".join([str(s) for s in tup]) # insert into group values (1, 10), (1, 11), (1, 12), (1, 13) yield from cur.execute("INSERT INTO group VALUES " + args_str)
источник
Наконец, в версии SQLalchemy1.2 эта новая реализация добавлена для использования psycopg2.extras.execute_batch () вместо executemany, когда вы инициализируете свой движок с помощью use_batch_mode = True, например:
engine = create_engine( "postgresql+psycopg2://scott:tiger@host/dbname", use_batch_mode=True)
http://docs.sqlalchemy.org/en/latest/changelog/migration_12.html#change-4109
Тогда кому-то придется использовать SQLalchmey, не пытаясь пробовать разные комбинации sqla и psycopg2 и напрямую SQL вместе ..
источник