En el momento en que se hizo esta pregunta, se acababa de lanzar pandas 0.23.0. Esa versión cambió el comportamiento predeterminado de .to_sql()
de llamar al DBAPI .executemany()
método para construir un constructor de valores de tabla (TVC) que mejoraría la velocidad de carga mediante la inserción de varias filas con un solo .execute()
llamada de una instrucción INSERT. Desafortunadamente, ese enfoque a menudo excedía el límite de T-SQL de 2100 valores de parámetros para un procedimiento almacenado, lo que generaba el error citado en la pregunta.
Poco después, una versión posterior de pandas agregó un method=
argumento para .to_sql()
. El valor predeterminado:method=None
– restauró el comportamiento anterior de usar .executemany()
, al especificar method="multi"
le diría a .to_sql()
para usar el nuevo enfoque de TVC.
Casi al mismo tiempo, se lanzó SQLAlchemy 1.3 y agregó un fast_executemany=True
argumento para create_engine()
que mejoró en gran medida la velocidad de carga utilizando los controladores ODBC de Microsoft para SQL Server. Con esa mejora, method=None
demostró ser al menos tan rápido como method="multi"
mientras evita el límite de 2100 parámetros.
Entonces, con las versiones actuales de pandas, SQLAlchemy y pyodbc, el mejor enfoque para usar .to_sql()
con los controladores ODBC de Microsoft para SQL Server es usar fast_executemany=True
y el comportamiento predeterminado de .to_sql()
, es decir,
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")
Este es el enfoque recomendado para las aplicaciones que se ejecutan en Windows, macOS y las variantes de Linux que admite Microsoft para su controlador ODBC. Si necesita usar FreeTDS ODBC, entonces .to_sql()
se puede llamar con method="multi"
y chunksize=
como se describe a continuación.
(Respuesta original)
Antes de la versión 0.23.0 de pandas, to_sql
generaría un INSERT separado para cada fila en el 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'
Presumiblemente para mejorar el rendimiento, pandas 0.23.0 ahora genera un constructor de valores de tabla para insertar varias filas por llamada
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'
El problema es que los procedimientos almacenados de SQL Server (incluidos los procedimientos almacenados del sistema como sp_prepexec
) están limitados a 2100 parámetros, por lo que si el DataFrame tiene 100 columnas, entonces to_sql
solo puede insertar unas 20 filas a la vez.
Podemos calcular el chunksize
requerido 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)
Sin embargo, es probable que el enfoque más rápido sea:
-
volcar el DataFrame en un archivo CSV (o similar), y luego
-
hacer que Python llame al servidor SQL
bcp
utilidad para cargar ese archivo en la tabla.