Importazione di un file CSV in una tabella di database sqlite3 utilizzando Python


106

Ho un file CSV e voglio importare in blocco questo file nel mio database sqlite3 usando Python. il comando è ".import .....". ma sembra che non possa funzionare così. Qualcuno può darmi un esempio di come farlo in sqlite3? Sto usando Windows per ogni evenienza. Grazie


3
Fornisci il comando effettivo che non ha funzionato e il messaggio di errore effettivo . "import ...." potrebbe essere qualsiasi cosa. "non può funzionare" è troppo vago per essere indovinato. Senza dettagli, non possiamo aiutare.
S.Lott

2
il comando effettivo come ho detto è ".import" e dice errore di sintassi nuovo ".import"
Hossein

10
Si prega di inserire effettivamente il comando effettivo nella domanda. Si prega di pubblicare effettivamente il messaggio di errore effettivo nella domanda. Per favore, non aggiungere commenti che semplicemente ripetono le cose. Aggiorna la domanda con il copia e incolla effettivo di ciò che stai effettivamente facendo.
S.Lott

Risposte:


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
Nel caso avessi gli stessi problemi che ho fatto io: assicurati di cambiare col1 e col2 nelle intestazioni di colonna nel file csv. E chiudi la connessione al database chiamando con.close () alla fine.
Jonas

1
Grazie, @ Jonas. Post aggiornato.
Mechanical_meat

Continuo a ricevere not all arguments converted during string formattingquando provo questo metodo.
Whitecat

Ho provato questo metodo, ma non funziona per me. Potresti controllare i miei set di dati qui (sono molto normali, tranne che alcune colonne hanno valori vuoti) e provare a importarli con il tuo codice? stackoverflow.com/questions/46042623/...
user177196

2
Questo codice non è ottimizzato per file CSV molto grandi (ordine di GB)
Nisba

91

La creazione di una connessione sqlite a un file su disco è lasciata come esercizio per il lettore ... ma ora c'è una doppia riga resa possibile dalla libreria pandas

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

grazie. Ho un problema con il panda. il mio csv è delimitato da ";" e hanno "," nelle voci. panda dà errore su read_csv. qualsiasi impostazione per leggere le voci con virgole senza sostituire temporaneamente?
Alexei Martianov

3
usa sep = ';'. La documentazione dei panda delinea chiaramente come affrontarlo.
Tennessee Leeuwenburg,

3
c'è un modo per usare i panda ma senza usare la RAM ?, ho un enorme .csv (7gb) che non posso importare come dataframe e poi aggiunto al DB.
Pablo

1
Sì, c'è un metodo nei panda che leggerà a pezzi anziché tutto in una volta. Temo di non riuscire a ricordare esattamente dalla parte superiore della mia testa. Penso che aggiungi chunksize = <number_of_rows>, e poi ottieni un iteratore che puoi usare per aggiungere a un database a tratti. Fammi sapere se hai problemi a trovarlo e posso tirare fuori una ricetta.
Tennessee Leeuwenburg

1
Molto gentile, @TennesseeLeeuwenburg. Non ne avevo bisogno, dfquindi ho ridotto il tuo esempio a:pandas.read_csv(csvfile).to_sql(table_name, conn, if_exists='append', index=False)
keithpjolley

13

I miei 2 centesimi (più generici):

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
if len (feildslLeft)> 0: sempre vero, quindi solleva un'eccezione. Si prega di rivedere e correggere questo.
amu61

Qualche modo per farlo senza dover fseek (), in modo che possa essere utilizzato negli stream?
mwag

1
@mwag puoi semplicemente saltare il controllo del tipo di colonna e importare invece tutte le colonne come testo.
user5359531

12

Il .importcomando è una funzionalità dello strumento da riga di comando sqlite3. Per farlo in Python, dovresti semplicemente caricare i dati utilizzando qualsiasi funzionalità di Python, come il modulo csv , e inserire i dati come al solito.

In questo modo, hai anche il controllo sui tipi da inserire, piuttosto che fare affidamento sul comportamento apparentemente non documentato di sqlite3.


1
Non è necessario preparare l'inserto. L'origine delle istruzioni SQL e dei risultati compilati vengono conservati in una cache.
John Machin

@ John Machin: C'è un collegamento a come SQLite fa questo?
Marcelo Cantos

@ Marcelo: Se sei interessato a COME è fatto (perché?), Guarda nel sorgente sqlite o chiedi nella mailing list sqlite.
John Machin

@ John Machin: Sono interessato perché in tutta la documentazione SQLite che ho trovato, non c'è una sola parola sulla memorizzazione automatica nella cache di istruzioni non preparate. Non penso sia ragionevole dover leggere il codice sorgente o sondare le mailing list per scoprire qualcosa di così basilare come se dovessi preparare le mie istruzioni SQL o meno. Qual è la tua fonte di informazioni su questo?
Marcelo Cantos

4
@ Marcelo: In realtà è fatto nel modulo wrapper sqlite3 di Python. docs.python.org/library/… dice "" "Il modulo sqlite3 utilizza internamente una cache delle istruzioni per evitare l'overhead di analisi SQL. Se desideri impostare esplicitamente il numero di istruzioni memorizzate nella cache per la connessione, puoi impostare il parametro cached_statements L'impostazione predefinita attualmente implementata è di memorizzare nella cache 100 istruzioni. "" "
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()

9

Molte grazie per la risposta di Bernie ! Ho dovuto modificarlo un po '- ecco cosa ha funzionato per me:

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

Il mio file di testo (PC.txt) ha questo aspetto:

1 | Term 1 | Definition 1
2 | Term 2 | Definition 2
3 | Term 3 | Definition 3

7

Hai ragione che .importè la strada da percorrere, ma è un comando dalla shell SQLite3.exe. Molte delle migliori risposte a questa domanda riguardano loop nativi di Python, ma se i tuoi file sono di grandi dimensioni (i miei sono da 10 ^ 6 a 10 ^ 7 record), vuoi evitare di leggere tutto nei panda o usare un ciclo di comprensione / ciclo nativo di python list (anche se non li ho presi tempo per il confronto).

Per file di grandi dimensioni, credo che l'opzione migliore sia creare la tabella vuota in anticipo utilizzando sqlite3.execute("CREATE TABLE..."), rimuovere le intestazioni dai file CSV e quindi utilizzare subprocess.run()per eseguire l'istruzione import di sqlite. Poiché l'ultima parte credo sia la più pertinente, inizierò con quella.

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)

Spiegazione
Dalla riga di comando, il comando che stai cercando è sqlite3 my.db -cmd ".mode csv" ".import file.csv table". subprocess.run()esegue un processo da riga di comando. L'argomento a subprocess.run()è una sequenza di stringhe che vengono interpretate come un comando seguito da tutti i suoi argomenti.

  • sqlite3 my.db apre il database
  • -cmdflag dopo il database consente di passare più comandi follow-on al programma sqlite. Nella shell, ogni comando deve essere racchiuso tra virgolette, ma qui devono essere solo il proprio elemento della sequenza
  • '.mode csv' fa quello che ti aspetteresti
  • '.import '+str(csv_file).replace('\\','\\\\')+' <table_name>'è il comando di importazione.
    Sfortunatamente, poiché il sottoprocesso passa tutti i follow-on -cmdcome stringhe tra virgolette, è necessario raddoppiare le barre rovesciate se si dispone di un percorso di directory di Windows.

Eliminazione delle intestazioni

Non proprio il punto principale della domanda, ma ecco cosa ho usato. Di nuovo, non volevo leggere tutti i file in memoria in nessun momento:

with open(csv, "r") as source:
    source.readline()
    with open(str(csv)+"_nohead", "w") as target:
        shutil.copyfileobj(source, target)

4

Basato sulla soluzione Guy L (lo adoro) ma può gestire i campi con escape.

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

Puoi farlo usando blazee in odomodo efficiente

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

Odo memorizzerà il file csv in data.db(database sqlite) sotto lo schemadata

Oppure lo usi ododirettamente, senza blaze. In entrambi i modi va bene. Leggi questa documentazione


2
bz non definito: P
holms

ed è probabilmente un pacchetto molto vecchio a causa del suo errore interno: AttributeError: l'oggetto 'SubDiGraph' non ha attributo 'edge'
holms


2

Se il file CSV deve essere importato come parte di un programma python, quindi per semplicità ed efficienza, è possibile utilizzarlo os.systemseguendo le linee suggerite da quanto segue:

import os

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

rc = os.system(cmd)

print(rc)

Il punto è che specificando il nome del file del database, i dati verranno salvati automaticamente, assumendo che non ci siano errori durante la lettura.


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
Si prega di formattare correttamente il codice e aggiungere qualche spiegazione
eseguibile

1

nell'interesse della semplicità, è possibile utilizzare lo strumento a riga di comando sqlite3 dal Makefile del progetto.

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

make test.sql3quindi crea il database sqlite da un file test.csv esistente, con una singola tabella "test". è quindi possibile make test.dumpverificare il contenuto.


1

Ho scoperto che può essere necessario suddividere il trasferimento dei dati dal csv al database in blocchi per non esaurire la memoria. Questo può essere fatto in questo modo:

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.