Импорт файла CSV в таблицу базы данных sqlite3 с использованием Python

106

У меня есть файл CSV, и я хочу массово импортировать этот файл в свою базу данных sqlite3 с помощью Python. команда ".import .....". но вроде так работать не может. Может ли кто-нибудь привести мне пример того, как это сделать в sqlite3? Я на всякий случай использую окна. Спасибо

Хоссейн
источник
3
Укажите фактическую команду, которая не сработала, и фактическое сообщение об ошибке. "импорт ...." может быть любым. «не может работать» слишком расплывчато, чтобы о нем можно было догадаться. Без подробностей мы не можем помочь.
S.Lott
3
фактическая команда, как я уже сказал, это ".import", и в ней написана синтаксическая ошибка new ".import"
Хоссейн
10
Пожалуйста, опубликуйте актуальную команду в вопросе. Пожалуйста, разместите фактическое сообщение об ошибке в вопросе. Пожалуйста, не добавляйте комментарии, которые просто повторяют вещи. Обновите вопрос, скопировав и вставив то, что вы на самом деле делаете.
S.Lott

Ответы:

135
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 01
Я пробовал этот метод, но у меня он не работает. Не могли бы вы проверить мои наборы данных здесь (они нормальные, за исключением того, что в некоторых столбцах есть пустые значения) и попробовать импортировать их с помощью своего кода? stackoverflow.com/questions/46042623/…
user177196 04
2
Этот код не оптимизирован для очень больших файлов csv (порядка гигабайт)
Нисба,
92

Создание подключения sqlite к файлу на диске оставлено в качестве упражнения для читателя ... но теперь есть двухстрочный интерфейс, который стал возможным благодаря библиотеке pandas

df = pandas.read_csv(csvfile)
df.to_sql(table_name, conn, if_exists='append', index=False)
Теннесси Левенбург
источник
Спасибо. У меня проблема с пандой. мой csv ограничен ';' и есть ',' в записях. панда выдает ошибку при read_csv. какие-либо настройки для чтения записей с запятыми без временной замены?
Alexei Martianov
3
используйте sep = ';'. Документация pandas четко описывает, как с этим бороться.
Теннесси Левенбург,
3
Есть ли способ использовать панды, но без использования ОЗУ? У меня есть огромный .csv (7 ГБ), который я не могу импортировать в качестве фрейма данных, а затем добавить в БД.
Пабло
1
Да, в пандах есть метод, который будет читать по частям, а не все сразу. Боюсь, что не могу вспомнить точно. Я думаю, вы добавляете 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 09
Любой способ сделать это без использования fseek (), чтобы его можно было использовать в потоках?
mwag
1
@mwag, вы можете просто пропустить проверку типа столбца и вместо этого импортировать все столбцы как текст.
user5359531
12

Команда .importявляется функцией инструмента командной строки sqlite3. Чтобы сделать это в Python, вы должны просто загрузить данные, используя любые возможности Python, такие как модуль csv , и вставить данные как обычно.

Таким образом, вы также можете контролировать, какие типы вставляются, вместо того, чтобы полагаться на, казалось бы, недокументированное поведение sqlite3.

Марсело Кантос
источник
1
Подготавливать вставку не нужно. Источник операторов SQL и скомпилированные результаты хранятся в кеше.
Джон Мачин
@John Machin: Есть ли ссылка на то, как SQLite это делает?
Марсело Кантос
@Marcelo: Если вас интересует, КАК это делается (почему?), Посмотрите исходный код sqlite или спросите в списке рассылки sqlite.
Джон Мачин
@John Machin: Мне интересно, потому что во всей документации SQLite, с которой я столкнулся, нет ни слова об автоматическом кэшировании неподготовленных операторов. Я не думаю, что разумно читать исходный код или проверять списки рассылки, чтобы обнаружить что-то столь же простое, как то, должен ли я готовить свои операторы SQL или нет. Каков ваш источник информации по этому поводу?
Марсело Кантос,
4
@Marcelo: На самом деле это делается в модуле оболочки Python sqlite3. docs.python.org/library/… говорит "" "Модуль sqlite3 внутренне использует кеш операторов, чтобы избежать накладных расходов на синтаксический анализ SQL. Если вы хотите явно указать количество операторов, которые кэшируются для соединения, вы можете установить параметр cached_statements . В настоящее время по умолчанию кэшируется 100 операторов. "" "
Джон Мачин
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

Большое спасибо за ответ Берни ! Пришлось немного подправить - вот что у меня сработало:

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, но если ваши файлы большие (у меня от 10 ^ 6 до 10 ^ 7 записей), вы хотите избежать чтения всего в pandas или использования собственного понимания / цикла списка 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Флаг после базы данных позволяет передавать в программу 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 (Love it), но может обрабатывать экранированные поля.

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' не имеет атрибута 'edge'
holms
Также
появляется такая
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проверить содержимое.

jcomeau_ictx
источник
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)
Питер Х.
источник