MS SQLを実行しているリモートサーバーに大きなpandas.DataFrame
を送信したいと思います。私が今やる方法は、data_frame
オブジェクトをタプルのリストに変換し、pyODBCのexecutemany()
関数で送信することです。次のようになります。
import pyodbc as pdb
list_of_tuples = convert_df(data_frame)
connection = pdb.connect(cnxn_str)
cursor = connection.cursor()
cursor.fast_executemany = True
cursor.executemany(sql_statement, list_of_tuples)
connection.commit()
cursor.close()
connection.close()
それから、data_frame.to_sql()
メソッドを使用することで、物事を高速化できるか(少なくとももっと読みやすいか)疑問に思い始めました。私は次の解決策を思いつきました:
import sqlalchemy as sa
engine = sa.create_engine("mssql+pyodbc:///?odbc_connect=%s" % cnxn_str)
data_frame.to_sql(table_name, engine, index=False)
これでコードは読みやすくなりましたが、アップロードは少なくとも150倍遅い ...です。
SQLAlchemyを使用するときにfast_executemany
を反転する方法はありますか?
私はpandas-0.20.3、pyODBC-4.0.21およびsqlalchemy-1.1.13を使用しています。
SQLAlchemyの開発者に連絡した後、この問題を解決する方法が登場しました。すばらしい仕事に感謝します!
カーソル実行イベントを使用して、executemany
フラグが立てられているかどうかを確認する必要があります。その場合は、fast_executemany
オプションをオンにしてください。例えば:
from sqlalchemy import event
@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
if executemany:
cursor.fast_executemany = True
実行イベントの詳細については、 こちら をご覧ください。
UPDATE:pyodbc
のfast_executemany
のサポートが SQLAlchemy 1.3. に追加されたため、ハックはもはや必要ありません。
EDIT(2019-03-08):Gord Thompsonは、sqlalchemyの更新ログからの良いニュースを以下にコメントしました:SQLAlchemy以降1.3.0、リリース2019-03-04、sqlalchemyはmssql+pyodbc
方言のengine = create_engine(sqlalchemy_url, fast_executemany=True)
をサポートするようになりました。つまり、関数を定義して@event.listens_for(engine, 'before_cursor_execute')
を使用する必要がなくなりました。以下の関数は削除でき、フラグのみをcreate_engineステートメントで設定する必要があります。 。
元の投稿:
これを投稿するためのアカウントを作成しました。既に提供された回答のフォローアップであるため、上記のスレッドの下にコメントしたかったです。上記のソリューションは、Ubuntuベースのインストールから書き込みを行うMicrosft SQLストレージ上のバージョン17 SQLドライバーで機能しました。
物事を大幅に高速化するために使用した完全なコード(100倍以上の高速化)は以下のとおりです。これは、関連する詳細で接続文字列を変更することを条件に、ターンキースニペットです。上記のポスターに、私はすでにかなりの時間を探していたので、解決策に感謝します。
import pandas as pd
import numpy as np
import time
from sqlalchemy import create_engine, event
from urllib.parse import quote_plus
conn = "DRIVER={ODBC Driver 17 for SQL Server};SERVER=IP_ADDRESS;DATABASE=DataLake;UID=USER;PWD=PASS"
quoted = quote_plus(conn)
new_con = 'mssql+pyodbc:///?odbc_connect={}'.format(quoted)
engine = create_engine(new_con)
@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
print("FUNC call")
if executemany:
cursor.fast_executemany = True
table_name = 'fast_executemany_test'
df = pd.DataFrame(np.random.random((10**4, 100)))
s = time.time()
df.to_sql(table_name, engine, if_exists = 'replace', chunksize = None)
print(time.time() - s)
以下のコメントに基づいて、pandas to_sql
実装とクエリの処理方法に関する制限を説明するために、少し時間をかけたいと思いました。 MemoryError
が引き起こされる原因となる可能性がある2つのことがあります。
1)リモートSQLストレージに書き込んでいると仮定します。 to_sql
メソッドを使用して大きなpandas DataFrameを書き込もうとすると、データフレーム全体が値のリストに変換されます。この変換は、元のDataFrameよりも多くのRAMを使用します(その上に、古いDataFrameがまだRAMに残っているため)。このリストは、ODBCコネクタの最後のexecutemany
呼び出しに提供されます。 ODBCコネクタには、このような大きなクエリの処理に問題があると思います。これを解決する方法は、to_sql
メソッドにチャンクサイズ引数を提供することです(10 ** 5は、Azureの2 CPU 7GB ram MSSQLストレージアプリケーションで約600メガビット/秒(!)の書き込み速度を与える最適なサイズのようです) -Azureをお勧めできませんbtw)。したがって、クエリサイズである最初の制限は、chunksize
引数を指定することで回避できます。ただし、これにより、10 ** 7以上のサイズのデータフレーム(少なくとも作業中のVMで〜55 GBのRAMを含む)を作成できなくなり、nr 2が発行されます。
これは、np.split
(10 ** 6サイズのDataFrameチャンク)でDataFrameを分割することで回避できます。これらは反復的に書き出すことができます。 pandas自体のコアにto_sql
メソッドの準備ができているときにプルリクエストを作成しようとするため、毎回事前に分割する必要はありません。とにかく、次のような(ターンキーではない)関数を書くことになりました。
import pandas as pd
import numpy as np
def write_df_to_sql(df, **kwargs):
chunks = np.split(df, df.shape()[0] / 10**6)
for chunk in chunks:
chunk.to_sql(**kwargs)
return True
上記のスニペットのより完全な例は、ここで見ることができます: https://gitlab.com/timelord/timelord/blob/master/timelord/utils/connector.py
これは、パッチを組み込み、SQLとの接続のセットアップに伴う必要なオーバーヘッドの一部を軽減する、私が書いたクラスです。まだいくつかのドキュメントを書く必要があります。また、pandas自体にパッチを提供することを計画していましたが、その方法についてはまだ良い方法を見つけていません。
これがお役に立てば幸いです。
新しいturbodbcライブラリを使用できる人のための追加の高性能オプションとして、この完全な例を投稿したかっただけです。 http://turbodbc.readthedocs.io/en/latest/
pandas .to_sql()の間に流動的な多くのオプションがあり、sqlalchemyを介してfast_executemanyをトリガーし、pyodbcをtuples/lists/etcで直接使用するか、フラットファイルでBULK UPLOADを試みます。
うまくいけば、現在のpandasプロジェクトで機能が進化したり、将来のturbodbc統合のようなものが含まれたりするので、次のことが少し楽しくなるかもしれません。
import pandas as pd
import numpy as np
from turbodbc import connect, make_options
from io import StringIO
test_data = '''id,transaction_dt,units,measures
1,2018-01-01,4,30.5
1,2018-01-03,4,26.3
2,2018-01-01,3,12.7
2,2018-01-03,3,8.8'''
df_test = pd.read_csv(StringIO(test_data), sep=',')
df_test['transaction_dt'] = pd.to_datetime(df_test['transaction_dt'])
options = make_options(parameter_sets_to_buffer=1000)
conn = connect(driver='{SQL Server}', server='server_nm', database='db_nm', turbodbc_options=options)
test_query = '''DROP TABLE IF EXISTS [db_name].[schema].[test]
CREATE TABLE [db_name].[schema].[test]
(
id int NULL,
transaction_dt datetime NULL,
units int NULL,
measures float NULL
)
INSERT INTO [db_name].[schema].[test] (id,transaction_dt,units,measures)
VALUES (?,?,?,?) '''
cursor.executemanycolumns(test_query, [df_test['id'].values, df_test['transaction_dt'].values, df_test['units'].values, df_test['measures'].values]
turbodbcは、多くのユースケースで非常に高速になります(特にnumpy配列の場合)。基になるnumpy配列をデータフレーム列からパラメーターとしてクエリに直接渡すことがいかに簡単かを観察してください。また、これはメモリ消費を過度に増加させる中間オブジェクトの作成を防ぐのに役立つと考えています。これが役立つことを願っています!
同じ問題に遭遇しましたが、PostgreSQLを使用していました。彼らは今リリースしますpandasバージョン0.24.そして、私の問題を解決したmethod
と呼ばれるto_sql
関数に新しいパラメーターがあります。
from sqlalchemy import create_engine
engine = create_engine(your_options)
data_frame.to_sql(table_name, engine, method="multi")
アップロード速度は100倍高速です。また、大量のデータを送信する場合は、chunksize
パラメーターを設定することをお勧めします。
Pandas 0.23.0および0.24.0 複数値の挿入を使用 PyODBCを使用しているため、高速executemanyの助けになりません。単一のINSERT ... VALUES ...
ステートメントがチャンクごとに発行されます。複数値の挿入チャンクは、古い低速のexecutemanyのデフォルトよりも改善されていますが、少なくとも単純なテストでは、複数の値の挿入に必要な手動のchunksize
計算の必要性は言うまでもありませんが、高速のexecutemanyメソッドが依然として有効です。将来構成オプションが提供されない場合は、monkeypatchingによって古い動作を強制できます。
import pandas.io.sql
def insert_statement(self, data, conn):
return self.table.insert(), data
pandas.io.sql.SQLTable.insert_statement = insert_statement
将来はここにあり、少なくともmaster
ブランチでは、 to_sql()
のキーワード引数method=
を使用して挿入メソッドを制御できます。デフォルトはNone
で、executemanyメソッドを強制します。 method='multi'
を渡すと、複数値の挿入が使用されます。 Postgresql COPY
など、DBMS固有のアプローチの実装にも使用できます。
@Pylanderが指摘したように
Turbodbcは、データの取り込みに最適です。
とても興奮して、githubとmediumで「ブログ」を書きました: https://medium.com/@erickfis/etl-process-with-turbodbc-1d19ed71510e を確認してください
作業例とpandas.to_sqlとの比較
簡単に言えば、
turbodbcを使用すると、3秒で10000行(77列)が得られます
pandas.to_sqlを使用すると、198秒で同じ10000行(77列)が得られます...
そして、ここに私が詳細にやっていることがあります
インポート:
import sqlalchemy
import pandas as pd
import numpy as np
import turbodbc
import time
いくつかのデータをロードして処理します-私のsample.pklをあなたのものに置き換えてください:
df = pd.read_pickle('sample.pkl')
df.columns = df.columns.str.strip() # remove white spaces around column names
df = df.applymap(str.strip) # remove white spaces around values
df = df.replace('', np.nan) # map nans, to drop NAs rows and columns later
df = df.dropna(how='all', axis=0) # remove rows containing only NAs
df = df.dropna(how='all', axis=1) # remove columns containing only NAs
df = df.replace(np.nan, 'NA') # turbodbc hates null values...
SqlAlchemyを使用してテーブルを作成する
残念ながら、turbodbcには、テーブルの作成とデータの挿入のために、多くのSQLの手作業による多大なオーバーヘッドが必要です。
幸いなことに、Pythonは純粋な喜びであり、SQLコードを記述するこのプロセスを自動化できます。
最初のステップは、データを受け取るテーブルを作成することです。ただし、テーブルに複数の列がある場合、SQLコードを手動で作成してテーブルを作成すると問題が発生する可能性があります。私の場合、非常に多くの場合、テーブルには240列があります!
SqlAlchemyとpandasはまだ役立ちます:pandasは、多数の行(この例では10000)を書き込むのに適していますが、6行だけで、テーブル?このようにして、テーブルを作成するプロセスを自動化します。
SqlAlchemy接続を作成します。
mydb = 'someDB'
def make_con(db):
"""Connect to a specified db."""
database_connection = sqlalchemy.create_engine(
'mssql+pymssql://{0}:{1}@{2}/{3}'.format(
myuser, mypassword,
myhost, db
)
)
return database_connection
pd_connection = make_con(mydb)
SQL Serverでテーブルを作成する
pandas + sqlAlchemyを使用しますが、前述のturbodbc用のスペースを準備するためだけです。ここでdf.head()に注意してください。データの6行のみを挿入するためにpandas + sqlAlchemyを使用しています。これは非常に高速に実行され、テーブルの作成を自動化するために行われています。
table = 'testing'
df.head().to_sql(table, con=pd_connection, index=False)
テーブルがすでに配置されているので、ここで真剣に話しましょう。
Turbodbc接続:
def turbo_conn(mydb):
"""Connect to a specified db - turbo."""
database_connection = turbodbc.connect(
driver='ODBC Driver 17 for SQL Server',
server=myhost,
database=mydb,
uid=myuser,
pwd=mypassword
)
return database_connection
Turbodbc用のSQLコマンドとデータの準備。創造的であるこのコード作成を自動化しましょう:
def turbo_write(mydb, df, table):
"""Use turbodbc to insert data into sql."""
start = time.time()
# preparing columns
colunas = '('
colunas += ', '.join(df.columns)
colunas += ')'
# preparing value place holders
val_place_holder = ['?' for col in df.columns]
sql_val = '('
sql_val += ', '.join(val_place_holder)
sql_val += ')'
# writing sql query for turbodbc
sql = f"""
INSERT INTO {mydb}.dbo.{table} {colunas}
VALUES {sql_val}
"""
# writing array of values for turbodbc
valores_df = [df[col].values for col in df.columns]
# cleans the previous head insert
with connection.cursor() as cursor:
cursor.execute(f"delete from {mydb}.dbo.{table}")
connection.commit()
# inserts data, for real
with connection.cursor() as cursor:
try:
cursor.executemanycolumns(sql, valores_df)
connection.commit()
except Exception:
connection.rollback()
print('something went wrong')
stop = time.time() - start
return print(f'finished in {stop} seconds')
Turbodbcを使用してデータを書き込む-3秒で10000行(77列)を取得しました。
turbo_write(mydb, df.sample(10000), table)
パンダ方式の比較-198秒で同じ10000行(77列)を取得しました…
table = 'pd_testing'
def pandas_comparisson(df, table):
"""Load data using pandas."""
start = time.time()
df.to_sql(table, con=pd_connection, index=False)
stop = time.time() - start
return print(f'finished in {stop} seconds')
pandas_comparisson(df.sample(10000), table)
環境と条件
Python 3.6.7 :: Anaconda, Inc.
TURBODBC version ‘3.0.0’
sqlAlchemy version ‘1.2.12’
pandas version ‘0.23.4’
Microsoft SQL Server 2014
user with bulk operations privileges
このコードの更新については、 https://erickfis.github.io/loose-code/ を確認してください!
to_sql
を使用してpandas DataFrameをSQL Serverにアップロードすると、turbodbcはfast_executemany
なしのpyodbcよりも確実に高速になります。ただし、fast_executemany
をpyodbcに対して有効にすると、どちらのアプローチでも基本的に同じパフォーマンスが得られます。
テスト環境:
[venv1_pyodbc]
pyodbc 2.0.25
[venv2_turbodbc]
turbodbc 3.0.0
sqlalchemy-turbodbc 0.1.0
[両方に共通]
Windows上のPython 3.6.4 64ビット
SQLAlchemy 1.3.0b1
パンダ0.23.4
numpy 1.15.4
テストコード:
# for pyodbc
engine = create_engine('mssql+pyodbc://sa:whatever@SQL_panorama', fast_executemany=True)
# for turbodbc
# engine = create_engine('mssql+turbodbc://sa:whatever@SQL_panorama')
# test data
num_rows = 10000
num_cols = 100
df = pd.DataFrame(
[[f'row{x:04}col{y:03}' for y in range(num_cols)] for x in range(num_rows)],
columns=[f'col{y:03}' for y in range(num_cols)]
)
t0 = time.time()
df.to_sql("sqlalchemy_test", engine, if_exists='replace', index=None)
print(f"pandas wrote {num_rows} rows in {(time.time() - t0):0.1f} seconds")
テストは、各環境で12回実行され、それぞれの単一のベストタイムとワーストタイムは破棄されました。結果(秒単位):
rank pyodbc turbodbc
---- ------ --------
1 22.8 27.5
2 23.4 28.1
3 24.6 28.2
4 25.2 28.5
5 25.7 29.3
6 26.9 29.9
7 27.0 31.4
8 30.1 32.1
9 33.6 32.5
10 39.8 32.9
---- ------ --------
average 27.9 30.0
@ J.K。の回答に追加したかっただけです。
このアプローチを使用している場合:
@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
if executemany:
cursor.fast_executemany = True
そして、あなたはこのエラーを受け取っています:
"sqlalchemy.exc.DBAPIError:(pyodbc.Error)( 'HY010'、 '[HY010] [Microsoft] [SQL Server Native Client 11.0]関数シーケンスエラー(0)(SQLParamData)')[SQL: 'INSERT INTO .. 。(...)VALUES(?、?) '] [パラメータ:((...、...)、(...、...)](このエラーの背景: http ://sqlalche.me/e/dbapi ) "
次のように文字列値をエンコードします:'yourStringValue'.encode('ascii')
これで問題が解決します。