Importation d'un fichier CSV dans une table de base de données sqlite3 à l'aide de Python

106

J'ai un fichier CSV et je souhaite importer ce fichier en masse dans ma base de données sqlite3 en utilisant Python. la commande est ".import .....". mais il semble que cela ne peut pas fonctionner comme ça. Quelqu'un peut-il me donner un exemple de la façon de le faire dans sqlite3? J'utilise Windows au cas où. Merci

Hossein
la source
3
Veuillez fournir la commande réelle qui n'a pas fonctionné et le message d'erreur réel . "importer ...." pourrait être n'importe quoi. «ne peut pas fonctionner» est trop vague pour que nous puissions le deviner. Sans détails, nous ne pouvons pas vous aider.
S.Lott
2
la commande réelle comme je l'ai dit est ".import" et elle indique une erreur de syntaxe nouveau ".import"
Hossein
10
Veuillez publier la commande réelle dans la question. Veuillez publier le message d'erreur réel dans la question. Veuillez ne pas ajouter de commentaires qui répètent simplement des choses. Veuillez mettre à jour la question avec un copier-coller réel de ce que vous faites réellement.
S.Lott

Réponses:

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()
viande_mécanique
la source
4
Au cas où vous auriez les mêmes problèmes que moi: assurez-vous de remplacer col1 et col2 par les en-têtes de colonne du fichier csv. Et fermez la connexion à la base de données en appelant con.close () à la fin.
Jonas
1
Merci, @Jonas. Message mis à jour.
mechanical_meat
Je continue à avoir not all arguments converted during string formattingquand j'essaye cette méthode.
Whitecat le
J'ai essayé cette méthode, mais cela ne fonctionne pas pour moi. Pourriez-vous consulter mes ensembles de données ici (ils sont très normaux, sauf que certaines colonnes ont des valeurs vides) et essayer de les importer avec votre code? stackoverflow.com/questions/46042623/…
user177196
2
Ce code n'est pas optimisé pour les très gros fichiers csv (ordre des Go)
Nisba
92

La création d'une connexion sqlite à un fichier sur le disque reste un exercice pour le lecteur ... mais il y a maintenant un two-liner rendu possible par la bibliothèque pandas

df = pandas.read_csv(csvfile)
df.to_sql(table_name, conn, if_exists='append', index=False)
Tennessee Leeuwenburg
la source
Merci. J'ai un problème avec le panda. mon csv est délimité par ';' et avoir ',' dans les entrées. panda donne une erreur sur read_csv. un paramètre pour lire les entrées avec des virgules sans remplacer temporairement?
Alexei Martianov
3
utilisez sep = ';'. La documentation sur les pandas explique clairement comment gérer cela.
Tennessee Leeuwenburg
3
existe-t-il un moyen d'utiliser des pandas mais sans utiliser la RAM?, j'ai un énorme .csv (7 Go) que je ne peux pas importer en tant que dataframe, puis ajouté à la base de données.
Pablo
1
Oui, il existe une méthode dans les pandas qui lira en morceaux plutôt qu'en même temps. J'ai peur de ne pas me souvenir exactement du haut de ma tête. Je pense que vous ajoutez chunksize = <number_of_rows>, puis vous récupérez un itérateur que vous pouvez ensuite utiliser pour ajouter à une base de données par morceaux. Faites-moi savoir si vous avez du mal à le trouver et je pourrai trouver une recette.
Tennessee Leeuwenburg
1
Très gentil, @TennesseeLeeuwenburg. Je n'en avais pas besoin df, j'ai donc raccourci votre exemple à:pandas.read_csv(csvfile).to_sql(table_name, conn, if_exists='append', index=False)
keithpjolley
13

Mes 2 cents (plus générique):

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
la source
1
if len (feildslLeft)> 0: toujours vrai, donc lever une exception. Veuillez revoir et corriger cela.
amu61
Un moyen de le faire sans avoir à fseek (), afin qu'il puisse être utilisé sur les flux?
mwag
1
@mwag vous pouvez simplement ignorer la vérification du type de colonne et importer les colonnes sous forme de texte à la place.
user5359531
12

La .importcommande est une fonctionnalité de l'outil de ligne de commande sqlite3. Pour le faire en Python, vous devez simplement charger les données en utilisant toutes les fonctionnalités de Python, telles que le module csv , et en insérant les données comme d'habitude.

De cette façon, vous avez également le contrôle sur les types insérés, plutôt que de vous fier au comportement apparemment non documenté de sqlite3.

Marcelo Cantos
la source
1
Il n'est pas nécessaire de préparer l'insert. La source des instructions SQL et des résultats compilés sont conservés dans un cache.
John Machin
@John Machin: Y a-t-il un lien vers comment SQLite fait cela?
Marcelo Cantos
@Marcelo: Si vous êtes intéressé par COMMENT c'est fait (pourquoi?), Regardez dans la source sqlite ou demandez sur la liste de diffusion sqlite.
John Machin
@John Machin: Je suis intéressé parce que dans toute la documentation SQLite que j'ai rencontrée, il n'y a pas un seul mot sur la mise en cache automatique des déclarations non préparées. Je ne pense pas qu'il soit raisonnable d'avoir à lire le code source ou à tester les listes de diffusion pour découvrir quelque chose d'aussi basique que de savoir si je dois préparer mes instructions SQL ou non. Quelle est votre source d'information à ce sujet?
Marcelo Cantos
4
@Marcelo: En fait, c'est fait dans le module wrapper Python sqlite3. docs.python.org/library/… dit "" "Le module sqlite3 utilise en interne un cache d'instructions pour éviter la surcharge d'analyse SQL. Si vous souhaitez définir explicitement le nombre d'instructions mises en cache pour la connexion, vous pouvez définir le paramètre cached_statements . La valeur par défaut actuellement implémentée est de mettre en cache 100 instructions. "" "
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()
Christophe
la source
9

Merci beaucoup pour la réponse de Bernie ! J'ai dû le peaufiner un peu - voici ce qui a fonctionné pour moi:

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

Mon fichier texte (PC.txt) ressemble à ceci:

1 | Term 1 | Definition 1
2 | Term 2 | Definition 2
3 | Term 3 | Definition 3
jiy
la source
7

Vous avez raison, c'est .importla voie à suivre, mais c'est une commande du shell SQLite3.exe. Un grand nombre des principales réponses à cette question impliquent des boucles python natives, mais si vos fichiers sont volumineux (les miens sont de 10 ^ 6 à 10 ^ 7 enregistrements), vous voulez éviter de tout lire dans des pandas ou d'utiliser une compréhension / boucle de liste python native (bien que je ne les ai pas chronométrés pour comparaison).

Pour les fichiers volumineux, je pense que la meilleure option est de créer la table vide à l'avance en utilisant sqlite3.execute("CREATE TABLE..."), de supprimer les en-têtes de vos fichiers CSV, puis de l'utiliser subprocess.run()pour exécuter la déclaration d'importation de sqlite. Puisque la dernière partie est, je crois, la plus pertinente, je vais commencer par là.

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)

Explication
À partir de la ligne de commande, la commande que vous recherchez est sqlite3 my.db -cmd ".mode csv" ".import file.csv table". subprocess.run()exécute un processus de ligne de commande. L'argument to subprocess.run()est une séquence de chaînes qui sont interprétées comme une commande suivie de tous ses arguments.

  • sqlite3 my.db ouvre la base de données
  • -cmdflag après que la base de données vous permet de transmettre plusieurs commandes de suivi au programme sqlite. Dans le shell, chaque commande doit être entre guillemets, mais ici, elles doivent juste être leur propre élément de la séquence
  • '.mode csv' fait ce que vous attendez
  • '.import '+str(csv_file).replace('\\','\\\\')+' <table_name>'est la commande d'importation.
    Malheureusement, puisque le sous-processus transmet toutes les suites à des -cmdchaînes entre guillemets, vous devez doubler vos barres obliques inverses si vous avez un chemin de répertoire Windows.

En-têtes de décapage

Pas vraiment le point principal de la question, mais voici ce que j'ai utilisé. Encore une fois, je ne voulais à aucun moment lire tous les fichiers en mémoire:

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

Basé sur la solution Guy L (Love it) mais peut gérer les champs échappés.

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
la source
4

Vous pouvez le faire en utilisant blazeet odoefficacement

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

Odo stockera le fichier csv dans data.db(base de données sqlite) sous le schémadata

Ou vous utilisez ododirectement, sans blaze. Dans les deux cas, c'est bien. Lisez cette documentation

Kathirmani Sukumar
la source
2
bz non défini: P
holms
et c'est probablement un paquet très ancien à cause de son erreur interne: AttributeError: l'objet 'SubDiGraph' n'a pas d'attribut 'edge'
holms
Obtention de la même erreur d'attribut: il semble qu'il y ait des commentaires sur GitHub pour cela, cependant
user791411
2

Si le fichier CSV doit être importé dans le cadre d'un programme python, pour plus de simplicité et d'efficacité, vous pouvez utiliser os.systemles lignes suggérées par ce qui suit:

import os

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

rc = os.system(cmd)

print(rc)

Le fait est qu'en spécifiant le nom de fichier de la base de données, les données seront automatiquement enregistrées, en supposant qu'il n'y ait aucune erreur de lecture.

de pointe
la source
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
la source
2
Veuillez formater votre code correctement et ajouter quelques explications
exécutable
1

dans un souci de simplicité, vous pouvez utiliser l'outil de ligne de commande sqlite3 du Makefile de votre projet.

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

make test.sql3crée ensuite la base de données sqlite à partir d'un fichier test.csv existant, avec une seule table "test". vous pouvez ensuite make test.dumpvérifier le contenu.

jcomeau_ictx
la source
1

J'ai constaté qu'il peut être nécessaire de fractionner le transfert de données du csv vers la base de données par morceaux pour ne pas manquer de mémoire. Cela peut être fait comme ceci:

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.
la source