Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

campo de contagem to_sql pyodbc incorreto ou erro de sintaxe


No momento em que esta pergunta foi feita, o pandas 0.23.0 havia acabado de ser lançado. Essa versão mudou o comportamento padrão de .to_sql() de chamar a DBAPI .executemany() método para construir um construtor de valor de tabela (TVC) que melhoraria a velocidade de upload inserindo várias linhas com um único .execute() chamada de uma instrução INSERT. Infelizmente, essa abordagem geralmente excedeu o limite do T-SQL de 2100 valores de parâmetro para um procedimento armazenado, levando ao erro citado na pergunta.

Pouco tempo depois, uma versão subsequente de pandas adicionou um method= argumento para .to_sql() . O padrão – method=None – restaurou o comportamento anterior de usar .executemany() , enquanto especifica method="multi" diria .to_sql() para usar a nova abordagem TVC.

Na mesma época, o SQLAlchemy 1.3 foi lançado e adicionou um fast_executemany=True argumento para create_engine() que melhorou muito a velocidade de upload usando os drivers ODBC da Microsoft para SQL Server. Com esse aprimoramento, method=None provou ser pelo menos tão rápido quanto method="multi" evitando o limite de 2100 parâmetros.

Assim, com as versões atuais de pandas, SQLAlchemy e pyodbc, a melhor abordagem para usar .to_sql() com os drivers ODBC da Microsoft para SQL Server é usar fast_executemany=True e o comportamento padrão de .to_sql() , ou seja,
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")

Essa é a abordagem recomendada para aplicativos executados no Windows, macOS e nas variantes do Linux que a Microsoft oferece suporte para seu driver ODBC. Se você precisar usar o FreeTDS ODBC, então .to_sql() pode ser chamado com method="multi" e chunksize= como descrito abaixo.

(Resposta original)

Antes do pandas versão 0.23.0, to_sql geraria um INSERT separado para cada linha na 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'

Presumivelmente para melhorar o desempenho, o pandas 0.23.0 agora gera um construtor de valor de tabela para inserir várias linhas por chamada
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'

O problema é que os procedimentos armazenados do SQL Server (incluindo procedimentos armazenados do sistema como sp_prepexec ) são limitados a 2100 parâmetros, portanto, se o DataFrame tiver 100 colunas, to_sql só pode inserir cerca de 20 linhas de cada vez.

Podemos calcular o chunksize necessário 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)

No entanto, a abordagem mais rápida ainda provavelmente será:

  • despeje o DataFrame em um arquivo CSV (ou similar) e, em seguida,

  • faça com que o Python chame o SQL Server bcp utilitário para carregar esse arquivo na tabela.