Mengimpor file CSV ke dalam tabel database sqlite3 menggunakan Python

106

Saya memiliki file CSV dan saya ingin mengimpor file ini secara massal ke database sqlite3 saya menggunakan Python. perintahnya adalah ".import .....". tapi sepertinya tidak bisa bekerja seperti ini. Adakah yang bisa memberi saya contoh bagaimana melakukannya di sqlite3? Saya menggunakan windows untuk berjaga-jaga. Terima kasih

Hossein
sumber
3
Berikan perintah sebenarnya yang tidak berfungsi dan pesan kesalahan yang sebenarnya . "import ...." bisa apa saja. "tidak bisa bekerja" terlalu kabur untuk kita tebak. Tanpa detail, kami tidak dapat membantu.
S. Lotot
2
perintah sebenarnya seperti yang saya katakan adalah ".import" dan dikatakan kesalahan sintaks baru ".import"
Hossein
10
Harap benar-benar memposting perintah sebenarnya dalam pertanyaan. Harap benar-benar memposting pesan kesalahan sebenarnya dalam pertanyaan. Tolong jangan menambahkan komentar yang hanya mengulangi sesuatu. Harap perbarui pertanyaan dengan salin dan tempel sebenarnya dari apa yang sebenarnya Anda lakukan.
S. Lotot

Jawaban:

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()
mechanical_meat
sumber
4
Jika Anda memiliki masalah yang sama dengan yang saya alami: Pastikan untuk mengubah col1 dan col2 ke tajuk kolom di file csv. Dan tutup koneksi ke database dengan memanggil con.close () di akhir.
Jonas
1
Terima kasih, @Jonas. Posting yang diperbarui.
mechanical_meat
Saya terus mendapatkan not all arguments converted during string formattingketika saya mencoba metode ini.
Whitecat
Saya mencoba metode ini, tetapi tidak berhasil untuk saya. Bisakah Anda memeriksa kumpulan data saya di sini (sangat normal, kecuali beberapa kolom memiliki nilai kosong) dan mencoba mengimpornya dengan kode Anda? stackoverflow.com/questions/46042623/…
pengguna177196
2
Kode ini tidak dioptimalkan untuk file csv yang sangat besar (urutan GB)
Nisba
91

Membuat koneksi sqlite ke file pada disk dibiarkan sebagai latihan bagi pembaca ... tetapi sekarang ada dua baris yang dimungkinkan oleh pustaka pandas

df = pandas.read_csv(csvfile)
df.to_sql(table_name, conn, if_exists='append', index=False)
Tennessee Leeuwenburg
sumber
Terima kasih. Saya punya masalah dengan panda. csv saya dibatasi oleh ';' dan memiliki ',' di entri. panda memberikan kesalahan pada read_csv. ada pengaturan untuk membaca entri dengan koma tanpa mengganti sementara?
Alexei Martianov
3
gunakan sep = ';'. Dokumentasi panda dengan jelas menguraikan cara mengatasinya.
Tennessee Leeuwenburg
3
apakah ada cara untuk menggunakan panda tetapi tanpa menggunakan RAM ?, saya memiliki .csv besar (7gb) saya tidak dapat mengimpor sebagai bingkai data dan kemudian ditambahkan ke DB.
Pablo
1
Ya, ada metode di panda yang akan terbaca dalam beberapa bagian daripada sekaligus. Saya khawatir saya tidak dapat mengingat dengan tepat di atas kepala saya. Saya pikir Anda menambahkan chunksize = <number_of_rows>, dan kemudian Anda mendapatkan kembali iterator yang kemudian dapat Anda gunakan untuk menambahkan ke database sedikit demi sedikit. Beri tahu saya jika Anda kesulitan menemukannya dan saya bisa mencari tahu resepnya.
Tennessee Leeuwenburg
1
Bagus sekali, @TennesseeLeeuwenburg. Saya tidak membutuhkannya, dfjadi saya mempersingkat contoh Anda menjadi:pandas.read_csv(csvfile).to_sql(table_name, conn, if_exists='append', index=False)
keithpjolley
13

2 sen saya (lebih umum):

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
sumber
1
if len (feildslLeft)> 0: selalu benar, jadi buat pengecualian. Harap tinjau dan perbaiki ini.
amu61
Adakah cara untuk melakukan ini tanpa harus fseek (), sehingga dapat digunakan di streaming?
mwag
1
@mwag Anda dapat melewati pemeriksaan jenis kolom dan mengimpor semua kolom sebagai teks.
pengguna5359531
12

The .importperintah adalah fitur dari alat sqlite3 baris perintah. Untuk melakukannya dengan Python, Anda cukup memuat data menggunakan fasilitas apa pun yang dimiliki Python, seperti modul csv , dan memasukkan data seperti biasa.

Dengan cara ini, Anda juga memiliki kendali atas tipe apa yang disisipkan, daripada mengandalkan perilaku sqlite3 yang tampaknya tidak terdokumentasi.

Marcelo Cantos
sumber
1
Tidak perlu menyiapkan sisipan. Sumber pernyataan SQL dan hasil yang dikompilasi disimpan dalam cache.
John Machin
@ John Machin: Apakah ada tautan ke cara SQLite melakukan ini?
Marcelo Cantos
@Marcelo: Jika Anda tertarik dengan BAGAIMANA hal ini dilakukan (mengapa?), Lihat di sumber sqlite atau tanyakan di milis sqlite.
John Machin
@ John Machin: Saya tertarik karena di semua dokumentasi SQLite yang saya temukan, tidak ada satu kata pun tentang cache otomatis untuk pernyataan yang tidak disiapkan. Menurut saya tidak masuk akal untuk harus membaca kode sumber atau menyelidiki milis untuk menemukan sesuatu yang mendasar seperti apakah saya harus menyiapkan pernyataan SQL saya atau tidak. Apa sumber informasi Anda tentang ini?
Marcelo Cantos
4
@Marcelo: Sebenarnya ini dilakukan dalam modul pembungkus sqlite3 Python. docs.python.org/library/… mengatakan "" "Modul sqlite3 secara internal menggunakan cache pernyataan untuk menghindari overhead parsing SQL. Jika Anda ingin secara eksplisit mengatur jumlah pernyataan yang di-cache untuk koneksi, Anda dapat mengatur parameter cached_statements . Default yang diterapkan saat ini adalah menyimpan 100 pernyataan. "" "
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
sumber
9

Terima kasih banyak atas jawaban bernie ! Harus sedikit mengubahnya - inilah yang berhasil untuk saya:

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

File teks saya (PC.txt) terlihat seperti ini:

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

Anda benar begitulah .importcaranya, tapi itu adalah perintah dari shell SQLite3.exe. Banyak jawaban teratas untuk pertanyaan ini melibatkan loop python asli, tetapi jika file Anda besar (milik saya adalah 10 ^ 6 hingga 10 ^ 7 catatan), Anda ingin menghindari membaca semuanya ke dalam panda atau menggunakan pemahaman / loop daftar python asli (meskipun saya tidak mengatur waktunya untuk perbandingan).

Untuk file besar, saya yakin pilihan terbaik adalah membuat tabel kosong terlebih dahulu menggunakan sqlite3.execute("CREATE TABLE..."), menghapus header dari file CSV Anda, dan kemudian gunakan subprocess.run()untuk menjalankan pernyataan impor sqlite. Karena bagian terakhir adalah saya percaya yang paling relevan, saya akan mulai dengan itu.

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)

Penjelasan
Dari baris perintah, perintah yang Anda cari adalah sqlite3 my.db -cmd ".mode csv" ".import file.csv table". subprocess.run()menjalankan proses baris perintah. Argumen ke subprocess.run()adalah urutan string yang ditafsirkan sebagai perintah yang diikuti oleh semua argumennya.

  • sqlite3 my.db membuka database
  • -cmdbendera setelah database memungkinkan Anda untuk meneruskan beberapa perintah ikuti ke program sqlite. Di shell, setiap perintah harus dalam tanda kutip, tetapi di sini, perintah hanya perlu menjadi elemen urutannya sendiri
  • '.mode csv' melakukan apa yang Anda harapkan
  • '.import '+str(csv_file).replace('\\','\\\\')+' <table_name>'adalah perintah impor.
    Sayangnya, karena subproses meneruskan semua tindak lanjut ke -cmdstring yang dikutip, Anda perlu menggandakan garis miring terbalik jika Anda memiliki jalur direktori windows.

Stripping Header

Bukan poin utama dari pertanyaan itu, tapi inilah yang saya gunakan. Sekali lagi, saya tidak ingin membaca seluruh file ke dalam memori kapan pun:

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

Berdasarkan solusi Guy L (Love it) tetapi dapat menangani bidang yang lolos.

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
sumber
4

Anda dapat melakukan ini menggunakan blaze& odoefisien

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

Odo akan menyimpan file csv ke data.db(database sqlite) di bawah skemadata

Atau Anda gunakan odosecara langsung, tanpa blaze. Apa pun itu baik-baik saja. Baca dokumentasi ini

Kathirmani Sukumar
sumber
2
bz tidak ditentukan: P
holms
dan itu mungkin paket yang sangat lama karena kesalahan dalam dirinya: AttributeError: Objek 'SubDiGraph' tidak memiliki atribut 'edge'
holms
Juga mendapatkan kesalahan atribut yang sama: tampaknya ada komentar di GitHub untuk itu, meskipun
pengguna791411
2

Jika file CSV harus diimpor sebagai bagian dari program python, maka untuk kesederhanaan dan efisiensi, Anda dapat menggunakan os.systemseperti yang disarankan oleh berikut ini:

import os

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

rc = os.system(cmd)

print(rc)

Intinya adalah dengan menentukan nama file dari database, maka data secara otomatis akan tersimpan, dengan asumsi tidak ada kesalahan saat membacanya.

puncak
sumber
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
sumber
2
Harap format kode Anda dengan benar dan tambahkan beberapa penjelasan
dapat dijalankan
1

demi kesederhanaan, Anda dapat menggunakan alat baris perintah sqlite3 dari Makefile proyek Anda.

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

make test.sql3kemudian membuat database sqlite dari file test.csv yang ada, dengan satu tabel "test". Anda kemudian make test.dumpdapat memverifikasi isinya.

jcomeau_ictx
sumber
1

Saya telah menemukan bahwa perlu untuk memecah transfer data dari csv ke database dalam potongan-potongan agar tidak kehabisan memori. Ini bisa dilakukan seperti ini:

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