psycopg2 : 하나의 쿼리로 여러 행 삽입


141

하나의 쿼리로 여러 행을 삽입해야합니다 (행 수가 일정하지 않음). 다음과 같이 쿼리를 실행해야합니다.

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)

그러나 나는 더 간단한 방법을 원합니다.

답변:


219

다른 도시에있는 서버에 여러 줄을 삽입하는 프로그램을 만들었습니다.

이 방법을 사용하는 것보다 약 10 배 빠릅니다 executemany. 제 경우에는 tup약 2000 개의 행을 포함하는 튜플입니다. 이 방법을 사용할 때 약 10 초가 걸렸습니다.

args_str = ','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup)
cur.execute("INSERT INTO table VALUES " + args_str) 

이 방법을 사용할 때 2 분 :

cur.executemany("INSERT INTO table VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s)", tup)

15
거의 2 년 후에도 여전히 관련이 있습니다. 오늘날의 경험에 따르면 푸시하려는 행 수가 증가할수록 execute전략 을 사용하는 것이 좋습니다 . 이 덕분에 약 100 배의 속도가 향상되었습니다!
Rob Watts

4
아마도 executemany각 삽입 후에 커밋을 실행할 것 입니다. 대신 트랜잭션에서 전체를 래핑하면 신속하게 처리 할 수 ​​있습니까?
Richard

4
이 개선 사항을 직접 확인했습니다. 내가 읽은 psycopg2에서 executemany최적의 작업을 수행하지 않고 루프를 반복하고 많은 execute진술을 수행합니다. 이 방법을 사용하면 원격 서버에 대한 700 개의 행 삽입이 60 초에서 <2 초로 늘어났습니다.
Nelson

5
어쩌면 나는 편집증 일지 모르지만 쿼리를 연결하면 +SQL 주입을 열 수 있는 것처럼 보입니다 execute_values(). @ Clodoaldo Neto 솔루션이 더 안전하다고 생각합니다.
Will Munn

26
누군가 다음과 같은 오류가 발생하는 경우 : [TypeError : sequence item 0 : expect str instance, bytes found] 대신이 명령을 실행하십시오 [args_str = ','. join (cur.mogrify ( "(% s, % s)", x ) .decode ( "utf-8") x in tup)]
mrt

147

Psycopg 2.7의 새로운 execute_values방법 :

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에서 그것을하는 pythonic 방법 :

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')]

이미 정확한 형식으로되어 있습니다.

  1. values의 구문 insert절에서와 같은 기록의 목록을 기대

    insert into t (a, b) values (1, 'x'),(2, 'y')

  2. PsycopgPython tuple을 Postgresql에 적용합니다 record.

유일하게 필요한 작업은 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')

1
이 방법의 성능은 cur.copy_from과 어떻게 비교됩니까?
Michael Goldshteyn

1
벤치 마크가있는 요지가 있습니다. copy_from은 10M 레코드로 내 컴퓨터에서 약 6.5 배 빠르게 확장됩니다.
Joseph Sheedy

멋지게 보입니다-insert_query의 초기 정의 끝에 튜플이 있고 (튜플을 만들지 않는 한?) insert_query의 초기 정의에서 % s에 대한 % 다음과 같이 누락되었습니다.
deadcode

2
를 사용하여 execute_values시스템을 분당 1k 레코드에서 분당 최대 128k 레코드까지 실행할 수있었습니다
Conrad.Dean

66

psycopg2 2.7로 업데이트 :

executemany()이 스레드에서 설명하는 것처럼 클래식 은 @ ant32 구현 ( "폴딩"이라고 함)보다 약 60 배 느립니다. https://www.postgresql.org/message-id/20170130215151.GA7081%40deb76.aryehleib.com

이 구현은 버전 2.7의 psycopg2에 추가되었으며 다음과 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()사용하면 psycopg2를 사용하는 것보다 약 10 배 빠릅니다 executemany(). 실제로 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) 

26

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

이 작은 크기의 args의 경우 속도 차이가 크지 않지만 수천 개 이상의 행을 처리 할 때 속도가 크게 향상됩니다. 또한 거대한 쿼리 문자열을 작성하는 것보다 메모리 효율성이 더 뛰어납니다. 반복자는 메모리에 한 번에 하나의 입력 레코드 만 보유합니다.이 시점에서 쿼리 프로세스를 빌드하여 Python 프로세스 또는 Postgres의 메모리가 부족합니다.


3
다음은 copy_from / IteratorFile을 쿼리 작성기 솔루션과 비교 하는 벤치 마크 입니다. copy_from은 10M 레코드로 내 컴퓨터에서 약 6.5 배 빠르게 확장됩니다.
Joseph Sheedy

3
이스케이프 문자열과 타임 스탬프 등으로 장난 쳐야합니까?
CpILL

예, TSV 레코드가 제대로 구성되어 있는지 확인해야합니다.
Joseph Sheedy

24

Postgresql.org 의 Psycopg2 튜토리얼 페이지의 스 니펫 (아래 참조) :

마지막으로 보여 드리고 싶은 것은 사전을 사용하여 여러 행을 삽입하는 방법입니다. 다음과 같은 경우 :

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)

많은 코드를 저장하지는 않지만 확실히 좋아 보입니다.


35
이것은 많은 개별 INSERT진술 을 실행할 것 입니다. 유용하지만 단일 multi- VALUEd 인서트 와 동일하지는 않습니다 .
Craig Ringer

7

이러한 모든 기술을 Postgres 용어에서 '확장 삽입'이라고하며 2016 년 11 월 24 일 현재 psychopg2의 executemany () 및이 스레드에 나열된 다른 모든 방법보다 훨씬 빠릅니다. 대답).

다음은 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;)을 사용해야합니다.


죽음에서 자라지 만 마지막 몇 줄의 상황에서는 어떻게됩니까? 실제로 마지막 행을 마지막 행으로 다시 실행한다고 가정합니다. 짝수의 행이있는 경우?
mcpeterson

맞습니다. 죄송합니다. 예제를 작성할 때 잊어 버렸어야합니다. 그렇게하지 않으면 사람들에게 오류를주지 않았을 것입니다. 이로 인해 얼마나 많은 사람들이 솔루션을 복사 / 붙여 넣기하고 그들의 비즈니스에 관심을 가지게되었는지 걱정하게되었습니다.
JJ

2

나는 몇 년 동안 위의 ant32의 대답을 사용해 왔습니다. 그러나 mogrify바이트 문자열을 반환 하기 때문에 파이썬 3에서 오류가 발생했습니다 .

명시 적으로 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)

1

또 다른 훌륭하고 효율적인 접근 방법은 삽입을 위해 행을 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을 지원하기에 충분히 새로운 것이어야합니다


1

cursor.copyfrom에 의해 제공되는 용액 @ jopseph.sheedy ( https://stackoverflow.com/users/958118/joseph-sheedy (위)가 https://stackoverflow.com/a/30721460/11100064 ) 빠른 실제로 낙뢰이다.

그러나 그가 제공하는 예제는 일반적으로 여러 필드가있는 레코드에 사용할 수 없으며 올바르게 사용하는 방법을 알아내는 데 시간이 걸렸습니다.

IteratorFile은 다음과 같이 탭으로 구분 된 필드로 인스턴스화해야합니다 ( r각 dict가 레코드 인 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)

여기 요점에서 완전한 기능 .


0

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)

후드 아래에서 SQLAlchemy는 이와 같은 호출에 psychopg2의 executemany ()를 사용 하므로이 답변은 큰 쿼리에 심각한 성능 문제가 있습니다. docs.sqlalchemy.org/en/latest/orm/session_api.html 실행 메소드를 참조하십시오 .
sage88

2
나는 그것이 사실이라고 생각하지 않습니다. 내가 이것을 보았을 때 조금 지났지 만 IIRC는 실제로 insert_query줄 에 단일 삽입 문장을 작성하는 것 입니다. 그런 다음 방대한 단일 문자열로 session.execute()psycopg2의 execute()문장을 호출 합니다. 따라서 "트릭"은 전체 삽입 문 객체를 먼저 구축합니다. 나는 이것을 사용하여 한 번에 200,000 행을 삽입 하고이 코드를 사용하여 일반과 비교하여 엄청난 성능 향상을 보았습니다 executemany().
Jeff Widman

1
연결 한 SQLAlchemy 문서에는 이것이 어떻게 작동하는지 정확하게 보여주는 섹션이 있습니다. "여러 값을 전달하는 것이 기존의 executemany () 형식을 사용하는 것과 동일하지 않다는 점에 유의해야합니다." 따라서 이것이 효과가 있다고 명시 적으로 지적하고 있습니다.
Jeff Widman

1
나는 정정되었다. values ​​() 메소드의 사용법을 알지 못했습니다 (SQLAlchemy가 없으면 executemany 만 수행함). 내 투표를 변경할 수 있도록 해당 문서에 대한 링크를 포함하도록 답변을 편집하라고 말하지만 분명히 포함 시켰습니다. 아마도 이것은 dicts 목록과 함께 execute ()를 사용하여 insert ()를 호출하는 것과 같지 않다고 말할 수 있습니까?
sage88

execute_values와 비교하여 어떻게 수행합니까?
MrR

0

이 질문이 게시 된 이후 execute_batch 가 psycopg2에 추가되었습니다.

execute_values 보다 느리지 만 사용하기가 더 쉽습니다.


2
다른 의견을보십시오. psycopg2의 방법 execute_values입니다 빨리 보다execute_batch
명 Fierr

0

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

-1

하나의 삽입 상태 안에 여러 행을 삽입하려면 (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

sqlalchemy 엔진에서 로깅을 표시하는 것은 단일 쿼리 만 실행하는 것이 아니라 sqlalchemy 엔진이 하나의 명령을 실행했음을 의미합니다. 후드 아래에서 이것은 psychopg2의 executemany를 사용하고 있으며 이는 매우 비효율적입니다. docs.sqlalchemy.org/en/latest/orm/session_api.html 실행 메소드를 참조하십시오 .
sage88

-3

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)


-4

마지막으로 SQLalchemy1.2 버전에서이 새로운 구현은 use_batch_mode = True로 엔진을 초기화 할 때 executemany 대신 psycopg2.extras.execute_batch ()를 사용하도록 추가되었습니다.

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을 직접 처리하지 않아도됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.