Como escrever DataFrame na tabela postgres?

103

Existe o método DataFrame.to_sql , mas funciona apenas para bancos de dados mysql, sqlite e oracle. Não consigo passar para este método de conexão postgres ou motor sqlalchemy.

m9_psy
fonte

Respostas:

125

A partir do pandas 0.14 (lançado no final de maio de 2014), o postgresql é compatível. O sqlmódulo agora usa sqlalchemypara suportar diferentes tipos de banco de dados. Você pode passar um mecanismo sqlalchemy para um banco de dados postgresql (consulte os documentos ). Por exemplo:

from sqlalchemy import create_engine
engine = create_engine('postgresql://scott:tiger@localhost:5432/mydatabase')
df.to_sql('table_name', engine)

Você está correto que nos pandas até a versão 0.13.1 postgresql não era suportado. Se você precisar usar uma versão mais antiga do pandas, aqui está uma versão corrigida de pandas.io.sql: https://gist.github.com/jorisvandenbossche/10841234 .
Eu escrevi isso há um tempo atrás, então não posso garantir totalmente que sempre funcione, mas a base deve estar lá). Se você colocar esse arquivo em seu diretório de trabalho e importá-lo, deverá ser capaz de fazer (onde conestá uma conexão postgresql):

import sql  # the patched version (file is named sql.py)
sql.write_frame(df, 'table_name', con, flavor='postgresql')
Joris
fonte
1
Isso chegou a 0,14?
Quant,
Sim, e também o 0,15 já foi lançado (candidato a lançamento). Vou atualizar a resposta, obrigado por perguntar.
joris
1
Esta postagem resolveu o problema para mim: stackoverflow.com/questions/24189150/…
srodriguex
Nota: to_sql não exporta tipos de array no postgres.
Saurabh Saha de
1
Em vez de criar um novo Sqlalchemy engine, posso usar uma Postgresconexão existente criada usando psycopg2.connect()?
Jarvis
84

Opção mais rápida:

O código a seguir copiará seu Pandas DF para o banco de dados postgres muito mais rápido do que o método df.to_sql e você não precisará de nenhum arquivo csv intermediário para armazenar o df.

Crie um motor com base nas especificações do seu banco de dados.

Crie uma tabela em seu banco de dados postgres que tenha o mesmo número de colunas do Dataframe (df).

Os dados no DF serão inseridos em sua tabela postgres.

from sqlalchemy import create_engine
import psycopg2 
import io

se você quiser substituir a tabela, podemos substituí-la pelo método to_sql normal usando cabeçalhos de nosso df e, em seguida, carregar todo o df que consome muito tempo no banco de dados.

engine = create_engine('postgresql+psycopg2://username:password@host:port/database')

df.head(0).to_sql('table_name', engine, if_exists='replace',index=False) #truncates the table

conn = engine.raw_connection()
cur = conn.cursor()
output = io.StringIO()
df.to_csv(output, sep='\t', header=False, index=False)
output.seek(0)
contents = output.getvalue()
cur.copy_from(output, 'table_name', null="") # null values become ''
conn.commit()
Aseem
fonte
O que a variável contentsfaz? Deve ser este o que está escrito copy_from()?
n1000
@ n1000 Sim, apenas ignore a contentsvariável, todo o resto deve funcionar bem
Bobby
2
porque você faz output.seek(0)?
moshevi de
7
Isso é tão rápido que é engraçado: D
shadi
1
Load is table está falhando para mim por causa de novos caracteres de linha em alguns campos. Como faço para lidar com isso? df.to_csv (output, sep = '\ t', header = False, index = False, encoding = 'utf-8') cur.copy_from (output, 'messages', null = "") # valores nulos tornam-se ''
conetfun
23

Solução Pandas 0.24.0+

No Pandas 0.24.0 foi introduzido um novo recurso projetado especificamente para gravações rápidas no Postgres. Você pode saber mais sobre isso aqui: https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#io-sql-method

import csv
from io import StringIO

from sqlalchemy import create_engine

def psql_insert_copy(table, conn, keys, data_iter):
    # gets a DBAPI connection that can provide a cursor
    dbapi_conn = conn.connection
    with dbapi_conn.cursor() as cur:
        s_buf = StringIO()
        writer = csv.writer(s_buf)
        writer.writerows(data_iter)
        s_buf.seek(0)

        columns = ', '.join('"{}"'.format(k) for k in keys)
        if table.schema:
            table_name = '{}.{}'.format(table.schema, table.name)
        else:
            table_name = table.name

        sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
            table_name, columns)
        cur.copy_expert(sql=sql, file=s_buf)

engine = create_engine('postgresql://myusername:mypassword@myhost:5432/mydatabase')
df.to_sql('table_name', engine, method=psql_insert_copy)
mgoldwasser
fonte
3
Na maioria das vezes, adicionar method='multi'opção é rápido o suficiente. Mas sim, este COPYmétodo é o caminho mais rápido agora.
ssword
Isso é apenas para csv? Também pode ser usado com .xlsx? Algumas notas sobre o que cada parte disso está fazendo seriam úteis. A primeira parte após o withestá escrevendo em um buffer de memória. A última parte do withé usar uma instrução SQL e aproveitar a velocidade de copy_expert para carregar os dados em massa. Qual é a parte do meio que começa columns =fazendo?
DudeWah
Isso funcionou muito bem para mim. E você poderia explicar os keysargumentos da psql_insert_copyfunção, por favor? Como ele obtém quaisquer chaves e as chaves são apenas os nomes das colunas?
Bowen Liu
Eu tentei usar este método, no entanto, ele atira-me um erro: Table 'XYZ' already exists. Pelo que entendi, não deveria criar uma mesa, deveria?
E. Epstein
@ E.Epstein - você pode modificar a última linha para df.to_sql('table_name', engine, if_exists='replace', method=psql_insert_copy)- isso cria uma tabela em seu banco de dados.
mgoldwasser 01 de
21

É assim que eu fiz.

Pode ser mais rápido porque está usando execute_batch:

# df is the dataframe
if len(df) > 0:
    df_columns = list(df)
    # create (col1,col2,...)
    columns = ",".join(df_columns)

    # create VALUES('%s', '%s",...) one '%s' per column
    values = "VALUES({})".format(",".join(["%s" for _ in df_columns])) 

    #create INSERT INTO table (columns) VALUES('%s',...)
    insert_stmt = "INSERT INTO {} ({}) {}".format(table,columns,values)

    cur = conn.cursor()
    psycopg2.extras.execute_batch(cur, insert_stmt, df.values)
    conn.commit()
    cur.close()
Behdad Forghani
fonte
1
Recebo AttributeError: o módulo 'psycopg2' não tem atributo 'extras'. Ah, isso precisa ser importado explicitamente. import psycopg2.extras
GeorgeLPerkins
esta função é muito mais rápida do que a solução sqlalchemy
Saurabh Saha
-1

Para Python 2.7 e Pandas 0.24.2 e usando Psycopg2

Módulo de conexão Psycopg2

def dbConnect (db_parm, username_parm, host_parm, pw_parm):
    # Parse in connection information
    credentials = {'host': host_parm, 'database': db_parm, 'user': username_parm, 'password': pw_parm}
    conn = psycopg2.connect(**credentials)
    conn.autocommit = True  # auto-commit each entry to the database
    conn.cursor_factory = RealDictCursor
    cur = conn.cursor()
    print ("Connected Successfully to DB: " + str(db_parm) + "@" + str(host_parm))
    return conn, cur

Conecte-se ao banco de dados

conn, cur = dbConnect(databaseName, dbUser, dbHost, dbPwd)

Supondo que o dataframe já esteja presente como df

output = io.BytesIO() # For Python3 use StringIO
df.to_csv(output, sep='\t', header=True, index=False)
output.seek(0) # Required for rewinding the String object
copy_query = "COPY mem_info FROM STDOUT csv DELIMITER '\t' NULL ''  ESCAPE '\\' HEADER "  # Replace your table name in place of mem_info
cur.copy_expert(copy_query, output)
conn.commit()
Mayukh Ghosh
fonte