Importieren einer CSV-Datei in eine SQLite3-Datenbanktabelle mit Python

105

Ich habe eine CSV-Datei und möchte diese Datei mithilfe von Python in meine SQLite3-Datenbank importieren. Der Befehl lautet ".import .....". aber es scheint, dass es so nicht funktionieren kann. Kann mir jemand ein Beispiel geben, wie es in sqlite3 geht? Ich benutze Windows nur für den Fall. Vielen Dank

Hossein
quelle
3
Bitte geben Sie den tatsächlichen Befehl, der nicht funktioniert hat, und die tatsächliche Fehlermeldung an. "import ...." könnte alles sein. "kann nicht arbeiten" ist zu vage, als dass wir es erraten könnten. Ohne Details können wir nicht helfen.
S.Lott
2
Der eigentliche Befehl, wie gesagt, ist ".import" und es heißt Syntaxfehler neu ".import"
Hossein
10
Bitte posten Sie tatsächlich den eigentlichen Befehl in der Frage. Bitte posten Sie tatsächlich die eigentliche Fehlermeldung in der Frage. Bitte fügen Sie keine Kommentare hinzu, die einfach Dinge wiederholen. Bitte aktualisieren Sie die Frage mit dem tatsächlichen Kopieren und Einfügen von dem, was Sie tatsächlich tun.
S.Lott

Antworten:

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()
mechanisches Fleisch
quelle
4
Falls Sie dieselben Probleme hatten wie ich: Stellen Sie sicher, dass Sie col1 und col2 in die Spaltenüberschriften in der CSV-Datei ändern. Schließen Sie die Verbindung zur Datenbank, indem Sie am Ende con.close () aufrufen.
Jonas
1
Danke, @Jonas. Aktualisierter Beitrag.
mechanisches
Ich bekomme immer wieder, not all arguments converted during string formattingwenn ich diese Methode versuche.
Whitecat
Ich habe diese Methode ausprobiert, aber sie funktioniert bei mir nicht. Könnten Sie meine Datensätze hier überprüfen (sie sind sehr normal, außer dass einige Spalten leere Werte haben) und versuchen, sie mit Ihrem Code zu importieren? stackoverflow.com/questions/46042623/…
user177196
2
Dieser Code ist nicht für sehr große CSV-Dateien (Reihenfolge der GBs) optimiert
Nisba
91

Das Erstellen einer SQLite-Verbindung zu einer Datei auf der Festplatte bleibt dem Leser als Übung überlassen. Die Pandas-Bibliothek ermöglicht jetzt einen zweizeiligen Vorgang

df = pandas.read_csv(csvfile)
df.to_sql(table_name, conn, if_exists='append', index=False)
Tennessee Leeuwenburg
quelle
Danke. Ich habe ein Problem mit Panda. Mein CSV wird durch ';' und haben ',' in Einträgen. Panda gibt Fehler auf read_csv. Gibt es eine Einstellung zum Lesen von Einträgen mit Kommas ohne vorübergehende Ersetzung?
Alexei Martianov
3
benutze sep = ';'. In der Pandas-Dokumentation wird klar umrissen, wie damit umgegangen werden soll.
Tennessee Leeuwenburg
3
Gibt es eine Möglichkeit, Pandas zu verwenden, aber ohne den RAM zu verwenden? Ich habe eine riesige CSV-Datei (7 GB), die ich nicht als Datenrahmen importieren und dann an die Datenbank anhängen kann.
Pablo
1
Ja, es gibt eine Methode in Pandas, die eher in Stücken als auf einmal liest. Ich fürchte, ich kann mich nicht genau an meinen Kopf erinnern. Ich denke, Sie fügen chunksize = <Anzahl_der_Zeilen> hinzu und erhalten dann einen Iterator zurück, mit dem Sie stückweise an eine Datenbank anhängen können. Lassen Sie mich wissen, wenn Sie Probleme haben, es zu finden, und ich kann ein Rezept ausgraben.
Tennessee Leeuwenburg
1
Sehr schön, @TennesseeLeeuwenburg. Ich hatte keine Notwendigkeit dafür, dfalso verkürzte ich Ihr Beispiel auf:pandas.read_csv(csvfile).to_sql(table_name, conn, if_exists='append', index=False)
keithpjolley
13

Meine 2 Cent (allgemeiner):

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
Guy L.
quelle
1
if len (feildslLeft)> 0: immer wahr, also eine Ausnahme auslösen. Bitte überprüfen und korrigieren Sie dies.
Amu61
Gibt es eine Möglichkeit, dies zu tun, ohne fseek () verwenden zu müssen, damit es für Streams verwendet werden kann?
Mwag
1
@mwag Sie können einfach die Überprüfung des Spaltentyps überspringen und stattdessen alle Spalten als Text importieren.
user5359531
12

Der .importBefehl ist eine Funktion des Befehlszeilentools sqlite3. Um dies in Python zu tun, sollten Sie die Daten einfach mit den von Python bereitgestellten Funktionen wie dem CSV-Modul laden und die Daten wie gewohnt einfügen.

Auf diese Weise haben Sie auch die Kontrolle darüber, welche Typen eingefügt werden, anstatt sich auf das scheinbar undokumentierte Verhalten von sqlite3 zu verlassen.

Marcelo Cantos
quelle
1
Der Einsatz muss nicht vorbereitet werden. Die Quelle von SQL-Anweisungen und kompilierten Ergebnissen wird in einem Cache gespeichert.
John Machin
@ John Machin: Gibt es einen Link dazu, wie SQLite das macht?
Marcelo Cantos
@Marcelo: Wenn Sie daran interessiert sind, wie es gemacht wird (warum?), Schauen Sie in die SQLite-Quelle oder fragen Sie in der SQLite-Mailingliste nach.
John Machin
@ John Machin: Ich bin interessiert, weil es in der gesamten SQLite-Dokumentation, auf die ich gestoßen bin, kein einziges Wort über das automatische Zwischenspeichern unvorbereiteter Anweisungen gibt. Ich denke nicht, dass es vernünftig ist, Quellcode zu lesen oder Mailinglisten zu prüfen, um etwas so Grundlegendes zu entdecken, wie ob ich meine SQL-Anweisungen vorbereiten sollte oder nicht. Was ist Ihre Informationsquelle dazu?
Marcelo Cantos
4
@Marcelo: Eigentlich ist es im Python sqlite3 Wrapper Modul. docs.python.org/library/… sagt "" "Das sqlite3-Modul verwendet intern einen Anweisungscache, um SQL-Parsing-Overhead zu vermeiden. Wenn Sie die Anzahl der Anweisungen, die für die Verbindung zwischengespeichert werden, explizit festlegen möchten, können Sie den Parameter cached_statements festlegen Die derzeit implementierte Standardeinstellung ist das Zwischenspeichern von 100 Anweisungen. "" "
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()
Christopher
quelle
9

Vielen Dank für Bernies Antwort ! Musste es ein bisschen optimieren - hier ist, was für mich funktioniert hat:

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()

Meine Textdatei (PC.txt) sieht folgendermaßen aus:

1 | Term 1 | Definition 1
2 | Term 2 | Definition 2
3 | Term 3 | Definition 3
jiy
quelle
6

Sie haben Recht, das .importist der richtige Weg, aber das ist ein Befehl aus der SQLite3.exe-Shell. Viele der häufigsten Antworten auf diese Frage betreffen native Python-Schleifen. Wenn Ihre Dateien jedoch groß sind (meine sind 10 ^ 6 bis 10 ^ 7 Datensätze), möchten Sie vermeiden, alles in Pandas einzulesen oder ein natives Python-Listenverständnis / eine native Python-Listenschleife zu verwenden (obwohl ich sie nicht zum Vergleich zeitlich festgelegt habe).

Für große Dateien ist es meiner Meinung nach die beste Option, die leere Tabelle im Voraus zu erstellen sqlite3.execute("CREATE TABLE..."), die Header aus Ihren CSV-Dateien subprocess.run()zu entfernen und dann die Importanweisung von sqlite auszuführen. Da der letzte Teil meiner Meinung nach der relevanteste ist, werde ich damit beginnen.

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)

Erläuterung
In der Befehlszeile suchen Sie nach dem Befehl sqlite3 my.db -cmd ".mode csv" ".import file.csv table". subprocess.run()führt einen Befehlszeilenprozess aus. Das Argument to subprocess.run()ist eine Folge von Zeichenfolgen, die als Befehl interpretiert werden, gefolgt von allen Argumenten.

  • sqlite3 my.db öffnet die Datenbank
  • -cmdMit flag nach der Datenbank können Sie mehrere Folgebefehle an das SQLite-Programm übergeben. In der Shell muss jeder Befehl in Anführungszeichen stehen, aber hier müssen sie nur ihr eigenes Element der Sequenz sein
  • '.mode csv' tut, was Sie erwarten würden
  • '.import '+str(csv_file).replace('\\','\\\\')+' <table_name>'ist der Importbefehl.
    Da der Unterprozess alle Folgemaßnahmen -cmdals Zeichenfolgen in Anführungszeichen übergibt , müssen Sie Ihre Backslashes leider verdoppeln, wenn Sie einen Windows-Verzeichnispfad haben.

Header entfernen

Nicht wirklich der Hauptpunkt der Frage, aber hier ist, was ich verwendet habe. Auch hier wollte ich zu keinem Zeitpunkt die gesamten Dateien in den Speicher lesen:

with open(csv, "r") as source:
    source.readline()
    with open(str(csv)+"_nohead", "w") as target:
        shutil.copyfileobj(source, target)
Jake Stevens-Haas
quelle
4

Basierend auf der Guy L-Lösung (Love it), kann jedoch maskierte Felder verarbeiten.

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()
Jace
quelle
4

Sie können dies mit blaze& odoeffizient tun

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

Odo speichert die CSV-Datei in data.db(SQLite-Datenbank) unter dem Schemadata

Oder Sie verwenden ododirekt, ohne blaze. In beiden Fällen ist alles in Ordnung. Lesen Sie diese Dokumentation

Kathirmani Sukumar
quelle
2
bz nicht definiert: P
holms
und es ist wahrscheinlich ein sehr altes Paket wegen seines inneren Fehlers: AttributeError: 'SubDiGraph'-Objekt hat kein Attribut' edge '
holms
Erhält
2

Wenn die CSV-Datei als Teil eines Python-Programms importiert werden muss, können Sie sie der Einfachheit und Effizienz halber wie os.systemfolgt verwenden:

import os

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

rc = os.system(cmd)

print(rc)

Der Punkt ist, dass durch Angabe des Dateinamens der Datenbank die Daten automatisch gespeichert werden, vorausgesetzt, es gibt keine Fehler beim Lesen.

Gipfel
quelle
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()
Ramy Awad
quelle
2
Bitte formatieren Sie Ihren Code richtig und fügen Sie eine Erklärung hinzu
ausführbare Datei
1

Der Einfachheit halber können Sie das Befehlszeilentool sqlite3 aus dem Makefile Ihres Projekts verwenden.

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

make test.sql3Anschließend wird die SQLite-Datenbank aus einer vorhandenen Datei test.csv mit einer einzelnen Tabelle "test" erstellt. Sie können dann make test.dumpden Inhalt überprüfen.

jcomeau_ictx
quelle
1

Ich habe festgestellt, dass es notwendig sein kann, die Übertragung von Daten von der CSV zur Datenbank in Blöcken aufzuteilen, damit nicht der Speicher ausgeht. Dies kann folgendermaßen geschehen:

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)
Peter H.
quelle