web-dev-qa-db-fra.com

utilisation de sqlalchemy pour charger un fichier csv dans une base de données

J'aimerais utiliser des fichiers csv dans une base de données

26
alex chan

En raison de la puissance de SQLAlchemy, je l'utilise également sur un projet. Sa puissance vient de la façon orientée objet de "parler" à une base de données au lieu de coder en dur des instructions SQL qui peuvent être difficiles à gérer. Sans oublier, c'est aussi beaucoup plus rapide.

Pour répondre franchement à votre question, oui! Le stockage de données d'un CSV dans une base de données à l'aide de SQLAlchemy est un jeu d'enfant. Voici un exemple de travail complet (j'ai utilisé SQLAlchemy 1.0.6 et Python 2.7.6):

from numpy import genfromtxt
from time import time
from datetime import datetime
from sqlalchemy import Column, Integer, Float, Date
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

def Load_Data(file_name):
    data = genfromtxt(file_name, delimiter=',', skip_header=1, converters={0: lambda s: str(s)})
    return data.tolist()

Base = declarative_base()

class Price_History(Base):
    #Tell SQLAlchemy what the table name is and if there's any table-specific arguments it should know about
    __tablename__ = 'Price_History'
    __table_args__ = {'sqlite_autoincrement': True}
    #tell SQLAlchemy the name of column and its attributes:
    id = Column(Integer, primary_key=True, nullable=False) 
    date = Column(Date)
    opn = Column(Float)
    hi = Column(Float)
    lo = Column(Float)
    close = Column(Float)
    vol = Column(Float)

if __name__ == "__main__":
    t = time()

    #Create the database
    engine = create_engine('sqlite:///csv_test.db')
    Base.metadata.create_all(engine)

    #Create the session
    session = sessionmaker()
    session.configure(bind=engine)
    s = session()

    try:
        file_name = "t.csv" #sample CSV file used:  http://www.google.com/finance/historical?q=NYSE%3AT&ei=W4ikVam8LYWjmAGjhoHACw&output=csv
        data = Load_Data(file_name) 

        for i in data:
            record = Price_History(**{
                'date' : datetime.strptime(i[0], '%d-%b-%y').date(),
                'opn' : i[1],
                'hi' : i[2],
                'lo' : i[3],
                'close' : i[4],
                'vol' : i[5]
            })
            s.add(record) #Add all the records

        s.commit() #Attempt to commit all the records
    except:
        s.rollback() #Rollback the changes on error
    finally:
        s.close() #Close the connection
    print "Time elapsed: " + str(time() - t) + " s." #0.091s

(Remarque: ce n'est pas nécessairement la "meilleure" façon de le faire, mais je pense que ce format est très lisible pour un débutant; il est également très rapide: 0,091 s pour 251 enregistrements insérés!)

Je pense que si vous le parcourez ligne par ligne, vous verrez à quel point il est facile d’utiliser. Remarquez le manque d'instructions SQL - hourra! J'ai également pris la liberté d'utiliser numpy pour charger le contenu CSV sur deux lignes, mais cela peut se faire sans si vous le souhaitez.

Si vous vouliez comparer avec la façon traditionnelle de le faire, voici un exemple complet pour référence:

import sqlite3
import time
from numpy import genfromtxt

def dict_factory(cursor, row):
    d = {}
    for idx, col in enumerate(cursor.description):
        d[col[0]] = row[idx]
    return d


def Create_DB(db):      
    #Create DB and format it as needed
    with sqlite3.connect(db) as conn:
        conn.row_factory = dict_factory
        conn.text_factory = str

        cursor = conn.cursor()

        cursor.execute("CREATE TABLE [Price_History] ([id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, [date] DATE, [opn] FLOAT, [hi] FLOAT, [lo] FLOAT, [close] FLOAT, [vol] INTEGER);")


def Add_Record(db, data):
    #Insert record into table
    with sqlite3.connect(db) as conn:
        conn.row_factory = dict_factory
        conn.text_factory = str

        cursor = conn.cursor()

        cursor.execute("INSERT INTO Price_History({cols}) VALUES({vals});".format(cols = str(data.keys()).strip('[]'), 
                    vals=str([data[i] for i in data]).strip('[]')
                    ))


def Load_Data(file_name):
    data = genfromtxt(file_name, delimiter=',', skiprows=1, converters={0: lambda s: str(s)})
    return data.tolist()


if __name__ == "__main__":
    t = time.time() 

    db = 'csv_test_sql.db' #Database filename 
    file_name = "t.csv" #sample CSV file used:  http://www.google.com/finance/historical?q=NYSE%3AT&ei=W4ikVam8LYWjmAGjhoHACw&output=csv

    data = Load_Data(file_name) #Get data from CSV

    Create_DB(db) #Create DB

    #For every record, format and insert to table
    for i in data:
        record = {
                'date' : i[0],
                'opn' : i[1],
                'hi' : i[2],
                'lo' : i[3],
                'close' : i[4],
                'vol' : i[5]
            }
        Add_Record(db, record)

    print "Time elapsed: " + str(time.time() - t) + " s." #3.604s

(Remarque: même à "l'ancienne" manière, ce n'est en aucun cas la meilleure façon de le faire, mais c'est très lisible et une traduction "1 à 1" de la manière SQLAlchemy par rapport à l '"ancienne" manière.)

Remarquez les instructions SQL: l'une pour créer la table, l'autre pour insérer des enregistrements. Notez également qu'il est un peu plus lourd de conserver de longues chaînes SQL par rapport à un simple ajout d'attribut de classe. Vous aimez SQLAlchemy jusqu'à présent?

Quant à votre demande de clé étrangère, bien sûr. SQLAlchemy a également le pouvoir de le faire. Voici un exemple de l'apparence d'un attribut de classe avec une affectation de clé étrangère (en supposant que la classe ForeignKey a également été importée du module sqlalchemy):

class Asset_Analysis(Base):
    #Tell SQLAlchemy what the table name is and if there's any table-specific arguments it should know about
    __tablename__ = 'Asset_Analysis'
    __table_args__ = {'sqlite_autoincrement': True}
    #tell SQLAlchemy the name of column and its attributes:
    id = Column(Integer, primary_key=True, nullable=False) 
    fid = Column(Integer, ForeignKey('Price_History.id'))

qui pointe la colonne "fid" comme clé étrangère vers la colonne id de Price_History.

J'espère que ça t'as aidé!

38
Manuel J. Diaz

Si votre CSV est assez volumineux, l'utilisation de INSERTS est très inefficace. Vous devez utiliser un mécanisme de chargement en masse, qui diffère d'une base à l'autre. Par exemple. dans PostgreSQL, vous devez utiliser la méthode "COPY FROM":

with open(csv_file_path, 'r') as f:    
    conn = create_engine('postgresql+psycopg2://...').raw_connection()
    cursor = conn.cursor()
    cmd = 'COPY tbl_name(col1, col2, col3) FROM STDIN WITH (FORMAT CSV, HEADER FALSE)'
    cursor.copy_expert(cmd, f)
    conn.commit()
34
ARA1307

J'ai eu exactement le même problème et j'ai trouvé paradoxalement plus facile d'utiliser un processus en 2 étapes avec des pandas:

import pandas as pd
with open(csv_file_path, 'r') as file:
    data_df = pd.read_csv(file)
data_df.to_sql('tbl_name', con=engine, index=True, index_label='id', if_exists='replace')

Notez que mon approche est similaire à celle-ci , mais Google m'a envoyé à ce sujet à la place, alors j'ai pensé partager.

3

Pour importer un fichier CSV relativement petit dans la base de données à l'aide de sqlalchemy, vous pouvez utiliser engine.execute(my_table.insert(), list_of_row_dicts), comme décrit en détail dans la section "Exécution de plusieurs instructions" du didacticiel sqlalchemy .

Ceci est parfois appelé style d'invocation "executemany" , car il en résulte un appel executemany DBAPI =. Le pilote de base de données peut exécuter une seule instruction à plusieurs valeurs INSERT .. VALUES (..), (..), (..), ce qui se traduit par moins d'allers-retours vers la base de données et une exécution plus rapide:

Selon le sqlalchemy's FAQ , c'est le plus rapide que vous pouvez obtenir sans utiliser de méthodes de chargement en masse spécifiques à la base de données, telles que COPY FROM dans Postgres, LOAD DATA LOCAL INFILE dans MySQL, etc. En particulier, c'est plus rapide que d'utiliser ORM ordinaire (comme dans la réponse de @Manuel J. Diaz ici), bulk_save_objects, ou bulk_insert_mappings.

import csv
from sqlalchemy import create_engine, Table, Column, Integer, MetaData

engine = create_engine('sqlite:///sqlalchemy.db', echo=True)

metadata = MetaData()
# Define the table with sqlalchemy:
my_table = Table('MyTable', metadata,
    Column('foo', Integer),
    Column('bar', Integer),
)
metadata.create_all(engine)
insert_query = my_table.insert()

# Or read the definition from the DB:
# metadata.reflect(engine, only=['MyTable'])
# my_table = Table('MyTable', metadata, autoload=True, autoload_with=engine)
# insert_query = my_table.insert()

# Or hardcode the SQL query:
# insert_query = "INSERT INTO MyTable (foo, bar) VALUES (:foo, :bar)"

with open('test.csv', 'r', encoding="utf-8") as csvfile:
    csv_reader = csv.reader(csvfile, delimiter=',')
    engine.execute(
        insert_query,
        [{"foo": row[0], "bar": row[1]} 
            for row in csv_reader]
    )
0
Nickolay