使用Python将csv文件批量化导入SQL Server

一开始的时候,是想用bulk insert的方法直接将csv文件导入数据库的,后来发现这一方法在SQL语法上是可行的,但是由于Python需要pymmsql包来与SQL Server交互,而这个包似乎并不支持这一语句=。= 无奈只能另想办法,曲线救国,先将csv文件读取到内存中,然后用cursor.executemany的方法将数据导入。虽然慢是慢了点,不过好歹实现了目的。如果有大神有更快的办法,还请赐教

在网上下了所有股票91年至今每天的交易数据,下下来之后发现是每个股票一个csv文件,那么总共有好几千的文件。这么多的文件指望用手动的方法在MSSQL中导入是不可能的,所以只能用脚本来实现。在发现bulk insert似乎行不通以后,只能转而使用executemany指令来实现。

1.首先需要下载pymssql包 http://www.pymssql.org/en/latest/

2.在插入之前,由于bulk insert不行,那么就需要先将csv文件读入到内存中,然后用insert…指令插入。如果文件中列的设置与数据库表的列的设置不一致,则还需要修改,筛选文件中的内容,挑选出要插入的那几列。在读取csv文件的过程中,还需要注意一个问题,就是现在网上大部分的程序都是reader=csv.reader(open('file_name.csv', 'rb'))的格式,但是我在用的时候发现会报错

_csv.Error: iterator should return strings, not bytes (did you open the file in text mode?)

后来在http://bugs.python.org/msg82661中,找到了问题所在。

Sorry, folks, we’ve got an understanding problem here. CSV files are
typically NOT created by text editors. They are created e.g. by “save
as csv” from a spreadsheet program, or as an output option by some
database query program. They can have just about any character in a
field, including \r and \n. Fields containing those characters should
be quoted (just like a comma) by the csv file producer. A csv reader
should be capable of reproducing the original field division. Here for
example is a dump of a little file I just created using Excel 2003:
… This sentence in the documentation is NOT an error: “”“If csvfile
is a file object, it must be opened with the ‘b’ flag on platforms
where that makes a difference.”“”

根据上面这段话,将代码改成下面这样就OK了:

reader=csv.reader(open('file_name.csv', 'r'))
for item in reader:
    print(item)

3.在将csv文件读入到内存中之后,需要比对文件中列与待插入的数据表列的异同,需要设立一个数组来表示两者间的映射关系。这就需要从数据库中获得待插入表的列名信息。可以用如下sql语句获得:

select name from syscolumns where id=object_id(N'your_table_name')

然而在实际操作过程中,可能会因为没指定数据库而报错。所以还需要再加上一句,如下面所示:

use your_database_name;select name from syscolumns where id=object_id(N'your_table_name')

在python中,返回的列名为’name’格式,需要进行一些简单的处理才能使用

4.使用executemany指令插入。executemany的使用方法如下

cursor.executemany(
    "INSERT INTO persons VALUES (%d, %s, %s)",
    [(1, 'John Smith', 'John Doe'),
     (2, 'Jane Doe', 'Joe Dog'),
     (3, 'Mike T.', 'Sarah H.')])
# you must call commit() to persist your data if you don't set autocommit to True
conn.commit()

这里又两个要注意的地方,一是指令的数据部分
[(1, 'John Smith', 'John Doe'),(2, 'Jane Doe', 'Joe Dog'),(3, 'Mike T.', 'Sarah H.')]
是一个list,而list的组成单元必须要是tuple或者Dictionary,也就是元组或词典形式,否则会报错。而csv读取到内存中是二维数组的形式,因此需要用tuple()函数转化成元组形式才能插入

第二个要注意的部分就是执行完cursor.executemany指令以后,一定要执行conn.commit(),否则是没有效果的。

5.性能提升。为了提高插入的速度,目前我把文件的读取跟插入分开了,也就是说不是读取一个文件就插入一次,而是读取多个文件后统一插入一次。另外,还可以想办法通过多线程同时读取文件的方式,进一步提高运行速率

目前代码如下

# @function :   import csv data into mysql database
# @author:      multiangle
# @date:        2015/8/14

import pymssql
import os
import csv

class MSSQL_Interface :
    def __init__(self,host,user,pwd,dbname):
        self.host=host
        self.user=user
        self.pwd=pwd
        self.dbname=dbname

        self.connect_db()

    def connect_db(self):
        try:
            self.conn=pymssql.connect(user=self.user,password=self.pwd,host=self.host,database=self.dbname,charset="utf8")
            self.cur=self.conn.cursor()
        except:
            print("ERROR: fail to connect mssql")

    def __del__(self):
        self.cur.close()
        self.conn.close()

    def get_col_name(self,table_name):
        q1="use "+self.dbname+';'
        q2="select name from syscolumns where id=object_id(N'"+table_name+"')"
        query=q1+q2
        self.cur.execute(query)
        col_pre_name=self.cur.fetchall()
        col_num=col_pre_name.__len__()
        col_name=[]
        for item in col_pre_name:
            col_name.append(item[0])
        return col_name

    def read_csv(self,path,table_col): #需要输入表的列名
        file=csv.reader(open(path,'r'))
        data_list=[]
        num=0
        for row in file:
            if num==0:
                file_col=row
                mapping_array=[-1]*table_col.__len__()
                for i in range(0,table_col.__len__()):
                    try:
                        temp_index=file_col.index(table_col[i])
                        mapping_array[i]=temp_index         #mapping_arry[i]记录表中第i列的数据在待导入文件的第几列
                    except:
                        pass
            else:
                temp_new=[]
                for i in range(0,table_col.__len__()):
                    if mapping_array[i]>=0:
                        temp_new.append(row[mapping_array[i]])
                    else:
                        temp_new.append("''")
                data_list.append(tuple(temp_new))
            num=num+1
        return data_list

    def import_csv_file_weak(self,path,table_name):
        #会先将欲引入的文件与表的列对比,值选择导入数据表中已经存在的列的内容,对于不存在与数据表中的数据,则不导入
        table_col=self.get_col_name(table_name)
        data_list=self.read_csv(path,table_col)

        q0="use "+self.dbname+';'
        q1="insert into "+table_name+" values("
        temp_q=""
        for i in range(0,table_col.__len__()):
            temp_q=temp_q+"%s"
            if i1 :
                temp_q=temp_q+','
        query=q0+q1+temp_q+")"
        # try:
        self.cur.executemany(query,data_list)
        self.conn.commit()

    def import_folder(self,path,table_name):
        #只能处理一个文件夹内文件格式统一的文件
        table_col=self.get_col_name(table_name)
        file_list=os.listdir(path)
        extension_name=file_list[0][file_list[0].index('.')+1:file_list[0].__len__()]
        if extension_name=='csv':
            temp_data_list=[]
            temp_file_list=""
            for name in file_list:
                file_path=path+'\\'+name
                temp_file_list=temp_file_list+name+'\t'
                temp_data_list=temp_data_list+self.read_csv(file_path,table_col)
                if temp_data_list.__len__()>20000: #如果超过1W行,则读写一次
                    try:
                        self.import_list_weak(temp_data_list,table_name)
                        print(temp_file_list+" insert success, "+str(temp_data_list.__len__())+" lines is influenced")
                        temp_data_list=[]
                        temp_file_list=""
                    except:
                        print("--------------------------------------------------ERROR: "+temp_file_list+" insert fail--------------------------------------------------")


    def import_list_weak(self,data_list,table_name):    #输入已经整理好的list格式
        q0="use "+self.dbname+';'
        q1="insert into "+table_name+" values("
        temp_q=""
        for i in range(0,data_list[0].__len__()):
            temp_q=temp_q+"%s"
            if i0].__len__()-1 :
                temp_q=temp_q+','
        query=q0+q1+temp_q+")"

        self.cur.executemany(query,data_list)
        self.conn.commit()

if __name__ == '__main__':
    host='127.0.0.1'
    user='sa'
    pwd='admin'
    db='multiangle_investment'
    item=MSSQL_Interface(host,user,pwd,db)
    item.import_folder('D:\\multiangle\\Stock\\Data\\History\\overview-data-sh','stock_past_data')

你可能感兴趣的:(python,股票,数据库相关)