Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

campo di conteggio to_sql pyodbc errato o errore di sintassi

Nel momento in cui è stata posta questa domanda, Pandas 0.23.0 era appena stato rilasciato. Quella versione ha cambiato il comportamento predefinito di .to_sql() dalla chiamata al DBAPI .executemany() metodo per costruire un costruttore di valori di tabella (TVC) che migliorerebbe la velocità di caricamento inserendo più righe con un singolo .execute() chiamata di un'istruzione INSERT. Sfortunatamente quell'approccio spesso superava il limite di 2100 valori di parametro di T-SQL per una procedura memorizzata, portando all'errore citato nella domanda.

Poco dopo, un successivo rilascio di panda ha aggiunto un method= argomento in .to_sql() . L'impostazione predefinita:method=None – ripristinato il comportamento precedente dell'utilizzo di .executemany() , specificando method="multi" direbbe a .to_sql() per utilizzare il nuovo approccio TVC.

Più o meno nello stesso periodo, SQLAlchemy 1.3 è stato rilasciato e ha aggiunto un fast_executemany=True argomento per create_engine() che ha notevolmente migliorato la velocità di caricamento utilizzando i driver ODBC di Microsoft per SQL Server. Con quel miglioramento, method=None dimostrato di essere veloce almeno quanto method="multi" evitando il limite di 2100 parametri.

Quindi, con le versioni attuali di panda, SQLAlchemy e pyodbc, l'approccio migliore per l'utilizzo di .to_sql() con i driver ODBC di Microsoft per SQL Server è usare fast_executemany=True e il comportamento predefinito di .to_sql() , cioè

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")

Questo è l'approccio consigliato per le app in esecuzione su Windows, macOS e le varianti Linux supportate da Microsoft per il driver ODBC. Se devi usare FreeTDS ODBC, allora .to_sql() può essere chiamato con method="multi" e chunksize= come descritto di seguito.

(Risposta originale)

Prima della versione panda 0.23.0, to_sql genererebbe un INSERT separato per ogni riga nella DataTable:

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'

Presumibilmente per migliorare le prestazioni, pandas 0.23.0 ora genera un costruttore di valori di tabella per inserire più righe per chiamata

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'

Il problema è che le stored procedure di SQL Server (incluse le stored procedure di sistema come sp_prepexec ) sono limitati a 2100 parametri, quindi se DataFrame ha 100 colonne allora to_sql può inserire solo circa 20 righe alla volta.

Possiamo calcolare il chunksize richiesto usando

# 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)

Tuttavia, è probabile che l'approccio più veloce sia ancora:

  • scarica il DataFrame in un file CSV (o simile), quindi

  • fai in modo che Python chiami SQL Server bcp utility per caricare quel file nella tabella.