По времето, когато беше зададен този въпрос, pandas 0.23.0 току-що беше пусната. Тази версия промени поведението по подразбиране на .to_sql()
от извикване на DBAPI .executemany()
метод за конструиране на конструктор на стойност на таблица (TVC), който би подобрил скоростта на качване чрез вмъкване на множество реда с един .execute()
извикване на оператор INSERT. За съжаление този подход често надвишава ограничението на T-SQL от 2100 стойности на параметъра за съхранена процедура, което води до грешката, цитирана във въпроса.
Малко след това последващо издание на pandas добави method=
аргумент към .to_sql()
. По подразбиране – method=None
– възстановено предишното поведение при използване на .executemany()
, като указвате method="multi"
ще каже на .to_sql()
да използвате по-новия подход на TVC.
Приблизително по същото време беше пусната SQLAlchemy 1.3 и добави fast_executemany=True
аргумент към create_engine()
което значително подобри скоростта на качване с помощта на ODBC драйверите на Microsoft за SQL Server. С това подобрение, method=None
се оказа, че е поне толкова бърз, колкото method="multi"
като избягвате ограничението от 2100 параметъра.
Така че с текущите версии на pandas, SQLAlchemy и pyodbc, най-добрият подход за използване на .to_sql()
с ODBC драйверите на Microsoft за SQL Server е да използвате fast_executemany=True
и поведението по подразбиране на .to_sql()
, т.е.
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")
Това е препоръчителният подход за приложения, работещи под Windows, macOS и вариантите на Linux, които Microsoft поддържа за своя ODBC драйвер. Ако трябва да използвате FreeTDS ODBC, тогава .to_sql()
може да се извика с method="multi"
и chunksize=
както е описано по-долу.
(Оригинален отговор)
Преди pandas версия 0.23.0, to_sql
ще генерира отделен INSERT за всеки ред в 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'
Вероятно, за да подобри производителността, pandas 0.23.0 вече генерира конструктор на стойност на таблица за вмъкване на множество реда на извикване
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'
Проблемът е, че съхранените процедури на SQL Server (включително системни съхранени процедури като sp_prepexec
) са ограничени до 2100 параметъра, така че ако DataFrame има 100 колони, тогава to_sql
може да вмъкне само около 20 реда наведнъж.
Можем да изчислим необходимия chunksize
използвайки
# 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)
Въпреки това, най-бързият подход все още вероятно ще бъде:
-
изхвърлете DataFrame в CSV файл (или подобен) и след това
-
накарайте Python да извика SQL Server
bcp
помощна програма за качване на този файл в таблицата.