¿Cómo escribir DataFrame en la tabla postgres?

103

Existe el método DataFrame.to_sql , pero funciona solo para bases de datos mysql, sqlite y oracle. No puedo pasar a este método de conexión postgres o motor sqlalchemy.

m9_psy
fuente

Respuestas:

125

A partir de pandas 0.14 (lanzado a finales de mayo de 2014), se admite postgresql. El sqlmódulo ahora se usa sqlalchemypara admitir diferentes tipos de bases de datos. Puede pasar un motor sqlalchemy para una base de datos postgresql (ver documentos ). P.ej:

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

Tiene razón en que en pandas hasta la versión 0.13.1 postgresql no era compatible. Si necesita usar una versión anterior de pandas, aquí hay una versión parcheada de pandas.io.sql: https://gist.github.com/jorisvandenbossche/10841234 .
Escribí esto hace un tiempo, por lo que no puedo garantizar completamente que siempre funcione, pero la base debería estar ahí). Si coloca ese archivo en su directorio de trabajo y lo importa, entonces debería poder hacer (dónde conestá una conexión postgresql):

import sql  # the patched version (file is named sql.py)
sql.write_frame(df, 'table_name', con, flavor='postgresql')
joris
fuente
1
¿Llegó a 0,14?
Cant.
Sí, y también 0.15 ya está disponible (versión candidata). Actualizaré la respuesta, gracias por preguntar.
joris
1
Esta publicación me resolvió el problema: stackoverflow.com/questions/24189150/…
srodriguex
Nota: to_sql no exporta tipos de matriz en postgres.
Saurabh Saha
1
En lugar de crear una nueva Sqlalchemy engine, ¿puedo utilizar una Postgresconexión existente creada con psycopg2.connect()?
Jarvis
84

Opción más rápida:

El siguiente código copiará su Pandas DF a postgres DB mucho más rápido que el método df.to_sql y no necesitará ningún archivo csv intermedio para almacenar el df.

Cree un motor basado en las especificaciones de su base de datos.

Cree una tabla en su base de datos de postgres que tenga el mismo número de columnas que el Dataframe (df).

Los datos en DF se insertarán en su tabla de postgres.

from sqlalchemy import create_engine
import psycopg2 
import io

si desea reemplazar la tabla, podemos reemplazarla con el método to_sql normal usando encabezados de nuestro df y luego cargar todo el df que consume mucho tiempo en DB.

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
fuente
¿Qué hace la variable contents? ¿Debería ser este el que está escrito copy_from()?
n1000
@ n1000 Sí, simplemente ignore la contentsvariable, todo lo demás debería funcionar bien
Bobby
2
¿por qué haces output.seek(0)?
moshevi
7
Esto es tan rápido que es divertido: D
shadi
1
La carga de la tabla me falla debido a nuevos caracteres de línea en algunos campos. ¿Cómo manejo esto? df.to_csv (salida, sep = '\ t', header = False, index = False, encoding = 'utf-8') cur.copy_from (salida, 'messages', null = "") # los valores nulos se convierten en ''
conetfun
23

Solución Pandas 0.24.0+

En Pandas 0.24.0 se introdujo una nueva característica diseñada específicamente para escrituras rápidas en Postgres. Puede obtener más información al respecto aquí: 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
fuente
3
La mayor parte del tiempo, agregar method='multi'opciones es lo suficientemente rápido. Pero sí, este COPYmétodo es el más rápido en este momento.
ssword
¿Es esto solo para csv? ¿Se puede usar también con .xlsx? Algunas notas sobre lo que está haciendo cada parte de esto serían útiles. La primera parte después de withescribir en un búfer de memoria. La última parte del proceso withconsiste en utilizar una declaración SQL y aprovechar la velocidad de copy_expert para cargar los datos de forma masiva. ¿Cuál es la parte intermedia que comienza con columns =hacer?
DudeWah
Esto funcionó muy bien para mí. ¿Y podría explicar los keysargumentos en la psql_insert_copyfunción por favor? ¿Cómo obtiene las claves y son las claves solo los nombres de las columnas?
Bowen Liu
He intentado usar este método, sin embargo, me tira un error: Table 'XYZ' already exists. Por lo que tengo entendido, no debería crear una tabla, ¿verdad?
E. Epstein
@ E.Epstein - puede modificar la última línea a df.to_sql('table_name', engine, if_exists='replace', method=psql_insert_copy)- esto crea una tabla en su base de datos.
mgoldwasser
21

Así es como lo hice.

Puede ser más rápido porque usa 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
fuente
1
Recibo AttributeError: el módulo 'psycopg2' no tiene ningún atributo 'extras'. Ah, esto debe importarse explícitamente. import psycopg2.extras
GeorgeLPerkins
esta función es mucho más rápida que la solución sqlalchemy
Saurabh Saha
-1

Para Python 2.7 y Pandas 0.24.2 y usando Psycopg2

Módulo de conexión 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

Conectarse a la base de datos

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

Suponiendo que el marco de datos ya esté 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
fuente