Імпорт файлу 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.