Importar un archivo CSV en una tabla de base de datos sqlite3 usando Python

106

Tengo un archivo CSV y quiero importar este archivo de forma masiva a mi base de datos sqlite3 usando Python. el comando es ".importar .....". pero parece que no puede funcionar así. ¿Alguien puede darme un ejemplo de cómo hacerlo en sqlite3? Estoy usando Windows por si acaso. Gracias

Hossein
fuente
3
Proporcione el comando real que no funcionó y el mensaje de error real . "importar ...." podría ser cualquier cosa. "no puede funcionar" es demasiado vago para que podamos adivinarlo. Sin detalles, no podemos ayudar.
S.Lott
2
el comando real como dije es ".import" y dice error de sintaxis nuevo ".import"
Hossein
10
Publique el comando real en la pregunta. Publique el mensaje de error real en la pregunta. No agregue comentarios que simplemente repitan cosas. Actualice la pregunta con copiar y pegar lo que está haciendo en realidad.
S.Lott

Respuestas:

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()
carne_mecánica
fuente
4
En caso de que tuviera los mismos problemas que yo: asegúrese de cambiar col1 y col2 a los encabezados de columna en el archivo csv. Y cierre la conexión a la base de datos llamando a con.close () al final.
Jonas
1
Gracias, @Jonas. Publicación actualizada.
mechanical_meat
Sigo recibiendo not all arguments converted during string formattingcuando intento este método.
Whitecat
Probé este método, pero no me funciona. ¿Podría consultar mis conjuntos de datos aquí (son muy normales, excepto que algunas columnas tienen valores vacíos) e intentar importarlos con su código? stackoverflow.com/questions/46042623/…
user177196
2
Este código no está optimizado para archivos csv muy grandes (orden de GB)
Nisba
91

La creación de una conexión sqlite a un archivo en el disco se deja como un ejercicio para el lector ... pero ahora hay dos líneas posibles gracias a la biblioteca de pandas

df = pandas.read_csv(csvfile)
df.to_sql(table_name, conn, if_exists='append', index=False)
Tennessee Leeuwenburg
fuente
gracias. Tengo un problema con Panda. mi csv está delimitado por ';' y tener ',' en las entradas. panda da error en read_csv. alguna configuración para leer las entradas con comas sin reemplazar temporalmente?
Alexei Martianov
3
use sep = ';'. La documentación de los pandas describe claramente cómo lidiar con esto.
Tennessee Leeuwenburg
3
¿Hay alguna manera de usar pandas pero sin usar la RAM? Tengo un enorme .csv (7gb) que no puedo importar como un marco de datos y luego adjunto a la base de datos.
Pablo
1
Sí, hay un método en pandas que leerá en trozos en lugar de todos a la vez. Me temo que no puedo recordar exactamente lo que tengo en la cabeza. Creo que agrega chunksize = <number_of_rows>, y luego obtiene un iterador que luego puede usar para agregar a una base de datos por partes. Avísame si tienes problemas para encontrarlo y puedo buscar una receta.
Tennessee Leeuwenburg
1
Muy bien, @TennesseeLeeuwenburg. No necesitaba, dfasí que acorté su ejemplo a:pandas.read_csv(csvfile).to_sql(table_name, conn, if_exists='append', index=False)
keithpjolley
13

Mis 2 centavos (más genérico):

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
fuente
1
if len (feildslLeft)> 0: siempre es cierto, por lo que se genera una excepción. Revise y corrija esto.
amu61
¿Alguna forma de hacer esto sin tener que fseek (), para que pueda usarse en transmisiones?
mwag
1
@mwag, puede omitir la verificación del tipo de columna e importar las columnas como texto.
user5359531
12

El .importcomando es una característica de la herramienta de línea de comandos sqlite3. Para hacerlo en Python, simplemente debe cargar los datos utilizando las instalaciones que Python tenga, como el módulo csv , e insertando los datos como de costumbre.

De esta manera, también tiene control sobre qué tipos se insertan, en lugar de depender del comportamiento aparentemente indocumentado de sqlite3.

Marcelo Cantos
fuente
1
No es necesario preparar el inserto. La fuente de las sentencias SQL y los resultados compilados se guardan en una caché.
John Machin
@John Machin: ¿Hay un enlace a cómo SQLite hace esto?
Marcelo Cantos
@Marcelo: Si está interesado en CÓMO se hace (¿por qué?), Busque en la fuente de sqlite o pregunte en la lista de correo de sqlite.
John Machin
@John Machin: Estoy interesado porque en toda la documentación de SQLite con la que me he encontrado, no hay una sola palabra sobre el almacenamiento en caché automático de declaraciones no preparadas. No creo que sea razonable tener que leer el código fuente o sondear listas de correo para descubrir algo tan básico como si debería preparar mis declaraciones SQL o no. ¿Cuál es su fuente de información sobre esto?
Marcelo Cantos
4
@Marcelo: En realidad se hace en el módulo contenedor de Python sqlite3. docs.python.org/library/… dice "" "El módulo sqlite3 utiliza internamente una caché de instrucciones para evitar la sobrecarga de análisis de SQL. Si desea establecer explícitamente la cantidad de declaraciones que se almacenan en caché para la conexión, puede establecer el parámetro cached_statements . El valor predeterminado actualmente implementado es almacenar en caché 100 declaraciones. "" "
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
fuente
9

¡Muchas gracias por la respuesta de Bernie ! Tuve que modificarlo un poco, esto es lo que funcionó para mí:

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

Mi archivo de texto (PC.txt) se ve así:

1 | Term 1 | Definition 1
2 | Term 2 | Definition 2
3 | Term 3 | Definition 3
jiy
fuente
7

Tiene razón, ese .importes el camino a seguir, pero ese es un comando del shell SQLite3.exe. Muchas de las respuestas principales a esta pregunta involucran bucles de Python nativos, pero si sus archivos son grandes (los míos son de 10 ^ 6 a 10 ^ 7 registros), desea evitar leer todo en pandas o usar un bucle / comprensión de lista de Python nativo (aunque no los cronometré para compararlos).

Para archivos grandes, creo que la mejor opción es crear la tabla vacía de antemano usando sqlite3.execute("CREATE TABLE..."), quitar los encabezados de sus archivos CSV y luego usar subprocess.run()para ejecutar la declaración de importación de sqlite. Dado que la última parte es, creo, la más pertinente, comenzaré con eso.

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)

Explicación
Desde la línea de comandos, el comando que está buscando es sqlite3 my.db -cmd ".mode csv" ".import file.csv table". subprocess.run()ejecuta un proceso de línea de comandos. El argumento de subprocess.run()es una secuencia de cadenas que se interpretan como un comando seguido de todos sus argumentos.

  • sqlite3 my.db abre la base de datos
  • -cmdflag después de que la base de datos le permite pasar múltiples comandos de seguimiento al programa sqlite. En el shell, cada comando debe estar entre comillas, pero aquí, solo deben ser su propio elemento de la secuencia.
  • '.mode csv' hace lo que esperabas
  • '.import '+str(csv_file).replace('\\','\\\\')+' <table_name>'es el comando de importación.
    Desafortunadamente, dado que el subproceso pasa todos los seguimientos -cmdcomo cadenas entre comillas, debe duplicar sus barras diagonales inversas si tiene una ruta de directorio de Windows.

Eliminación de encabezados

No es realmente el punto principal de la pregunta, pero esto es lo que usé. Nuevamente, no quería leer todos los archivos en la memoria en ningún momento:

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

Basado en la solución Guy L (Me encanta) pero puede manejar campos de 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()
Jace
fuente
4

Puedes hacer esto usando blaze& odoeficientemente

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

Odo almacenará el archivo csv en data.db(base de datos sqlite) bajo el esquemadata

O lo usa ododirectamente, sin blaze. De cualquier manera está bien. Leer esta documentación

Kathirmani Sukumar
fuente
2
bz no definido: P
holms
y probablemente sea un paquete muy antiguo debido a su error interno: AttributeError: el objeto 'SubDiGraph' no tiene atributo 'edge'
holms
También se obtiene el mismo error de atributo: parece que hay comentarios en GitHub para ello, aunque
user791411
2

Si el archivo CSV debe importarse como parte de un programa de Python, entonces, por simplicidad y eficiencia, puede usarlo os.systemsiguiendo las líneas sugeridas por lo siguiente:

import os

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

rc = os.system(cmd)

print(rc)

El punto es que al especificar el nombre de archivo de la base de datos, los datos se guardarán automáticamente, asumiendo que no hay errores al leerlos.

pico
fuente
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
fuente
2
Formatee su código correctamente y agregue alguna explicación
ejecutable el
1

En aras de la simplicidad, puede utilizar la herramienta de línea de comandos sqlite3 del Makefile de su proyecto.

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

make test.sql3luego crea la base de datos sqlite a partir de un archivo test.csv existente, con una única tabla "prueba". A continuación, puede make test.dumpverificar el contenido.

jcomeau_ictx
fuente
1

Descubrí que puede ser necesario dividir la transferencia de datos desde el csv a la base de datos en trozos para no quedarse sin memoria. Esto se puede hacer así:

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.
fuente