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

Hossein
fonte
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()
carne_mecânica
fonte
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)
Tennessee Leeuwenburg
fonte
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
Guy L
fonte
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.

Marcelo Cantos
fonte
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()
Christopher
fonte
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
jiy
fonte
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)
Jake Stevens-Haas
fonte
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()
Jace
fonte
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

Kathirmani Sukumar
fonte
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.

pico
fonte
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
fonte
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.

jcomeau_ictx
fonte
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)
Peter H.
fonte