Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

to_sql Pyodbc-Zählerfeld falsch oder Syntaxfehler

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.