web-dev-qa-db-ja.com

sqlalchemyを使用してcsvファイルをデータベースにロードする

データベースにcsvファイルを使用したい

26
alex chan

SQLAlchemyのパワーにより、プロジェクトでも使用しています。その力は、管理するのが面倒なSQLステートメントをハードコーディングするのではなく、オブジェクト指向でデータベースと「対話」する方法からもたらされます。言うまでもなく、それは非常に高速です。

質問に素直に答えるために、はい! SQLAlchemyを使用してCSVからデータベースにデータを保存するのは簡単です。完全な動作例を次に示します(SQLAlchemy 1.0.6および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

(注:これは必ずしもこれを行うための「最良の」方法ではありませんが、この形式は初心者にとって非常に読みやすいと思います。また、非常に高速です:挿入された251レコードに対して0.091秒!)

行ごとに行けば、使用するのがどれほど簡単かがわかると思います。 SQLステートメントの欠如に注意してください-やったー!また、numpyを使用してCSVの内容を2行で読み込むこともできましたが、必要な場合はそれを使用せずに実行できます。

従来の方法と比較したい場合は、参照用の完全な例を以下に示します。

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

(注:「古い」方法であっても、これが最良の方法ではありませんが、非常に読みやすく、SQLAlchemyの方法と「古い」方法の「1対1」の変換です。)

SQLステートメントに注意してください。1つはテーブルを作成し、もう1つはレコードを挿入します。また、長いSQL文字列を維持するのは、単純なクラス属性を追加するよりも少し面倒です。これまでSQLAlchemyが好きですか?

もちろん、外部キーの問い合わせについては。 SQLAlchemyにはこれを行う機能もあります。クラス属性が外部キー割り当てでどのように見えるかの例を次に示します(ForeignKeyクラスも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'))

「fid」列をPrice_Historyのid列への外部キーとしてポイントします。

お役に立てば幸いです!

38
Manuel J. Diaz

CSVが非常に大きい場合、INSERTの使用は非常に効果的ではありません。ベースごとに異なるバルクロードメカニズムを使用する必要があります。例えば。 PostgreSQLでは、「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

私はまったく同じ問題を抱えていましたが、逆説的にパンダで2ステップのプロセスを使用する方が簡単であることがわかりました:

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')

私のアプローチは this one に似ていますが、どういうわけかGoogleが代わりにこのスレッドに送ってくれたので、共有すると思いました。

3

Sqlalchemyを使用してデータベースに比較的小さなCSVファイルをインポートするには、sqlalchemyチュートリアルの 「複数のステートメントの実行」セクション で詳細に説明されているように、engine.execute(my_table.insert(), list_of_row_dicts)を使用できます。

これは、 executemany DBAPI呼び出し になるため、「executemany」スタイルの呼び出しと呼ばれることもあります。 =。 DBドライバーは、単一の複数値INSERT .. VALUES (..), (..), (..)ステートメントを実行する場合があります。これにより、DBへのラウンドトリップが少なくなり、実行が高速になります。

sqlalchemyのFAQ によると、これは、Postgresの COPY FROM などのDB固有のバルクロードメソッドを使用せずに取得できる最速です。 LOAD DATA LOCAL INFILE MySQLなど。特に、プレーンORMを使用するよりも高速です(こちらの@Manuel J. Diazの回答のように)、bulk_save_objects、または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