Accélérer les pandas.DataFrame.to_sql avec fast_executemany de pyODBC
Je voudrais adresser un grand pandas.DataFrame
à un serveur distant fonctionnant sous MS SQL. La façon dont je le fais maintenant, c'est par la conversion d'un data_frame
objet d'une liste de tuples et ensuite l'envoyer balader avec pyODBC de executemany()
fonction. Il va quelque chose comme ceci:
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()
J'ai alors commencé à me demander si les choses peuvent être accéléré (ou au moins plus lisible) à l'aide de data_frame.to_sql()
méthode. Je suis venu avec la solution suivante:
import sqlalchemy as sa
engine = sa.create_engine("mssql+pyodbc:///?odbc_connect=%s" % cnxn_str)
data_frame.to_sql(table_name, engine, index=False)
Maintenant le code est plus lisible, mais le téléchargement est au moins 150 fois plus lent...
Est-il un moyen de retourner la fast_executemany
lors de l'utilisation de SQLAlchemy?
Je suis en utilisant des pandas-0.20.3, pyODBC-4.0.21 et sqlalchemy-1.1.13.
Vous devez vous connecter pour publier un commentaire.
MODIFIER (08/03/2019): Gord Thompson a commenté ci-dessous, avec de bonnes nouvelles de la mise à jour de journaux de sqlalchemy: Depuis SQLAlchemy 1.3.0, publié 2019-03-04, sqlalchemy prend désormais en charge
engine = create_engine(sqlalchemy_url, fast_executemany=True)
pour lamssql+pyodbc
dialecte. I. e., il n'est plus nécessaire de définir une fonction et l'utilisation@event.listens_for(engine, 'before_cursor_execute')
Signification ci-dessous la fonction peut être supprimée et seule l'indicateur doit être défini dans le create_engine de l'instruction et de toujours conserver le speed-up.Original Post:
Viens de faire un compte pour poster ceci. Je voulais commentaire sous le fil au-dessus car il s'agit d'un suivi sur le déjà fourni la réponse. La solution ci-dessus a fonctionné pour moi avec la Version 17 SQL pilote Microsoft SQL stockage de l'écriture à partir d'un Ubuntu installation.
Le code complet que j'ai utilisé pour accélérer les choses de manière significative (parler >100x speed-up) est ci-dessous. Ceci est un extrait de à la condition que vous modifiez la chaîne de connexion avec vos détails pertinents. L'affiche ci-dessus, je vous remercie très beaucoup pour la solution que je cherchais tout à fait un certain temps déjà.
Sur la base des commentaires ci-dessous, je voulais prendre un peu de temps pour expliquer certaines limitations sur les pandas
to_sql
mise en œuvre et la façon dont la requête est traitée. Il y a 2 choses qui peuvent causer laMemoryError
soulevé autant que je sache:1) en Supposant que vous écrivez à une instance distante de SQL de stockage. Lorsque vous essayez d'écrire un grand pandas DataFrame avec le
to_sql
méthode il convertit l'intégralité du dataframe dans une liste de valeurs. Cette transformation prend beaucoup plus de RAM que l'original DataFrame n' (sur le dessus de cela, comme l'ancien DataFrame reste encore présent dans la mémoire RAM). Cette liste est fournie à la dernièreexecutemany
appel à votre connecteur ODBC. Je pense que le connecteur ODBC a quelques problèmes pour le traitement de ces requêtes importantes. Une façon de résoudre ce problème est de fournir leto_sql
méthode un chunksize argument (10**5 semble être autour optimale donnant sur 600 mbit/s (!) des vitesses d'écriture sur un 2 UC 7 GO de ram MSSQL application de Stockage d'Azur ne peux pas le recommander Azure btw). Donc, la première limitation, soit la taille de requête peut être contournée par la fourniture d'unchunksize
argument. Cependant, ce ne sera pas vous permettre d'écrire un dataframe la taille de 10**7 ou plus, (au moins pas sur la VM, je travaille avec ce qui a ~55GB RAM), étant à la question n ° 2.Cela peut être contourné par la rupture de la DataFrame avec
np.split
(10**6 taille DataFrame morceaux) ils peuvent être écrits à l'écart de manière itérative. Je vais essayer de faire une pull request, quand j'ai une solution toute prête pour lato_sql
méthode dans la base de pandas lui-même de sorte que vous n'aurez pas à faire cette pré-rupture de tous les temps. De toute façon j'ai fini par écrire une fonction similaire (pas de clé) pour les opérations suivantes:Un exemple plus complet de l'extrait ci-dessus peuvent être consultées ici: https://gitlab.com/timelord/timelord/blob/master/timelord/utils/connector.py
C'est une classe que j'ai écrit qui intègre le patch et facilite certaines des frais généraux qui vient avec l'établissement de connexions avec SQL. Encore écrire de la documentation. J'ai également la planification sur le patch pour les pandas lui-même, mais n'ai pas trouvé une belle façon sur la façon de le faire.
J'espère que cette aide.
engine = sa.create_engine('mssql+pyodbc://SERVER/DATABASE?driver=SQL+Server+Native+Client+11.0')
. pensez-vous que le Native Client a quelque chose à voir avec le débrancher?to_sql
est très gourmande en mémoire en raison de la transformation en unelist
type. (un 700MO de RAM DataFrame semblait utiliser à peakload sur 8GO de RAM avec lato_sql
méthode à cause de cela. )df.to_sql
fonctionne bien lorsqueif_exists
est définiereplace
. Lorsque j'essaie d'utiliserappend
jupyter ordinateur portable se bloque. Toutes les solutions?'replace'
mais pas avec'append'
, Comment est-ce n'est pas lié?to_sql
. Vous vous demandez maintenant sur une erreur d'un argument dans la même méthode, ce qui n'est pas liée avec la question d'origine: autant que je sache. Juste essayer de respecter les normes de SORTE que j'ai l'habitude de voir. concernant les informations que vous avez fournies maintenant peut-être que l'erreur est déclenchée, car déjà présent tableau est de taille différente et donc ne peut pas être ajouté à la fin (erreur type)? Aussi le dernier extrait de code que j'ai fourni a titre d'illustration, vous avez probablement besoin de le modifier un peu.con._init_engine(SET_FAST_EXECUTEMANY_SWITCH=False)
Après avoir initialisé la classe. Bonne chance.mssql+pyodbc
. Cela permettra de tirer parti de la fast_insert SUR le switch qui est définie sur True dans la classe en tant que par défaut. La classe est juste une fantaisie wrapper de deux extraits de code dans mon exemple.sqlalchemy.exc.DBAPIError: (pyodbc.Error) ('HY010', '[HY010] [Microsoft][ODBC Driver 11 for SQL Server]Function sequence error (0) (SQLParamData)') [SQL:......
où l' ..... est l'instruction insert générés par les pandasto_sql()
. Je suis à l'aide de Python 3.7.0, Windows 7 et SQL Server 12.0.5538.0. L'accès via un nom de connexion ODBC. (Toutes mes EXCUSES: j'ai posté ce commentaire sur une autre réponse que de bien, juste pensé que ce fil semble aussi active.)engine = create_engine(sqlalchemy_url, fast_executemany=True)
pour lamssql+pyodbc
dialecte. I. e., il n'est plus nécessaire de définir une fonction et l'utilisation@event.listens_for(engine, 'before_cursor_execute')
. Merci.Après contact avec les développeurs de SQLAlchemy, une façon de résoudre ce problème est apparu. Merci à eux pour leur excellent travail!
On doit utiliser un curseur de l'exécution de l'événement et de vérifier si le
executemany
drapeau a été soulevée. Si c'est effectivement le cas, mettez lefast_executemany
option. Par exemple:Plus d'informations sur les événements de l'exécution peut être trouvé ici.
Mise à JOUR: Soutien pour
fast_executemany
depyodbc
a été ajouté dans SQLAlchemy 1.3.0, de sorte que ce hack n'est pas plus nécessaire.to_sql()
après cette fonction?to_sql
directement après la fonction, mais il n'a pas de vitesse que quoi que ce soitto_sql()
et observer la performance qui est proche à l'aide de pyODBC seul, mais avec tous les avantages du moteur et de l'ORM de SQLA. Quelle est la version de pyODBC utilisez-vous? Les Versions plus anciennes que 4.0.19 n'ont pas cette fonctionnalité.sqlalchemy.exc.DBAPIError: (pyodbc.Error) ('HY010', '[HY010] [Microsoft][ODBC Driver 11 for SQL Server]Function sequence error (0) (SQLParamData)') [SQL:......
où l' ..... est l'instruction insert générés par les pandasto_sql()
. Je suis à l'aide de Python 3.7.0, Windows 7 et SQL Server 12.0.5538.0. L'accès via un nom de connexion ODBC. Toute aide appréciée merci!engine = create_engine(sqlalchemy_url, fast_executemany=True)
pour lamssql+pyodbc
dialecte. I. e., il n'est plus nécessaire de définir une fonction et l'utilisation@event.listens_for(engine, 'before_cursor_execute')
. Merci.Je voulais juste poster cet exemple comme un autre, hautes performances et pour ceux qui peuvent utiliser la nouvelle turbodbc bibliothèque: http://turbodbc.readthedocs.io/en/latest/
Il n'y a clairement beaucoup d'options dans le flux entre les pandas .to_sql(), le déclenchement de fast_executemany par le biais de sqlalchemy, à l'aide de pyodbc directement avec les tuples/lists/etc., ou même essayer de TÉLÉCHARGEMENT en VRAC avec des fichiers plats.
Espérons-le, les éléments suivants pourraient rendre la vie un peu plus agréable que la fonctionnalité évolue dans le courant de pandas projet ou comprend quelque chose comme turbodbc intégration dans l'avenir.
turbodbc devrait être TRÈS rapide dans de nombreux cas d'utilisation (en particulier avec des tableaux numpy). Veuillez observer comment simple c'est de passer les sous-jacents des tableaux numpy du dataframe colonnes en tant que paramètres à la requête directement. Je crois aussi que cela permet d'éviter la création d'objets intermédiaires que les pointes de consommation mémoire excessive. Espérons que cela est utile!
J'ai rencontré le même problème, mais l'utilisation de PostgreSQL. Ils ont maintenant lâchez pandas version 0.24.0 et il y a un nouveau paramètre dans le
to_sql
fonction appeléemethod
qui a résolu mon problème.Vitesse de téléchargement est 100x plus rapide pour moi.
Je recommande également le réglage de la
chunksize
paramètre si vous vous apprêtez à envoyer un grand nombre de données.Il semble que les Pandas 0.23.0 et 0.24.0 l'utilisation de plusieurs valeurs inserts avec PyODBC, ce qui empêche rapide executemany de l'aide – une seule
INSERT ... VALUES ...
déclaration est émise par morceau. Multi valeurs insérer des segments sont une amélioration par rapport à l'ancien lent executemany par défaut, mais au moins dans les tests simples de la rapide executemany méthode prévaut encore, pour ne pas mentionner aucun besoin de manuelchunksize
calculs, comme cela est requis, avec de multiples valeurs des inserts. Forçant l'ancien comportement peut être fait par monkeypatching, si aucune option de configuration est fourni dans l'avenir:L'avenir est ici, et au moins dans la
master
direction générale de la méthode d'insertion peut être contrôlé à l'aide de l'argument mot-clémethod=
deto_sql()
. La valeur par défaut estNone
, ce qui oblige les executemany méthode. En passantmethod='multi'
résultats dans l'utilisation de la multi valeurs à insérer. Il peut même être utilisé pour mettre en œuvre des SGBD approches spécifiques, tels que PostgresqlCOPY
.mssql+pyodbc
SQLAlchemy moteur. les pandas 0.23.4, en effet, laisser fast_executemany faire sa chose.master
branche, mais il est contrôlable maintenant: github.com/pandas-dev/pandas/blob/master/pandas/io/sql.py#L1157. Semble comme passerto_sql(..., method=None)
doit forcer l'executemany approche.None
est la valeur par défaut.SQL Server performance de l'INSERT: pyodbc vs turbodbc
Lors de l'utilisation de
to_sql
de télécharger une pandas DataFrame de SQL Server, turbodbc sera certainement plus rapide que pyodbc sansfast_executemany
. Cependant, avecfast_executemany
activé pour pyodbc, les deux approches donnent essentiellement la même performance.Des environnements de Test:
[venv1_pyodbc]
pyodbc 2.0.25
[venv2_turbodbc]
turbodbc 3.0.0
sqlalchemy-turbodbc 0.1.0
[commun aux deux]
Python 3.6.4 64 bits sur Windows
SQLAlchemy 1.3.0b1
les pandas 0.23.4
numpy 1.15.4
Code de Test:
Les Tests ont été exécutés dans les douze (12) fois pour chaque environnement, en écartant le meilleur et le pire des moments pour chaque. Résultats (en secondes):
Comme l'a souligné @Pylander
Turbodbc est le meilleur choix pour l'ingestion de données, et de loin!
Je suis tellement excité à ce sujet que j'ai écrit un blog sur mon github et support:
veuillez vérifier https://medium.com/@erickfis/etl-process-with-turbodbc-1d19ed71510e
pour un exemple de travail et de comparaison avec les pandas.to_sql
Longue histoire courte,
avec turbodbc
J'ai 10000 lignes (77 colonnes) en 3 secondes
avec les pandas.to_sql
J'ai eu la même 10000 lignes (77 colonnes) de 198 secondes...
Et voici ce que je fais en détail
Les importations:
Charge et traiter certaines données Substitut de mon échantillon.pkl pour le vôtre:
Créer la table à l'aide de sqlAlchemy
Malheureusement, turbodbc nécessite beaucoup de frais avec beaucoup de sql travail manuel, pour la création des tables et pour l'insertion de données.
Heureusement, Python est un pur bonheur et nous pouvons automatiser ce processus d'écriture de code sql.
La première étape est la création de la table qui recevra nos données. Toutefois, la création de la table manuellement l'écriture de code sql peut être problématique si votre table a plus que quelques colonnes. Dans mon cas, très souvent, les tables ont 240 colonnes!
C'est là que sqlAlchemy et les pandas peuvent encore nous aider: les pandas est mauvais pour la rédaction d'un grand nombre de lignes (10000 dans cet exemple), mais qu'en seulement 6 lignes, la tête de la table? De cette façon, nous automatiser le processus de création des tables.
Créer sqlAlchemy connexion:
Créer une table dans SQL Server
À l'aide de pandas + sqlAlchemy, mais juste pour la préparation de la chambre pour turbodbc, comme mentionné précédemment. Veuillez noter que les df.la tête() ici: nous sommes à l'aide de pandas + sqlAlchemy pour l'insertion de seulement 6 lignes de nos données. Cela permettra de courir assez vite et est fait pour automatiser la création de la table.
Maintenant que la table est déjà en place, nous allons prendre au sérieux ici.
Turbodbc connexion:
La préparation de sql comands et de données pour turbodbc. Nous allons automatiser la création de code, être créatif:
De l'écriture de données à l'aide de turbodbc - j'ai 10000 lignes (77 colonnes) dans les 3 secondes:
Pandas méthode de la comparaison, j'ai eu la même 10000 lignes (77 colonnes) de 198 secondes...
De l'environnement et des conditions de
Veuillez vérifier https://erickfis.github.io/loose-code/ pour les mises à jour dans ce code!
Voulais juste ajouter à la @J. K. réponse.
Si vous utilisez cette approche:
Et vous obtenez cette erreur:
Encoder votre chaîne de valeurs comme ceci:
'yourStringValue'.encode('ascii')
Cela permettra de résoudre votre problème.