Importando um arquivo CSV para uma tabela de banco de dados sqlite3 usando Python


106

Eu tenho um arquivo CSV e quero importar em massa esse arquivo para meu banco de dados sqlite3 usando Python. o comando é ".import .....". mas parece que não pode funcionar assim. Alguém pode me dar um exemplo de como fazer isso no sqlite3? Estou usando o Windows apenas no caso. obrigado


3
Forneça o comando real que não funcionou e a mensagem de erro real . "import ...." pode ser qualquer coisa. "não pode funcionar" é muito vago para adivinharmos. Sem detalhes, não podemos ajudar.
— S.Lott

2
o comando real como eu disse é ".import" e diz erro de sintaxe novo ".import"
— Hossein

10
Por favor, poste o comando real na questão. Por favor, poste a mensagem de erro real na pergunta. Por favor, não adicione comentários que simplesmente repetem coisas. Atualize a pergunta com copiar e colar o que você está realmente fazendo.
— S.Lott

Respostas:


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
Caso você tenha os mesmos problemas que eu: Certifique-se de alterar col1 e col2 para os cabeçalhos das colunas no arquivo csv. E feche a conexão com o banco de dados chamando con.close () no final.
— Jonas

1
Obrigado, @Jonas. Postagem atualizada.
— Mechanical_meat

Eu continuo entendendo not all arguments converted during string formattingquando tento esse método.
— Whitecat 01 de

Tentei esse método, mas não funciona para mim. Você poderia verificar meus conjuntos de dados aqui (eles são muito normais, exceto algumas colunas têm valores vazios) e tentar importá-los com seu código? stackoverflow.com/questions/46042623/…
— user177196

2
Este código não é otimizado para arquivos csv muito grandes (ordem dos GBs)
— Nisba

91

Criar uma conexão sqlite com um arquivo no disco é deixado como um exercício para o leitor ... mas agora existe um two-liner possibilitado pela biblioteca pandas

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

obrigado. Eu tenho um problema com o panda. meu csv é delimitado por ';' e têm ',' nas entradas. panda dá erro em read_csv. qualquer configuração para ler entradas com vírgulas sem substituir temporariamente?
— Alexei Martianov

3
use sep = ';'. A documentação do pandas descreve claramente como lidar com isso.
— Tennessee Leeuwenburg

3
existe uma maneira de usar o pandas, mas sem usar a RAM ?, eu tenho um enorme .csv (7 gb) que não consigo importar como um dataframe e depois anexado ao banco de dados.
— Pablo

1
Sim, existe um método nos pandas que lê em pedaços, em vez de todos de uma vez. Receio não conseguir lembrar exatamente de início. Acho que você adiciona chunksize = <number_of_rows> e, em seguida, obtém de volta um iterador que pode ser usado para anexar a um banco de dados por partes. Avise-me se tiver problemas para encontrar e eu poderei encontrar uma receita.
— Tennessee Leeuwenburg

1
Muito bom, @TennesseeLeeuwenburg. Eu não precisava, dfentão encurtei seu exemplo para:pandas.read_csv(csvfile).to_sql(table_name, conn, if_exists='append', index=False)
— keithpjolley

13

Meus 2 centavos (mais 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

1
if len (feildslLeft)> 0: sempre verdadeiro, gerando uma exceção. Por favor, revise e corrija isso.
— amu61

Alguma maneira de fazer isso sem ter que fseek (), para que possa ser usado em streams?
— mwag de

1
@mwag você pode simplesmente pular a verificação do tipo de coluna e importar todas as colunas como texto.
— user5359531

12

O .importcomando é um recurso da ferramenta de linha de comando sqlite3. Para fazer isso em Python, você deve simplesmente carregar os dados usando quaisquer recursos que o Python tenha, como o módulo csv , e inserir os dados normalmente.

Dessa forma, você também tem controle sobre quais tipos são inseridos, ao invés de confiar no comportamento aparentemente não documentado do sqlite3.


1
Não há necessidade de preparar o encarte. A origem das instruções SQL e dos resultados compilados são mantidos em um cache.
— John Machin

@John Machin: Existe um link para saber como o SQLite faz isso?
— Marcelo Cantos

@Marcelo: Se você está interessado em COMO isso é feito (por quê?), Olhe no código-fonte do sqlite ou pergunte na lista de discussão do sqlite.
— John Machin

@John Machin: Estou interessado porque em toda a documentação SQLite que encontrei, não existe uma única palavra sobre o cache automático de instruções não preparadas. Não acho que seja razoável ter que ler o código-fonte ou sondar as listas de discussão para descobrir algo tão básico como se devo preparar minhas instruções SQL ou não. Qual é a sua fonte de informação sobre isso?
— Marcelo Cantos

4
@Marcelo: Na verdade, é feito no módulo wrapper do Python sqlite3. docs.python.org/library/… diz "" "O módulo sqlite3 usa internamente um cache de instrução para evitar sobrecarga de análise SQL. Se você deseja definir explicitamente o número de instruções que são armazenadas em cache para a conexão, você pode definir o parâmetro cached_statements . O padrão atualmente implementado é armazenar em cache 100 instruções. "" "
— 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

Muito obrigado pela resposta de bernie ! Tive que ajustar um pouco - aqui está o que funcionou para mim:

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

Meu arquivo de texto (PC.txt) tem a seguinte aparência:

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

7

Você está certo, esse .importé o caminho a seguir, mas esse é um comando do shell SQLite3.exe. Muitas das principais respostas a esta pergunta envolvem loops nativos do python, mas se seus arquivos forem grandes (os meus são de 10 ^ 6 a 10 ^ 7 registros), você deve evitar ler tudo nos pandas ou usar uma compreensão / loop de lista nativa do python (embora eu não os tenha cronometrado para comparação).

Para arquivos grandes, acredito que a melhor opção é criar a tabela vazia com antecedência usando sqlite3.execute("CREATE TABLE..."), retirar os cabeçalhos de seus arquivos CSV e, em seguida, usar subprocess.run()para executar a instrução de importação do sqlite. Já que a última parte é, creio, a mais pertinente, começarei por aí.

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)

Explicação
Na linha de comando, o comando que você está procurando é sqlite3 my.db -cmd ".mode csv" ".import file.csv table". subprocess.run()executa um processo de linha de comando. O argumento para subprocess.run()é uma sequência de strings que são interpretadas como um comando seguido por todos os seus argumentos.

  • sqlite3 my.db abre o banco de dados
  • -cmdapós o banco de dados permitir que você passe vários comandos de acompanhamento para o programa sqlite. No shell, cada comando deve estar entre aspas, mas aqui, eles só precisam ser seus próprios elementos da sequência
  • '.mode csv' faz o que você esperaria
  • '.import '+str(csv_file).replace('\\','\\\\')+' <table_name>'é o comando de importação.
    Infelizmente, uma vez que o subprocesso passa todos os subprocessos -cmdcomo strings entre aspas, você precisa dobrar as barras invertidas se tiver um caminho de diretório do Windows.

Decapando Cabeçalhos

Não é realmente o ponto principal da pergunta, mas aqui está o que usei. Novamente, eu não queria ler todos os arquivos na memória em nenhum momento:

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

4

Baseado na solução Guy L (Love it), mas pode lidar com 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()

4

Você pode fazer isso usando blazee de forma odoeficiente

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

Odo irá armazenar o arquivo csv em data.db(banco de dados sqlite) sob o esquemadata

Ou você usa ododiretamente, sem blaze. Qualquer maneira está bem. Leia esta documentação


2
bz não definido: P
— holms

e é provavelmente um pacote muito antigo por causa de seu erro interno: AttributeError: objeto 'SubDiGraph' não tem atributo 'edge'
— holms

Também está recebendo o mesmo erro de atributo: parece que há comentários no GitHub sobre ele
— user791411

2

Se o arquivo CSV deve ser importado como parte de um programa Python, para simplicidade e eficiência, você pode usar os.systemas linhas sugeridas a seguir:

import os

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

rc = os.system(cmd)

print(rc)

A questão é que ao especificar o nome do arquivo do banco de dados, os dados serão salvos automaticamente, assumindo que não haja erros ao lê-los.


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
Formate seu código corretamente e adicione alguma explicação
— executável

1

por uma questão de simplicidade, você pode usar a ferramenta de linha de comando sqlite3 do Makefile do seu projeto.

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

make test.sql3em seguida, cria o banco de dados sqlite a partir de um arquivo test.csv existente, com uma única tabela "teste". você pode então make test.dumpverificar o conteúdo.


1

Descobri que pode ser necessário quebrar a transferência de dados do csv para o banco de dados em blocos para não ficar sem memória. Isso pode ser feito assim:

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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.