web-dev-qa-db-ja.com

pyodbcを使用してCSVからMS SQL Serverへの一括挿入を高速化する方法

以下は、助けて欲しい私のコードです。 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()

私は列ヘッダーを意図的に動的に選択しています(可能な限りPythonコードを作成したいので)。

SpikeData123はテーブル名です。

26
TangoAlee

更新:@SimonLangのコメントで述べたように、SQL Server 2017以降のBULK INSERTは、CSVファイルのテキスト修飾子をサポートしているようです(参照: here )。


BULK INSERTは、ほぼ確実にmuch行ごとにソースファイルを読み取り、各行に対して通常のINSERTを実行するよりも高速です。ただし、BULK INSERTとBCPの両方には、テキスト修飾子を参照できないという点でCSVファイルに関する重要な制限があります(参照: here )。つまり、CSVファイルにnotに修飾テキスト文字列が含まれていない場合...

1,Gord Thompson,2015-04-15
2,Bob Loblaw,2015-04-07

...その後、BULK INSERTできますが、テキスト修飾子が含まれている場合(一部のテキスト値にコンマが含まれているため)...

1,"Thompson, Gord",2015-04-15
2,"Loblaw, Bob",2015-04-07

... BULK INSERTは処理できません。それでも、このようなCSVファイルをパイプ区切りファイルに前処理する方が全体的に速いかもしれません...

1|Thompson, Gord|2015-04-15
2|Loblaw, Bob|2015-04-07

...またはタブ区切りファイル(はタブ文字を表します)...

1→Thompson, Gord→2015-04-15
2→Loblaw, Bob→2015-04-07

...そして、そのファイルを一括挿入します。後者の(タブ区切り)ファイルの場合、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()

注:コメントで述べたように、BULK INSERTステートメントの実行は、SQL Serverインスタンスがソースファイルを直接読み取ることができる場合にのみ適用できます。ソースファイルがリモートクライアントにある場合は、 this answer を参照してください。

33
Gord Thompson

別の回答のコメントに記載されているように、T-SQL BULK INSERTコマンドは、インポートするファイルがSQL Serverインスタンスと同じマシン上にあるか、SQLが実行される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')

...テストマシンで実行するのに約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')

...実行時間が1秒強に短縮されました。

40
Gord Thompson

はい、一括挿入は、大きなファイルをDBにロードするための正しいパスです。一見すると、時間がかかる理由は、あなたが言及したように、ファイルからデータの各行をループしているということです。その名前は、データのチャックを挿入するために使用されることを暗示していることを覚えておいてください。ループを削除して、再試行します。

また、私には正しく表示されないため、一括挿入の構文を再確認します。 pyodbcによって生成されたsqlを確認してください。通常の挿入のみを実行しているのではないかと思う

あるいは、それでもまだ遅い場合は、SQLから直接バルク挿入を使用して、ファイル全体をバルク挿入で一時テーブルにロードしてから、関連する列を適切なテーブルに挿入します。または、一括挿入とbcpを組み合わせて使用​​して、特定の列を挿入するか、OPENROWSETを取得します。

1
Michael Moura