การนำเข้าไฟล์ CSV ไปยังตารางฐานข้อมูล sqlite3 โดยใช้ Python


106

ฉันมีไฟล์ CSV และต้องการนำเข้าไฟล์จำนวนมากไปยังฐานข้อมูล sqlite3 โดยใช้ Python คำสั่งคือ ".import ..... " แต่ดูเหมือนว่าจะไม่สามารถทำงานได้เช่นนี้ ใครช่วยยกตัวอย่างวิธีทำใน sqlite3 ให้หน่อยได้ไหม ฉันใช้ windows ในกรณีนี้ ขอบคุณ


3
โปรดระบุคำสั่งจริงที่ใช้งานไม่ได้และข้อความแสดงข้อผิดพลาดจริง "import .... " อาจเป็นอะไรก็ได้ "ไม่สามารถทำงานได้" นั้นคลุมเครือเกินกว่าที่เราจะคาดเดาได้ หากไม่มีรายละเอียดเราไม่สามารถช่วยได้
ล็อตต์

2
คำสั่งจริงตามที่ฉันพูดคือ ".import" และมันบอกว่า syntax error new ".import"
Hossein

10
กรุณาโพสต์คำสั่งจริงในคำถาม กรุณาโพสต์ข้อความแสดงข้อผิดพลาดจริงในคำถาม โปรดอย่าเพิ่มความคิดเห็นที่ทำซ้ำ ๆ โปรดอัปเดตคำถามด้วยสำเนาจริงและวางสิ่งที่คุณกำลังทำอยู่
S.Lott

คำตอบ:


133
import csv, sqlite3

con = sqlite3.connect(":memory:") # change to 'sqlite:///your_filename.db'
cur = con.cursor()
cur.execute("CREATE TABLE t (col1, col2);") # use your column names here

with open('data.csv','r') as fin: # `with` statement available in 2.5+
    # csv.DictReader uses first line in file for column headings by default
    dr = csv.DictReader(fin) # comma is default delimiter
    to_db = [(i['col1'], i['col2']) for i in dr]

cur.executemany("INSERT INTO t (col1, col2) VALUES (?, ?);", to_db)
con.commit()
con.close()

4
ในกรณีที่คุณมีปัญหาเดียวกันกับฉัน: ตรวจสอบให้แน่ใจว่าได้เปลี่ยน col1 และ col2 เป็นส่วนหัวคอลัมน์ในไฟล์ csv และปิดการเชื่อมต่อกับฐานข้อมูลโดยเรียก con.close () ในตอนท้าย
Jonas

1
ขอบคุณ @Jonas อัปเดตโพสต์
mechanical_meat

ฉันได้รับnot all arguments converted during string formattingเรื่อย ๆ เมื่อพยายามใช้วิธีนี้
Whitecat

ฉันลองวิธีนี้แล้ว แต่มันไม่ได้ผลสำหรับฉัน คุณช่วยตรวจสอบชุดข้อมูลของฉันที่นี่ได้ไหม (ปกติมากยกเว้นบางคอลัมน์มีค่าว่างเปล่า) และลองนำเข้าด้วยรหัสของคุณ stackoverflow.com/questions/46042623/…
user177196

2
รหัสนี้ไม่ได้รับการปรับให้เหมาะกับไฟล์ csv ที่มีขนาดใหญ่มาก (ลำดับของ GB)
Nisba

92

การสร้างการเชื่อมต่อ sqlite ไปยังไฟล์บนดิสก์ถูกปล่อยให้เป็นแบบฝึกหัดสำหรับผู้อ่าน ...

df = pandas.read_csv(csvfile)
df.to_sql(table_name, conn, if_exists='append', index=False)

ขอบคุณ. ฉันมีปัญหากับแพนด้า csv ของฉันถูกคั่นด้วย ';' และมี ',' ในรายการ แพนด้าให้ข้อผิดพลาดใน read_csv การตั้งค่าใด ๆ ในการอ่านรายการที่มีเครื่องหมายจุลภาคโดยไม่ต้องแทนที่ชั่วคราว
Alexei Martianov

3
ใช้ sep = ';' เอกสารของแพนด้าระบุวิธีจัดการกับสิ่งนี้อย่างชัดเจน
Tennessee Leeuwenburg

3
มีวิธีใช้แพนด้า แต่ไม่ใช้ RAM หรือไม่ฉันมี. csv (7gb) ขนาดใหญ่ฉันไม่สามารถนำเข้าเป็น dataframe แล้วต่อท้ายเข้ากับ DB
Pablo

1
ใช่มีวิธีการในแพนด้าซึ่งจะอ่านเป็นชิ้น ๆ แทนที่จะอ่านทั้งหมดในครั้งเดียว ฉันกลัวว่าจะจำไม่ได้ว่าจากส่วนบนของหัว ฉันคิดว่าคุณเพิ่ม chunksize = <number_of_rows> แล้วคุณจะได้รับตัววนซ้ำกลับมาซึ่งคุณสามารถใช้เพื่อต่อท้ายฐานข้อมูลแบบทีละชิ้น โปรดแจ้งให้เราทราบหากคุณมีปัญหาในการค้นหาและฉันสามารถขุดสูตรได้
Tennessee Leeuwenburg

1
สวยมาก @TennesseeLeeuwenburg ฉันไม่มีความต้องการdfดังนั้นฉันจึงย่อตัวอย่างของคุณให้สั้นลงเป็น:pandas.read_csv(csvfile).to_sql(table_name, conn, if_exists='append', index=False)
keithpjolley

13

2 เซ็นต์ของฉัน (ทั่วไปมากขึ้น):

import csv, sqlite3
import logging

def _get_col_datatypes(fin):
    dr = csv.DictReader(fin) # comma is default delimiter
    fieldTypes = {}
    for entry in dr:
        feildslLeft = [f for f in dr.fieldnames if f not in fieldTypes.keys()]
        if not feildslLeft: break # We're done
        for field in feildslLeft:
            data = entry[field]

            # Need data to decide
            if len(data) == 0:
                continue

            if data.isdigit():
                fieldTypes[field] = "INTEGER"
            else:
                fieldTypes[field] = "TEXT"
        # TODO: Currently there's no support for DATE in sqllite

    if len(feildslLeft) > 0:
        raise Exception("Failed to find all the columns data types - Maybe some are empty?")

    return fieldTypes


def escapingGenerator(f):
    for line in f:
        yield line.encode("ascii", "xmlcharrefreplace").decode("ascii")


def csvToDb(csvFile, outputToFile = False):
    # TODO: implement output to file

    with open(csvFile,mode='r', encoding="ISO-8859-1") as fin:
        dt = _get_col_datatypes(fin)

        fin.seek(0)

        reader = csv.DictReader(fin)

        # Keep the order of the columns name just as in the CSV
        fields = reader.fieldnames
        cols = []

        # Set field and type
        for f in fields:
            cols.append("%s %s" % (f, dt[f]))

        # Generate create table statement:
        stmt = "CREATE TABLE ads (%s)" % ",".join(cols)

        con = sqlite3.connect(":memory:")
        cur = con.cursor()
        cur.execute(stmt)

        fin.seek(0)


        reader = csv.reader(escapingGenerator(fin))

        # Generate insert statement:
        stmt = "INSERT INTO ads VALUES(%s);" % ','.join('?' * len(cols))

        cur.executemany(stmt, reader)
        con.commit()

    return con

1
ถ้า len (feildslLeft)> 0: เป็นจริงเสมอดังนั้นจึงเพิ่มข้อยกเว้น โปรดตรวจสอบและแก้ไข
amu61

มีวิธีใดบ้างที่จะทำสิ่งนี้โดยไม่ต้อง fseek () เพื่อให้สามารถใช้กับสตรีมได้?
mwag

1
@mwag คุณสามารถข้ามการตรวจสอบประเภทคอลัมน์และนำเข้าคอลัมน์ทั้งหมดเป็นข้อความแทนได้
user5359531

12

.importคำสั่งเป็นคุณลักษณะของเครื่องมือบรรทัดคำสั่ง sqlite3 หากต้องการทำใน Python คุณควรโหลดข้อมูลโดยใช้สิ่งอำนวยความสะดวกใด ๆ ที่ Python มีเช่นโมดูล csvและแทรกข้อมูลตามปกติ

ด้วยวิธีนี้คุณยังสามารถควบคุมประเภทที่แทรกแทนที่จะอาศัยพฤติกรรมที่ดูเหมือนไม่มีเอกสารของ sqlite3


1
ไม่จำเป็นต้องเตรียมเม็ดมีด แหล่งที่มาของคำสั่ง SQL และผลลัพธ์ที่คอมไพล์จะถูกเก็บไว้ในแคช
John Machin

@ John Machin: มีลิงค์ว่า SQLite ทำสิ่งนี้ได้อย่างไร?
Marcelo Cantos

@Marcelo: หากคุณสนใจว่ามันทำอย่างไร (ทำไม?) ให้ดูในแหล่งที่มาของ sqlite หรือถามในรายชื่ออีเมลของ sqlite
John Machin

@ John Machin: ฉันสนใจเพราะในเอกสาร SQLite ทั้งหมดที่ฉันเจอไม่มีคำเดียวเกี่ยวกับการแคชอัตโนมัติของคำสั่งที่ไม่ได้เตรียมไว้ ฉันไม่คิดว่ามันสมเหตุสมผลที่จะต้องอ่านซอร์สโค้ดหรือตรวจสอบรายชื่อส่งเมลเพื่อค้นหาบางสิ่งที่เป็นพื้นฐานว่าฉันควรเตรียมคำสั่ง SQL ของฉันหรือไม่ แหล่งข้อมูลของคุณเกี่ยวกับเรื่องนี้คืออะไร?
Marcelo Cantos

4
@Marcelo: จริงๆแล้วมันทำในโมดูลตัวห่อ Python sqlite3 docs.python.org/library/…กล่าวว่า "" "โมดูล sqlite3 ใช้แคชคำสั่งภายในเพื่อหลีกเลี่ยงการแยกวิเคราะห์ค่าใช้จ่ายของ SQL หากคุณต้องการกำหนดจำนวนคำสั่งที่แคชไว้สำหรับการเชื่อมต่ออย่างชัดเจนคุณสามารถตั้งค่าพารามิเตอร์ cached_statements ค่าเริ่มต้นที่ใช้ในปัจจุบันคือแคช 100 คำสั่ง "" "
John Machin

9
#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys, csv, sqlite3

def main():
    con = sqlite3.connect(sys.argv[1]) # database file input
    cur = con.cursor()
    cur.executescript("""
        DROP TABLE IF EXISTS t;
        CREATE TABLE t (COL1 TEXT, COL2 TEXT);
        """) # checks to see if table exists and makes a fresh table.

    with open(sys.argv[2], "rb") as f: # CSV file input
        reader = csv.reader(f, delimiter=',') # no header information with delimiter
        for row in reader:
            to_db = [unicode(row[0], "utf8"), unicode(row[1], "utf8")] # Appends data from CSV file representing and handling of text
            cur.execute("INSERT INTO neto (COL1, COL2) VALUES(?, ?);", to_db)
            con.commit()
    con.close() # closes connection to database

if __name__=='__main__':
    main()

9

ขอบคุณมากสำหรับคำตอบของ Bernie ! ต้องปรับแต่งเล็กน้อย - นี่คือสิ่งที่ใช้ได้ผลสำหรับฉัน:

import csv, sqlite3
conn = sqlite3.connect("pcfc.sl3")
curs = conn.cursor()
curs.execute("CREATE TABLE PCFC (id INTEGER PRIMARY KEY, type INTEGER, term TEXT, definition TEXT);")
reader = csv.reader(open('PC.txt', 'r'), delimiter='|')
for row in reader:
    to_db = [unicode(row[0], "utf8"), unicode(row[1], "utf8"), unicode(row[2], "utf8")]
    curs.execute("INSERT INTO PCFC (type, term, definition) VALUES (?, ?, ?);", to_db)
conn.commit()

ไฟล์ข้อความของฉัน (PC.txt) มีลักษณะดังนี้:

1 | Term 1 | Definition 1
2 | Term 2 | Definition 2
3 | Term 3 | Definition 3

7

คุณคิดถูกที่.importจะไป แต่นั่นเป็นคำสั่งจากเชลล์ SQLite3.exe คำตอบยอดนิยมสำหรับคำถามนี้เกี่ยวข้องกับ python loops แต่ถ้าไฟล์ของคุณมีขนาดใหญ่ (ของฉันคือ 10 ^ 6 ถึง 10 ^ 7 เร็กคอร์ด) คุณต้องการหลีกเลี่ยงการอ่านทุกอย่างเป็นแพนด้าหรือใช้ความเข้าใจ / ลูปรายการ python แบบเนทีฟ (แม้ว่าฉันจะไม่ได้เวลาเปรียบเทียบ)

สำหรับไฟล์ขนาดใหญ่ฉันเชื่อว่าตัวเลือกที่ดีที่สุดคือการสร้างตารางว่างไว้ล่วงหน้าโดยใช้sqlite3.execute("CREATE TABLE...")แถบส่วนหัวจากไฟล์ CSV ของคุณจากนั้นใช้subprocess.run()เพื่อดำเนินการคำสั่งนำเข้าของ sqlite เนื่องจากส่วนสุดท้ายคือฉันเชื่อว่าตรงประเด็นที่สุดฉันจะเริ่มต้นด้วยสิ่งนั้น

subprocess.run()

from pathlib import Path
db_name = Path('my.db').resolve()
csv_file = Path('file.csv').resolve()
result = subprocess.run(['sqlite3',
                         str(db_name),
                         '-cmd',
                         '.mode csv',
                         '.import '+str(csv_file).replace('\\','\\\\')
                                 +' <table_name>'],
                        capture_output=True)

คำชี้แจงจากบรรทัดคำสั่งคำสั่งที่คุณกำลังมองหาอยู่
เรียกใช้กระบวนการบรรทัดคำสั่ง อาร์กิวเมนต์ถึงคือลำดับของสตริงที่ตีความเป็นคำสั่งตามด้วยอาร์กิวเมนต์ทั้งหมดsqlite3 my.db -cmd ".mode csv" ".import file.csv table"subprocess.run()subprocess.run()

  • sqlite3 my.db เปิดฐานข้อมูล
  • -cmdแฟล็กหลังจากฐานข้อมูลอนุญาตให้คุณส่งคำสั่ง follow on หลายคำสั่งไปยังโปรแกรม sqlite ในเชลล์แต่ละคำสั่งจะต้องอยู่ในเครื่องหมายคำพูด แต่ที่นี่จำเป็นต้องเป็นองค์ประกอบของลำดับของตัวเอง
  • '.mode csv' ทำในสิ่งที่คุณคาดหวัง
  • '.import '+str(csv_file).replace('\\','\\\\')+' <table_name>'คือคำสั่งนำเข้า
    น่าเสียดายเนื่องจากกระบวนการย่อยส่งต่อการติดตามทั้งหมดไป-cmdเป็นสตริงที่ยกมาคุณจึงต้องเพิ่มแบ็กสแลชเป็นสองเท่าหากคุณมีพา ธ ไดเร็กทอรี windows

การปอกส่วนหัว

ไม่ใช่ประเด็นหลักของคำถาม แต่นี่คือสิ่งที่ฉันใช้ อีกครั้งฉันไม่ต้องการอ่านไฟล์ทั้งหมดลงในหน่วยความจำ ณ เวลาใด:

with open(csv, "r") as source:
    source.readline()
    with open(str(csv)+"_nohead", "w") as target:
        shutil.copyfileobj(source, target)

4

ขึ้นอยู่กับวิธีแก้ปัญหา Guy L (รักมัน) แต่สามารถจัดการช่องที่มีการหลีกเลี่ยงได้

import csv, sqlite3

def _get_col_datatypes(fin):
    dr = csv.DictReader(fin) # comma is default delimiter
    fieldTypes = {}
    for entry in dr:
        feildslLeft = [f for f in dr.fieldnames if f not in fieldTypes.keys()]        
        if not feildslLeft: break # We're done
        for field in feildslLeft:
            data = entry[field]

            # Need data to decide
            if len(data) == 0:
                continue

            if data.isdigit():
                fieldTypes[field] = "INTEGER"
            else:
                fieldTypes[field] = "TEXT"
        # TODO: Currently there's no support for DATE in sqllite

    if len(feildslLeft) > 0:
        raise Exception("Failed to find all the columns data types - Maybe some are empty?")

    return fieldTypes


def escapingGenerator(f):
    for line in f:
        yield line.encode("ascii", "xmlcharrefreplace").decode("ascii")


def csvToDb(csvFile,dbFile,tablename, outputToFile = False):

    # TODO: implement output to file

    with open(csvFile,mode='r', encoding="ISO-8859-1") as fin:
        dt = _get_col_datatypes(fin)

        fin.seek(0)

        reader = csv.DictReader(fin)

        # Keep the order of the columns name just as in the CSV
        fields = reader.fieldnames
        cols = []

        # Set field and type
        for f in fields:
            cols.append("\"%s\" %s" % (f, dt[f]))

        # Generate create table statement:
        stmt = "create table if not exists \"" + tablename + "\" (%s)" % ",".join(cols)
        print(stmt)
        con = sqlite3.connect(dbFile)
        cur = con.cursor()
        cur.execute(stmt)

        fin.seek(0)


        reader = csv.reader(escapingGenerator(fin))

        # Generate insert statement:
        stmt = "INSERT INTO \"" + tablename + "\" VALUES(%s);" % ','.join('?' * len(cols))

        cur.executemany(stmt, reader)
        con.commit()
        con.close()

4

คุณสามารถทำได้โดยใช้blaze& odoอย่างมีประสิทธิภาพ

import blaze as bz
csv_path = 'data.csv'
bz.odo(csv_path, 'sqlite:///data.db::data')

Odo จะเก็บไฟล์ csv ไว้ที่data.db(ฐานข้อมูล sqlite) ภายใต้สคีมาdata

หรือคุณใช้โดยตรงโดยไม่ต้องodo blazeทั้งสองวิธีก็ใช้ได้ อ่านเอกสารนี้


2
bz ไม่ได้กำหนด: P
holms

และมันอาจจะเป็นแพคเกจที่เก่าแก่มากเนื่องจากข้อผิดพลาดภายในของเขา: AttributeError: วัตถุ 'SubDiGraph' ไม่มีแอตทริบิวต์ 'ขอบ'
Holms

นอกจากนี้ยังได้รับข้อผิดพลาดแอตทริบิวต์เดียวกัน: ดูเหมือนว่าจะมีความคิดเห็นเกี่ยวกับ GitHub สำหรับมัน
user791411

2

หากต้องนำเข้าไฟล์ CSV เป็นส่วนหนึ่งของโปรแกรม python ดังนั้นเพื่อความเรียบง่ายและมีประสิทธิภาพคุณสามารถใช้os.systemตามบรรทัดที่แนะนำโดยต่อไปนี้:

import os

cmd = """sqlite3 database.db <<< ".import input.csv mytable" """

rc = os.system(cmd)

print(rc)

ประเด็นก็คือการระบุชื่อไฟล์ของฐานข้อมูลข้อมูลจะถูกบันทึกโดยอัตโนมัติโดยสมมติว่าไม่มีข้อผิดพลาดในการอ่าน


1
import csv, sqlite3

def _get_col_datatypes(fin):
    dr = csv.DictReader(fin) # comma is default delimiter
    fieldTypes = {}
    for entry in dr:
        feildslLeft = [f for f in dr.fieldnames if f not in fieldTypes.keys()]        
        if not feildslLeft: break # We're done
        for field in feildslLeft:
            data = entry[field]

        # Need data to decide
        if len(data) == 0:
            continue

        if data.isdigit():
            fieldTypes[field] = "INTEGER"
        else:
            fieldTypes[field] = "TEXT"
    # TODO: Currently there's no support for DATE in sqllite

if len(feildslLeft) > 0:
    raise Exception("Failed to find all the columns data types - Maybe some are empty?")

return fieldTypes


def escapingGenerator(f):
    for line in f:
        yield line.encode("ascii", "xmlcharrefreplace").decode("ascii")


def csvToDb(csvFile,dbFile,tablename, outputToFile = False):

    # TODO: implement output to file

    with open(csvFile,mode='r', encoding="ISO-8859-1") as fin:
        dt = _get_col_datatypes(fin)

        fin.seek(0)

        reader = csv.DictReader(fin)

        # Keep the order of the columns name just as in the CSV
        fields = reader.fieldnames
        cols = []

        # Set field and type
        for f in fields:
            cols.append("\"%s\" %s" % (f, dt[f]))

        # Generate create table statement:
        stmt = "create table if not exists \"" + tablename + "\" (%s)" % ",".join(cols)
        print(stmt)
        con = sqlite3.connect(dbFile)
        cur = con.cursor()
        cur.execute(stmt)

        fin.seek(0)


        reader = csv.reader(escapingGenerator(fin))

        # Generate insert statement:
        stmt = "INSERT INTO \"" + tablename + "\" VALUES(%s);" % ','.join('?' * len(cols))

        cur.executemany(stmt, reader)
        con.commit()
        con.close()

2
โปรดจัดรูปแบบรหัสของคุณให้ถูกต้องและเพิ่มคำอธิบาย
ปฏิบัติการ

1

เพื่อความเรียบง่ายคุณสามารถใช้เครื่องมือบรรทัดคำสั่ง sqlite3 จาก Makefile ของโครงการของคุณ

%.sql3: %.csv
    rm -f $@
    sqlite3 $@ -echo -cmd ".mode csv" ".import $< $*"
%.dump: %.sql3
    sqlite3 $< "select * from $*"

make test.sql3จากนั้นสร้างฐานข้อมูล sqlite จากไฟล์ test.csv ที่มีอยู่พร้อมด้วย "test" ตารางเดียว จากนั้นคุณสามารถmake test.dumpตรวจสอบเนื้อหาได้


1

ฉันพบว่าอาจจำเป็นต้องแยกการถ่ายโอนข้อมูลจาก csv ไปยังฐานข้อมูลเป็นกลุ่มเพื่อไม่ให้หน่วยความจำหมด สามารถทำได้ดังนี้:

import csv
import sqlite3
from operator import itemgetter

# Establish connection
conn = sqlite3.connect("mydb.db")

# Create the table 
conn.execute(
    """
    CREATE TABLE persons(
        person_id INTEGER,
        last_name TEXT, 
        first_name TEXT, 
        address TEXT
    )
    """
)

# These are the columns from the csv that we want
cols = ["person_id", "last_name", "first_name", "address"]

# If the csv file is huge, we instead add the data in chunks
chunksize = 10000

# Parse csv file and populate db in chunks
with conn, open("persons.csv") as f:
    reader = csv.DictReader(f)

    chunk = []
    for i, row in reader: 

        if i % chunksize == 0 and i > 0:
            conn.executemany(
                """
                INSERT INTO persons
                    VALUES(?, ?, ?, ?)
                """, chunk
            )
            chunk = []

        items = itemgetter(*cols)(row)
        chunk.append(items)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.