Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

to_sql champ de comptage pyodbc incorrect ou erreur de syntaxe

Au moment où cette question a été posée, pandas 0.23.0 venait de sortir. Cette version a changé le comportement par défaut de .to_sql() d'appeler le DBAPI .executemany() méthode pour construire un constructeur de valeur de table (TVC) qui améliorerait la vitesse de téléchargement en insérant plusieurs lignes avec un seul .execute() appel d'une instruction INSERT. Malheureusement, cette approche dépassait souvent la limite de 2 100 valeurs de paramètre de T-SQL pour une procédure stockée, ce qui entraînait l'erreur citée dans la question.

Peu de temps après, une version ultérieure de pandas a ajouté une method= argument de .to_sql() . La valeur par défaut - method=None - restauré le comportement précédent d'utilisation de .executemany() , en spécifiant method="multi" dirait .to_sql() pour utiliser la nouvelle approche TVC.

À peu près au même moment, SQLAlchemy 1.3 a été publié et a ajouté un fast_executemany=True argument de create_engine() ce qui a considérablement amélioré la vitesse de téléchargement en utilisant les pilotes ODBC de Microsoft pour SQL Server. Avec cette amélioration, method=None s'est avéré au moins aussi rapide que method="multi" tout en évitant la limite de 2100 paramètres.

Donc, avec les versions actuelles de pandas, SQLAlchemy et pyodbc, la meilleure approche pour utiliser .to_sql() avec les pilotes ODBC de Microsoft pour SQL Server est d'utiliser fast_executemany=True et le comportement par défaut de .to_sql() , c'est-à-dire

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

Il s'agit de l'approche recommandée pour les applications exécutées sur Windows, macOS et les variantes Linux prises en charge par Microsoft pour son pilote ODBC. Si vous devez utiliser FreeTDS ODBC, alors .to_sql() peut être appelé avec method="multi" et chunksize= comme décrit ci-dessous.

(Réponse originale)

Avant la version 0.23.0 de pandas, to_sql générerait un INSERT séparé pour chaque ligne du 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'

Vraisemblablement pour améliorer les performances, pandas 0.23.0 génère désormais un constructeur de valeur de table pour insérer plusieurs lignes par appel

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'

Le problème est que les procédures stockées de SQL Server (y compris les procédures stockées système telles que sp_prepexec ) sont limités à 2100 paramètres, donc si le DataFrame a 100 colonnes alors to_sql ne peut insérer qu'environ 20 lignes à la fois.

Nous pouvons calculer la chunksize requise en utilisant

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

Cependant, l'approche la plus rapide reste probablement :

  • vider le DataFrame dans un fichier CSV (ou similaire), puis

  • demander à Python d'appeler SQL Server bcp utilitaire pour télécharger ce fichier dans la table.