it-swarm.com.de

Masseneinfügung eines Pandas-Datenrahmens mit SQLAlchemy

Ich habe einige recht große Pandas, DataFrames, und ich möchte die neuen Massen-SQL-Zuordnungen verwenden, um sie über SQL-Alchemie auf einen Microsoft SQL-Server hochzuladen. Die Methode pandas.to_sql ist zwar Nizza, aber langsam. 

Ich habe Probleme beim Schreiben des Codes ...

Ich möchte dieser Funktion einen Pandas DataFrame übergeben, den ich table nenne, einen Schemanamen, den ich schema nenne, und einen Tabellennamen, den ich name nenne. Idealerweise löscht die Funktion 1.) die Tabelle, falls sie bereits existiert. 2.) Erstellen Sie eine neue Tabelle. 3. Erstellen Sie einen Mapper und 4.) Einfügen von Massen unter Verwendung der Mapper- und Pandas-Daten. Ich bin bei Teil 3 festgefahren.

Hier ist mein (zugegebenermaßen grober) Code. Ich habe Probleme damit, wie ich die Mapper-Funktion dazu bringen kann, mit meinen Primärschlüsseln zu arbeiten. Ich brauche nicht wirklich Primärschlüssel, aber die Mapper-Funktion erfordert es. 

Danke für die Einsichten.

from sqlalchemy import create_engine Table, Column, MetaData
from sqlalchemy.orm import mapper, create_session
from sqlalchemy.ext.declarative import declarative_base
from pandas.io.sql import SQLTable, SQLDatabase

def bulk_upload(table, schema, name):
    e = create_engine('mssql+pyodbc://MYDB')
    s = create_session(bind=e)
    m = MetaData(bind=e,reflect=True,schema=schema)
    Base = declarative_base(bind=e,metadata=m)
    t = Table(name,m)
    m.remove(t)
    t.drop(checkfirst=True)
    sqld = SQLDatabase(e, schema=schema,meta=m)
    sqlt = SQLTable(name, sqld, table).table
    sqlt.metadata = m
    m.create_all(bind=e,tables=[sqlt])    
    class MyClass(Base):
        return
    mapper(MyClass, sqlt)    

    s.bulk_insert_mappings(MyClass, table.to_dict(orient='records'))
    return
23
Charles

Ich hatte ein ähnliches Problem mit pd.to_sql, das Stunden dauerte, um Daten hochzuladen. Der folgende Code-Bulk fügte in wenigen Sekunden die gleichen Daten hinzu. 

from sqlalchemy import create_engine
import psycopg2 as pg
#load python script that batch loads pandas df to sql
import cStringIO

address = 'postgresql://<username>:<pswd>@<Host>:<port>/<database>'
engine = create_engine(address)
connection = engine.raw_connection()
cursor = connection.cursor()

#df is the dataframe containing an index and the columns "Event" and "Day"
#create Index column to use as primary key
df.reset_index(inplace=True)
df.rename(columns={'index':'Index'}, inplace =True)

#create the table but first drop if it already exists
command = '''DROP TABLE IF EXISTS localytics_app2;
CREATE TABLE localytics_app2
(
"Index" serial primary key,
"Event" text,
"Day" timestamp without time zone,
);'''
cursor.execute(command)
connection.commit()

#stream the data using 'to_csv' and StringIO(); then use sql's 'copy_from' function
output = cStringIO.StringIO()
#ignore the index
df.to_csv(output, sep='\t', header=False, index=False)
#jump to start of stream
output.seek(0)
contents = output.getvalue()
cur = connection.cursor()
#null values become ''
cur.copy_from(output, 'localytics_app2', null="")    
connection.commit()
cur.close()
23
ansonw

Dies wurde bis dahin möglicherweise beantwortet, aber ich habe die Lösung gefunden, indem ich verschiedene Antworten auf dieser Site zusammenstellte und mit dem Dokument von SQLAlchemy abgeglichen wurde.

  1. Die Tabelle muss bereits in db1 vorhanden sein. mit einem Index, bei dem auto_increment aktiviert ist.
  2. Die Klasse Aktuell muss mit dem in CSV importierten Datenframe und der Tabelle in db1 übereinstimmen.

Ich hoffe, das hilft jedem, der hierher kommt und Panda und SQLAlchemy schnell mischen möchte.

from urllib import quote_plus as urlquote
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Numeric
from sqlalchemy.orm import sessionmaker
import pandas as pd


# Set up of the engine to connect to the database
# the urlquote is used for passing the password which might contain special characters such as "/"
engine = create_engine('mysql://root:%[email protected]/db1' % urlquote('weirdPassword*withsp€cialcharacters'), echo=False)
conn = engine.connect()
Base = declarative_base()

#Declaration of the class in order to write into the database. This structure is standard and should align with SQLAlchemy's doc.
class Current(Base):
    __table= 'tableName'

    id = Column(Integer, primary_key=True)
    Date = Column(String(500))
    Type = Column(String(500))
    Value = Column(Numeric())

    def __repr__(self):
        return "(id='%s', Date='%s', Type='%s', Value='%s')" % (self.id, self.Date, self.Type, self.Value)

# Set up of the table in db and the file to import
fileToRead = 'file.csv'
tableToWriteTo = 'tableName'

# Panda to create a lovely dataframe
df_to_be_written = pd.read_csv(fileToRead)
# The orient='records' is the key of this, it allows to align with the format mentioned in the doc to insert in bulks.
listToWrite = df_to_be_written.to_dict(orient='records')

metadata = sqlalchemy.schema.MetaData(bind=engine,reflect=True)
table = sqlalchemy.Table(tableToWriteTo, metadata, autoload=True)

# Open the session
Session = sessionmaker(bind=engine)
session = Session()

# Inser the dataframe into the database in one bulk
conn.execute(table.insert(), listToWrite)

# Commit the changes
session.commit()

# Close the session
session.close()
16
AkaGonjo

Basierend auf den Antworten von @ansonw:

def to_sql(engine, df, table, if_exists='fail', sep='\t', encoding='utf8'):
    # Create Table
    df[:0].to_sql(table, engine, if_exists=if_exists)

    # Prepare data
    output = cStringIO.StringIO()
    df.to_csv(output, sep=sep, header=False, encoding=encoding)
    output.seek(0)

    # Insert data
    connection = engine.raw_connection()
    cursor = connection.cursor()
    cursor.copy_from(output, table, sep=sep, null='')
    connection.commit()
    cursor.close()

Ich füge 200000 Zeilen in 5 Sekunden anstelle von 4 Minuten ein

8

Meine postgres-spezifische Lösung unten erstellt automatisch die Datenbanktabelle unter Verwendung Ihres Pandas-Datenrahmens und führt eine schnelle Masseneinfügung unter Verwendung der postgres COPY my_table FROM ... aus.

import io

import pandas as pd
from sqlalchemy import create_engine

def write_to_table(df, db_engine, schema, table_name, if_exists='fail'):
    string_data_io = io.StringIO()
    df.to_csv(string_data_io, sep='|', index=False)
    pd_sql_engine = pd.io.sql.pandasSQL_builder(db_engine, schema=schema)
    table = pd.io.sql.SQLTable(table_name, pd_sql_engine, frame=df,
                               index=False, if_exists=if_exists, schema=schema)
    table.create()
    string_data_io.seek(0)
    string_data_io.readline()  # remove header
    with db_engine.connect() as connection:
        with connection.connection.cursor() as cursor:
            copy_cmd = "COPY %s.%s FROM STDIN HEADER DELIMITER '|' CSV" % (schema, table_name)
            cursor.copy_expert(copy_cmd, string_data_io)
        connection.connection.commit()
3
mgoldwasser

Hier ist die einfache Methode

.

Laden Sie die Treiber für die SQL-Datenbankkonnektivität herunter

Für Linux und Mac OS:

https://docs.Microsoft.com/de-de/sql/connect/odbc/linux-mac/installing-the-Microsoft-odbc-driver-for-sql-server?view=sql-server- 2017

Für Windows:

https://www.Microsoft.com/en-us/download/details.aspx?id=56567

Verbindung erstellen

from sqlalchemy import create_engine 
import urllib
server = '*****'
database = '********'
username = '**********'
password = '*********'

params = urllib.parse.quote_plus(
'DRIVER={ODBC Driver 17 for SQL Server};'+ 
'SERVER='+server+';DATABASE='+database+';UID='+username+';PWD='+ password) 

engine = create_engine("mssql+pyodbc:///?odbc_connect=%s" % params) 

#Checking Connection 
connected = pd.io.sql._is_sqlalchemy_connectable(engine)

print(connected)   #Output is True if connection established successfully

Einfügen von Daten

df.to_sql('Table_Name', con=engine, if_exists='append', index=False)


"""
if_exists: {'fail', 'replace', 'append'}, default 'fail'
     fail: If table exists, do nothing.
     replace: If table exists, drop it, recreate it, and insert data.
     append: If table exists, insert data. Create if does not exist.
"""

Wenn es viele Datensätze gibt

# limit based on sp_prepexec parameter count
tsql_chunksize = 2097 // len(bd_pred_score_100.columns)
# cap at 1000 (limit for number of rows inserted by table-value constructor)
tsql_chunksize = 1000 if tsql_chunksize > 1000 else tsql_chunksize
print(tsql_chunksize)


df.to_sql('table_name', con = engine, if_exists = 'append', index= False, chunksize=tsql_chunksize)

PS: Sie können die Parameter gemäß Ihren Anforderungen ändern.

2
Suhas_Pote

Da dies eine große E/A-Belastung ist, können Sie das Python-Threading-Modul auch über multiprocessing.dummy verwenden. Dies beschleunigte die Dinge für mich:

import math
from multiprocessing.dummy import Pool as ThreadPool

...

def insert_df(df, *args, **kwargs):
    nworkers = 4

    chunksize = math.floor(df.shape[0] / nworkers)
    chunks = [(chunksize * i, (chunksize * i) + chunksize) for i in range(nworkers)]
    chunks.append((chunksize * nworkers, df.shape[0]))
    pool = ThreadPool(nworkers)

    def worker(chunk):
        i, j = chunk
        df.iloc[i:j, :].to_sql(*args, **kwargs)

    pool.map(worker, chunks)
    pool.close()
    pool.join()


....

insert_df(df, "foo_bar", engine, if_exists='append')
1
dgorissen

Das funktionierte für mich, um mit cx_Oracle und SQLALchemy eine Verbindung zu Oracle Database herzustellen

import sqlalchemy
import cx_Oracle
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String
from sqlalchemy.orm import sessionmaker
import pandas as pd

# credentials
username = "username"
password = "password"
connectStr = "connection:/string"
tableName = "tablename"

t0 = time.time()

# connection
dsn = cx_Oracle.makedsn('Host','port',service_name='servicename')

Base = declarative_base()

class LANDMANMINERAL(Base):
    __table= 'tablename'

    DOCUMENTNUM = Column(String(500), primary_key=True)
    DOCUMENTTYPE = Column(String(500))
    FILENUM = Column(String(500))
    LEASEPAYOR = Column(String(500))
    LEASESTATUS = Column(String(500))
    PROSPECT = Column(String(500))
    SPLIT = Column(String(500))
    SPLITSTATUS = Column(String(500))

engine = create_engine('Oracle+cx_Oracle://%s:%[email protected]%s' % (username, password, dsn))
conn = engine.connect()  

Base.metadata.bind = engine

# Creating the session

DBSession = sessionmaker(bind=engine)

session = DBSession()

# Bulk insertion
data = pd.read_csv('data.csv')
lists = data.to_dict(orient='records')


table = sqlalchemy.Table('landmanmineral', Base.metadata, autoreload=True)
conn.execute(table.insert(), lists)

session.commit()

session.close() 

print("time taken %8.8f seconds" % (time.time() - t0) )
0
bootstrap

In Pandas 0.25.1 gibt es einen Parameter zum Ausführen von Mehrfacheinfügungen. Daher muss dieses Problem in SQLAlchemy nicht mehr umgangen werden.

Stellen Sie method='multi' Ein, wenn Sie pandas.DataFrame.to_sql Aufrufen.

In diesem Beispiel wäre es df.to_sql(table, schema=schema, con=e, index=False, if_exists='replace', method='multi')

Antwort von docs hier

Bemerkenswert, dass ich das nur mit Redshift getestet habe. Bitte lassen Sie mich wissen, wie es in anderen Datenbanken läuft, damit ich diese Antwort aktualisieren kann.

0

für Leute wie mich, die versuchen, die oben genannten Lösungen zu implementieren:

Pandas 0.24.0 hat jetzt to_sql mit chunksize und method = 'multi' Option, die in großen Mengen eingefügt wird.

0
freddy888