Імпорт файлу CSV в таблицю бази даних sqlite3 за допомогою Python


106

У мене є файл CSV, і я хочу імпортувати цей файл у свою базу даних sqlite3 за допомогою Python. команда ".import .....". але, здається, він не може працювати так. Хтось може надати мені приклад, як це зробити в sqlite3? Я використовую Windows на всякий випадок. Дякую


3
Укажіть фактичну команду, яка не працювала, та фактичне повідомлення про помилку. "імпорт ...." може бути чим завгодно. "Не може працювати" - занадто розпливчасте для нас здогадування. Без деталей ми не можемо допомогти.
С.Лотт

2
фактична команда, як я вже сказав, - ".import", і вона говорить про помилку синтаксису новий ".import"
Hossein

10
Будь ласка, опублікуйте фактичну команду у питанні. Будь ласка, опублікуйте фактичне повідомлення про помилку у питанні. Будь ласка, не додайте коментарів, які просто повторюють речі. Оновіть питання актуальною копією та вставкою того, що ви насправді робите.
С.Лотт

Відповіді:


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 ().
Йонас

1
Дякую, @Jonas. Оновлена ​​публікація.
mechanical_meat

Я продовжую отримувати, not all arguments converted during string formattingколи я намагаюся використовувати цей метод.
Whitecat

Я спробував цей метод, але він не працює для мене. Чи можете ви перевірити мої набори даних тут (вони дуже нормальні, за винятком того, що у деяких стовпцях є порожні значення) та спробувати імпортувати їх зі своїм кодом? stackoverflow.com/questions/46042623 / ...
user177196

2
Цей код не оптимізований для дуже великих файлів CSV (близько ГЗ)
нісба

91

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

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

спасибі. У мене виникла проблема з пандою. мій csv обмежений символом ';' і мати "," у записах. panda видає помилку на read_csv. будь-яке налаштування для читання записів з комами без виходу тимчасово замінити?
Олексій Мартьянов

3
використовувати sep = ';'. Документація про панди чітко визначає, як з цим боротися.
Теннессі Левенвенбург

3
чи є спосіб використовувати панди, але без використання оперативної пам’яті? У мене величезна .csv (7gb), яку я не можу імпортувати як кадр даних, а потім додаю до БД.
Пабло

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: завжди відповідає дійсності, тому піднімаємо виняток. Перегляньте та виправте це.
amu61

Будь-який спосіб зробити це без необхідності fseek (), щоб його можна було використовувати в потоках?
mwag

1
@mwag ви можете просто пропустити перевірку типу стовпця та імпортувати всі стовпці як текст.
користувач5359531

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), ви хочете уникати читання всього в пандах або з використанням природного списку пітонів для розуміння / циклу (хоча я їх не встиг порівняти).

Для великих файлів я вважаю, що найкращим варіантом є заздалегідь створити порожню таблицю за допомогою 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 database) під схемоюdata

Або ви використовуєте odoбезпосередньо, без blaze. Будь-який спосіб - це добре. Прочитайте цю документацію


2
bz не визначено: P
holms

і це, мабуть, дуже старий пакет через його внутрішню помилку: AttributeError: 'SubDiGraph' об’єкт не має атрибута 'edge'
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 з єдиною таблицею "тест". Ви можете 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.