Als diese Frage gestellt wurde, war pandas 0.23.0 gerade veröffentlicht worden. Diese Version hat das Standardverhalten von .to_sql()
geändert vom Aufrufen der DBAPI .executemany()
-Methode zum Erstellen eines Tabellenwertkonstruktors (TVC), der die Upload-Geschwindigkeit verbessern würde, indem mehrere Zeilen mit einem einzigen .execute()
eingefügt werden Aufruf einer INSERT-Anweisung. Leider hat dieser Ansatz oft die T-SQL-Grenze von 2100 Parameterwerten für eine gespeicherte Prozedur überschritten, was zu dem in der Frage genannten Fehler führte.
Kurz danach fügte eine nachfolgende Version von pandas eine method=
hinzu Argument für .to_sql()
. Der Standardwert – method=None
– Wiederherstellung des vorherigen Verhaltens der Verwendung von .executemany()
, während Sie method="multi"
angeben würde .to_sql()
sagen um den neueren TVC-Ansatz zu verwenden.
Etwa zur gleichen Zeit wurde SQLAlchemy 1.3 veröffentlicht und es wurde ein fast_executemany=True
hinzugefügt Argument für create_engine()
Dadurch wurde die Upload-Geschwindigkeit mit den ODBC-Treibern von Microsoft für SQL Server erheblich verbessert. Mit dieser Erweiterung ist method=None
erwies sich als mindestens so schnell wie method="multi"
unter Vermeidung der 2100-Parameter-Grenze.
Also mit aktuellen Versionen von pandas, SQLAlchemy und pyodbc, der beste Ansatz für die Verwendung von .to_sql()
mit den ODBC-Treibern von Microsoft für SQL Server ist die Verwendung von fast_executemany=True
und das Standardverhalten von .to_sql()
, also
connection_uri = (
"mssql+pyodbc://scott:tiger^[email protected]/db_name"
"?driver=ODBC+Driver+17+for+SQL+Server"
)
engine = create_engine(connection_uri, fast_executemany=True)
df.to_sql("table_name", engine, index=False, if_exists="append")
Dies ist der empfohlene Ansatz für Apps, die unter Windows, macOS und den Linux-Varianten ausgeführt werden, die Microsoft für seinen ODBC-Treiber unterstützt. Wenn Sie FreeTDS ODBC verwenden müssen, dann .to_sql()
kann mit method="multi"
aufgerufen werden und chunksize=
wie unten beschrieben.
(ursprüngliche Antwort)
Vor Pandas-Version 0.23.0, to_sql
würde ein separates INSERT für jede Zeile in der DataTable generieren:
exec sp_prepexec @p1 output,N'@P1 int,@P2 nvarchar(6)',
N'INSERT INTO df_to_sql_test (id, txt) VALUES (@P1, @P2)',
0,N'row000'
exec sp_prepexec @p1 output,N'@P1 int,@P2 nvarchar(6)',
N'INSERT INTO df_to_sql_test (id, txt) VALUES (@P1, @P2)',
1,N'row001'
exec sp_prepexec @p1 output,N'@P1 int,@P2 nvarchar(6)',
N'INSERT INTO df_to_sql_test (id, txt) VALUES (@P1, @P2)',
2,N'row002'
Vermutlich um die Leistung zu verbessern, generiert Pandas 0.23.0 jetzt einen Tabellenwert-Konstruktor, um mehrere Zeilen pro Aufruf einzufügen
exec sp_prepexec @p1 output,N'@P1 int,@P2 nvarchar(6),@P3 int,@P4 nvarchar(6),@P5 int,@P6 nvarchar(6)',
N'INSERT INTO df_to_sql_test (id, txt) VALUES (@P1, @P2), (@P3, @P4), (@P5, @P6)',
0,N'row000',1,N'row001',2,N'row002'
Das Problem besteht darin, dass gespeicherte SQL Server-Prozeduren (einschließlich gespeicherter Systemprozeduren wie sp_prepexec
) sind auf 2100 Parameter begrenzt, wenn also der DataFrame 100 Spalten hat, dann to_sql
kann nur etwa 20 Zeilen gleichzeitig einfügen.
Wir können die erforderliche chunksize
berechnen mit
# df is an existing DataFrame
#
# limit based on sp_prepexec parameter count
tsql_chunksize = 2097 // len(df.columns)
# cap at 1000 (limit for number of rows inserted by table-value constructor)
tsql_chunksize = 1000 if tsql_chunksize > 1000 else tsql_chunksize
#
df.to_sql('tablename', engine, index=False, if_exists='replace',
method='multi', chunksize=tsql_chunksize)
Der schnellste Ansatz ist jedoch wahrscheinlich immer noch:
-
Dump den DataFrame in eine CSV-Datei (oder ähnliches) und dann
-
Lassen Sie Python den SQL Server
bcp
aufrufen Dienstprogramm, um diese Datei in die Tabelle hochzuladen.