147 votes

Importer un fichier CSV dans une table de base de données sqlite3 en utilisant Python

J'ai un fichier CSV et je veux l'importer en masse dans ma base de données sqlite3 en utilisant Python. La commande est ".import .....". Mais il semble que cela ne puisse pas fonctionner ainsi. Quelqu'un peut-il me donner un exemple de comment le faire dans sqlite3 ? J'utilise Windows, au cas où. Merci

4 votes

Veuillez fournir le réel qui n'a pas fonctionné et la commande réel message d'erreur. "import...." peut être n'importe quoi. "cannot work" est trop vague pour que nous puissions le deviner. Sans détails, nous ne pouvons pas vous aider.

3 votes

La commande réelle comme je l'ai dit est ".import" et il dit erreur de syntaxe nouveau ".import".

14 votes

Veuillez afficher la commande réelle dans la question. Veuillez afficher le message d'erreur réel dans la question. Veuillez ne pas ajouter de commentaires qui ne font que répéter les choses. Veuillez mettre à jour la question avec un copier-coller de ce que vous faites réellement.

177voto

bernie Points 44206
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 votes

Au cas où vous auriez eu les mêmes problèmes que moi : Assurez-vous de changer col1 et col2 par les en-têtes de colonne dans le fichier csv. Et fermez la connexion à la base de données en appelant con.close() à la fin.

1 votes

Merci, @Jonas. Poste mis à jour.

0 votes

Je continue à avoir not all arguments converted during string formatting lorsque je tente cette méthode.

136voto

La création d'une connexion sqlite à un fichier sur le disque est laissée comme un exercice pour le lecteur ... mais il y a maintenant une double ligne rendue possible par la bibliothèque pandas.

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

0 votes

Merci. J'ai un problème avec panda. mon csv est délimité par ';' et a ',' dans les entrées. panda donne une erreur sur read_csv. y a-t-il un paramètre pour lire les entrées avec des virgules sans les remplacer temporairement ?

3 votes

Utilisez sep=';'. La documentation de pandas explique clairement comment gérer ce problème.

6 votes

Existe-t-il un moyen d'utiliser pandas sans utiliser la RAM ? j'ai un énorme .csv (7gb) que je ne peux pas importer en tant que dataframe et ensuite ajouter à la base de données.

34voto

Ranting_Raven Points 185

Vous avez raison. .import est la meilleure solution, mais il s'agit d'une commande du programme de ligne de commande SQLite3. Un grand nombre des meilleures réponses à cette question impliquent des boucles python natives, mais si vos fichiers sont volumineux (les miens comptent 10^6 à 10^7 enregistrements), vous voudrez éviter de tout lire dans pandas ou d'utiliser une compréhension de liste/boucle python native (bien que je ne les ai pas chronométrés pour comparaison).

Pour les gros fichiers, je pense que la meilleure option est d'utiliser subprocess.run() pour exécuter la commande d'importation de sqlite. Dans l'exemple ci-dessous, je suppose que la table existe déjà, mais le fichier csv a des en-têtes dans la première ligne. Voir .import docs pour plus d'informations.

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 --skip 1 ' + str(csv_file).replace('\\','\\\\')
                                 +' <table_name>'],
                        capture_output=True)

_note d'édition : les fonctions de sqlite3 .import a été améliorée de sorte qu'elle peut traiter la première rangée comme des noms d'en-tête ou même sauter la première x (nécessite une version >=3.32, comme indiqué dans la rubrique cette réponse . Si vous avez une ancienne version de sqlite3, vous devrez peut-être d'abord créer la table, puis supprimer la première ligne du csv avant de l'importer. Le site --skip 1 donnera une erreur avant la version 3.32._

Explication
Depuis 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 en ligne de commande. L'argument de subprocess.run() est une séquence de chaînes de caractères qui sont interprétées comme une commande suivie de tous ses arguments.

  • sqlite3 my.db ouvre la base de données
  • -cmd après la base de données vous permet de passer plusieurs commandes de suivi au programme sqlite. Dans l'interpréteur de commandes, chaque commande doit être entre guillemets, mais ici, elles doivent simplement être leur propre élément de la séquence.
  • '.mode csv' fait ce que l'on attend d'elle
  • '.import --skip 1'+str(csv_file).replace('\\','\\\\')+' <table_name>' est la commande d'importation.
    Malheureusement, puisque le sous-processus transmet tous les suivis à -cmd en tant que chaînes entre guillemets, vous devez doubler vos antislashs si vous avez un chemin de répertoire Windows.

Décapage des en-têtes

Ce n'est pas vraiment le point principal de la question, mais voici ce que j'ai utilisé. Encore une fois, je ne voulais pas lire les fichiers entiers en mémoire à un moment donné :

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

0 votes

Je n'ai pas pu faire --skip 1 fonctionne avec 3.32.3 et 3.36.0

0 votes

@roman à partir de la ligne de commande ou subprocess.run() ?

0 votes

En utilisant la ligne de commande

14voto

Guy L Points 614

Mes 2 centimes (plus génériques) :

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 votes

If len(feildslLeft) > 0 : toujours vrai, donc levée d'une exception . Veuillez revoir et corriger ceci.

0 votes

Y a-t-il un moyen de faire cela sans avoir à utiliser fseek(), afin de pouvoir l'utiliser sur des flux ?

1 votes

@mwag vous pouvez simplement ignorer la vérification du type de colonne et importer les colonnes en tant que texte.

13voto

Marcelo Cantos Points 91211

El .import est une fonctionnalité de l'outil de ligne de commande sqlite3. Pour le faire en Python, il suffit de charger les données à l'aide de toutes les possibilités offertes par Python, telles que la commande module csv et d'insérer les données comme d'habitude.

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

1 votes

Il n'est pas nécessaire de préparer l'insert. La source des instructions SQL et les résultats compilés sont conservés dans un cache.

0 votes

@John Machin : Existe-t-il un lien vers la façon dont SQLite procède ?

0 votes

@Marcelo : Si vous êtes intéressé par la façon dont c'est fait (pourquoi ?), regardez dans les sources de sqlite ou demandez sur la liste de diffusion sqlite.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X