如何使用pyodbc从CSV加速批量插入到MS SQL Server

Tan*_*lee 26 python sql-server bulkinsert pyodbc sql-server-2012

下面是我想要帮助的代码.我不得不运行超过1,300,000行,这意味着插入~300,000行需要40分钟.

我认为批量插入是加速它的路线?或者是因为我通过for data in reader:部分迭代行?

#Opens the prepped csv file
with open (os.path.join(newpath,outfile), 'r') as f:
    #hooks csv reader to file
    reader = csv.reader(f)
    #pulls out the columns (which match the SQL table)
    columns = next(reader)
    #trims any extra spaces
    columns = [x.strip(' ') for x in columns]
    #starts SQL statement
    query = 'bulk insert into SpikeData123({0}) values ({1})'
    #puts column names in SQL query 'query'
    query = query.format(','.join(columns), ','.join('?' * len(columns)))

    print 'Query is: %s' % query
    #starts curser from cnxn (which works)
    cursor = cnxn.cursor()
    #uploads everything by row
    for data in reader:
        cursor.execute(query, data)
        cursor.commit()
Run Code Online (Sandbox Code Playgroud)

我是故意动态选择我的列标题(因为我想创建最可能的pythonic代码).

SpikeData123是表名.

Gor*_*son 41

如对另一个答案的评论中所述,T-SQL BULK INSERT命令仅在要导入的文件与SQL Server实例位于同一台计算机上或位于SQL Server实例可读取的SMB/CIFS网络位置时才有效.因此,它可能不适用于源文件位于远程客户端上的情况.

pyodbc 4.0.19添加了一个Cursor#fast_executemany功能,在这种情况下可能会有所帮助.fast_executemany默认为"关闭",以下测试代码......

cnxn = pyodbc.connect(conn_str, autocommit=True)
crsr = cnxn.cursor()
crsr.execute("TRUNCATE TABLE fast_executemany_test")

sql = "INSERT INTO fast_executemany_test (txtcol) VALUES (?)"
params = [(f'txt{i:06d}',) for i in range(1000)]
t0 = time.time()
crsr.executemany(sql, params)
print(f'{time.time() - t0:.1f} seconds')
Run Code Online (Sandbox Code Playgroud)

...在我的测试机器上执行大约需要22秒.只需添加crsr.fast_executemany = True......

cnxn = pyodbc.connect(conn_str, autocommit=True)
crsr = cnxn.cursor()
crsr.execute("TRUNCATE TABLE fast_executemany_test")

crsr.fast_executemany = True  # new in pyodbc 4.0.19

sql = "INSERT INTO fast_executemany_test (txtcol) VALUES (?)"
params = [(f'txt{i:06d}',) for i in range(1000)]
t0 = time.time()
crsr.executemany(sql, params)
print(f'{time.time() - t0:.1f} seconds')
Run Code Online (Sandbox Code Playgroud)

...将执行时间缩短到1秒多一点.

  • 你会如何使用这种方法从`DataFrame`插入?我试过`df.values.tolist()`作为SQL查询的`VALUES`部分,但是没有用.另外,`.txt.或`.csv`文件实际上会在你的答案中出现在哪里? (3认同)
  • @CameronTaylor - 有关在pandas中使用`fast_executemany`(通过SQLAlchemy)的详细信息,请参阅[此答案](/sf/answers/3364555771/). (2认同)

Gor*_*son 36

BULK INSERT几乎肯定会比阅读源文件一行一行地,做的每一行定期INSERT更快.但是,BULK INSERT和BCP都对CSV文件有很大的限制,因为它们无法处理文本限定符(参见此处).也就是说,如果您的CSV文件中没有合格的文本字符串...

1,Gord Thompson,2015-04-15
2,Bob Loblaw,2015-04-07
Run Code Online (Sandbox Code Playgroud)

...然后你可以BULK INSERT它,但如果它包含文本限定符(因为一些文本值包含逗号)...

1,"Thompson, Gord",2015-04-15
2,"Loblaw, Bob",2015-04-07
Run Code Online (Sandbox Code Playgroud)

...然后BULK INSERT无法处理它.但是,将这样的CSV文件预处理到管道分隔文件中总体上可能更快......

1|Thompson, Gord|2015-04-15
2|Loblaw, Bob|2015-04-07
Run Code Online (Sandbox Code Playgroud)

...或制表符分隔文件(其中BULK INSERT代表制表符)...

1?Thompson, Gord?2015-04-15
2?Loblaw, Bob?2015-04-07
Run Code Online (Sandbox Code Playgroud)

...然后BULK INSERT那个文件.对于后者(制表符分隔)文件,BULK INSERT代码看起来像这样:

import pypyodbc
conn_str = "DSN=myDb_SQLEXPRESS;"
cnxn = pypyodbc.connect(conn_str)
crsr = cnxn.cursor()
sql = """
BULK INSERT myDb.dbo.SpikeData123
FROM 'C:\\__tmp\\biTest.txt' WITH (
    FIELDTERMINATOR='\\t',
    ROWTERMINATOR='\\n'
    );
"""
crsr.execute(sql)
cnxn.commit()
crsr.close()
cnxn.close()
Run Code Online (Sandbox Code Playgroud)

注意:如注释中所述,?只有在SQL Server实例可以直接读取源文件时才执行语句.对于源文件位于远程客户端的情况,请参阅此答案.

  • 我知道这是一个旧帖子,但只有当文件与SQL Server位于同一服务器上时(或在SQL Server的服务用户能够看到的位置),此解决方案才有效.因此,如果文件驻留在我的工作站上,并且SQL Server位于其他地方,那么此解决方案将无效 (11认同)