使用Python将CSV文件导入sqlite3数据库表


106

我有一个CSV文件,我想使用Python将该文件批量导入到我的sqlite3数据库中。该命令是“ .import .....”。但似乎不能这样工作。谁能给我一个在sqlite3中如何做的例子?我正在使用Windows,以防万一。谢谢


3
请提供无法使用的实际命令和实际错误消息。“导入...”可以是任何东西。“无法工作”太模糊了,我们无法猜测。没有细节,我们无济于事。
S.Lott,2010年

2
我说的实际命令是“ .import”,它说语法错误为“ .import”
Hossein 2010年

10
请实际在问题中发布实际命令。请实际在问题中发布实际的错误消息。请不要添加简单重复的评论。请使用实际复制和粘贴的内容来更新问题。
S.Lott,2010年

Answers:


132
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
谢谢@乔纳斯。更新的帖子。
mechanical_meat

not all arguments converted during string formatting当我尝试这种方法时,我会不断得到帮助。
Whitecat'9

我尝试了这种方法,但对我不起作用。您能否在这里检查我的数据集(它们非常正常,除了某些列的值为空),然后尝试将其与代码一起导入?stackoverflow.com/questions/46042623/…–
user177196

2
此代码未针对非常大的csv文件(GB的顺序)进行优化
-Nisba,

91

留给读者一个创建与磁盘上文件的sqlite连接的练习...但是熊猫库现在提供了两种方法

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

谢谢。我遇到了熊猫问题。我的csv以';'分隔 并在条目中添加“,”。熊猫在read_csv上给出错误。读取带有逗号的条目的任何设置(不带临时替换)?
Alexei Martianov

3
使用sep =';'。大熊猫文档清楚地概述了如何处理。
田纳西州吕温堡

3
有没有一种方法可以使用熊猫但不使用RAM?我有一个巨大的.csv(7gb),我无法作为数据帧导入然后附加到数据库。
巴勃罗

1
是的,pandas中有一种方法可以分块读取,而不是一次读取。恐怕我回想不起来。我认为您添加了chunksize = <number_of_rows>,然后又获得了一个迭代器,可以将该迭代器用于分段添加到数据库中。让我知道您是否找不到它,我可以找出一个食谱。
田纳西州吕温堡

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:始终为true,则引发异常。请查看并更正此问题。
amu61 '16

有什么方法可以执行此操作而不必使用fseek(),以便可以在流上使用它?
mwag '16

1
@mwag您可以跳过列类型检查,而是将所有列都导入为文本。
user5359531 '18

12

.import命令是sqlite3命令行工具的功能。要在Python中完成此操作,您应该简单地使用Python具备的任何功能(例如csv模块)加载数据,然后照常插入数据。

这样,您还可以控制要插入的类型,而不必依赖sqlite3似乎没有记录的行为。


1
无需准备插入物。SQL语句的源和编译结果保存在缓存中。
约翰·马钦

@John Machin:是否有链接指向SQLite的方式?
Marcelo Cantos

@Marcelo:如果您对如何完成感兴趣(为什么?),请查看sqlite来源或在sqlite邮件列表中询问。
约翰·马钦

@John Machin:我很感兴趣,因为在我遇到的所有SQLite文档中,都没有关于自动缓存未准备好的语句的一句话。我认为必须阅读源代码或探查邮件列表来发现一些基本内容(例如是否应该准备SQL语句)是不合理的。您在这方面的信息来源是什么?
Marcelo Cantos

4
@Marcelo:实际上是在Python sqlite3包装器模块中完成的。docs.python.org/library/…说“”“ sqlite3模块在内部使用语句缓存来避免SQL解析开销。如果要显式设置为连接缓存的语句数,则可以设置cached_statements参数。当前实现的默认值是缓存100条语句。“”“
John Machin 2010年

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 Shell的命令。这个问题的很多顶级答案都涉及本机python循环,但如果文件很大(我的记录是10 ^ 6到10 ^ 7记录),则要避免将所有内容读入熊猫或使用本机python列表理解/循环(尽管我没有时间比较它们)。

对于大文件,我认为最好的选择是使用预先创建空表,sqlite3.execute("CREATE TABLE...")从CSV文件中删除标题,然后用于subprocess.run()执行sqlite的import语句。由于我相信最后一部分是最相关的,所以我将从这一点开始。

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()运行命令行过程。to的参数subprocess.run()是一串字符串,这些字符串被解释为命令,后跟所有参数。

  • sqlite3 my.db 打开数据库
  • -cmd数据库允许您将多个跟进命令传递给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

为此,您可以使用blazeodo有效

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”对象有没有属性“边缘”
霍尔姆斯

也得到相同的属性错误:似乎在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

为了简单起见,您可以使用项目的Makefile中的sqlite3命令行工具。

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

make test.sql3然后从现有的test.csv文件使用单个表“ test”创建sqlite数据库。然后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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.