Python kullanarak bir CSV dosyasını bir sqlite3 veritabanı tablosuna aktarmak


106

Bir CSV dosyam var ve bu dosyayı Python kullanarak sqlite3 veritabanıma toplu olarak aktarmak istiyorum. komut ".import ....." şeklindedir. ama böyle çalışamayacak gibi görünüyor. Sqlite3'te nasıl yapılacağına dair bir örnek verebilir misiniz? Her ihtimale karşı pencereleri kullanıyorum. Teşekkürler


3
Lütfen çalışmayan gerçek komutu ve gerçek hata mesajını sağlayın . "içe aktar ..." herhangi bir şey olabilir. "çalışamaz", tahmin edemeyeceğimiz kadar belirsizdir. Ayrıntılar olmadan yardım edemeyiz.
S.Lott

3
dediğim gibi asıl komut ".import" ve sözdizimi hatası yeni ".import" diyor
Hossein

10
Lütfen soruya asıl komutu gönderin. Lütfen soruda gerçek hata mesajını gönderiniz. Lütfen sadece tekrar eden yorumlar eklemeyin. Lütfen soruyu gerçekte yaptığınız şeyi kopyalayıp yapıştırarak güncelleyin.
S.Lott

Yanıtlar:


135
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
Benim yaşadığım sorunların aynısına sahipseniz: col1 ve col2'yi csv dosyasındaki sütun başlıklarına değiştirdiğinizden emin olun. Ve sonunda con.close () 'u çağırarak veritabanına olan bağlantıyı kapatın.
Jonas

1
Teşekkürler @Jonas. Güncellenmiş yayın.
mechanical_meat

not all arguments converted during string formattingBu yöntemi denediğimde almaya devam ediyorum.
Whitecat

Bu yöntemi denedim ama benim için çalışmıyor. Veri kümelerime buradan göz atabilir misiniz (bazı sütunlarda boş değerler olması dışında çok normaldirler) ve bunları kodunuzla içe aktarmayı deneyebilir misiniz? stackoverflow.com/questions/46042623/…
user177196

2
Bu kod, çok büyük csv dosyaları için optimize edilmemiştir (
GB'lerin

92

Diskteki bir dosyaya bir sqlite bağlantısı oluşturmak okuyucuya bir egzersiz olarak bırakılmıştır ... ancak artık pandas kitaplığı tarafından mümkün kılınan iki satırlı bir vardır

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

teşekkür ederim. Panda ile bir sorunum var. csv'm ';' ile sınırlandırılmıştır. ve girişlerde "," var. panda read_csv'de hata veriyor. girişleri geçici olarak değiştirmeden virgülle okumak için herhangi bir ayar var mı?
Alexei Martianov

3
sep = ';' kullanın. Pandaların belgeleri, bununla nasıl başa çıkılacağını açıkça ortaya koyuyor.
Tennessee Leeuwenburg

3
Pandaları kullanmanın bir yolu var, ancak RAM kullanmadan mı? Büyük bir .csv (7gb) var, veri çerçevesi olarak içe aktaramıyorum ve ardından DB'ye ekledim.
Pablo

1
Evet, pandalarda hepsini bir kerede okumak yerine parçalar halinde okuyacak bir yöntem var. Korkarım tam olarak kafamın tepesini hatırlayamıyorum. Sanırım chunksize = <number_of_rows> ekliyorsunuz ve sonra bir yineleyici geri alıyorsunuz, bu da daha sonra bir veritabanına parça parça eklemek için kullanabiliyorsunuz. Bulmakta zorlanırsan bana haber ver, bir tarif bulabilirim.
Tennessee Leeuwenburg

1
Çok güzel @TennesseeLeeuwenburg. Buna ihtiyacım yoktu, dfbu yüzden örneğinizi kısalttım:pandas.read_csv(csvfile).to_sql(table_name, conn, if_exists='append', index=False)
keithpjolley

13

2 sentim (daha genel):

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
eğer len (feildslLeft)> 0: her zaman doğrudur, bu yüzden bir istisna yaratır. Lütfen bunu inceleyin ve düzeltin.
amu61

Akışlarda kullanılabilmesi için bunu fseek () yapmadan yapmanın herhangi bir yolu var mı?
mwag

1
@mwag sütun türü kontrolünü atlayabilir ve sütunların tümünü metin olarak içe aktarabilirsiniz.
user5359531

12

.importKomut Sqlite3 komut satırı aracı bir özelliktir. Bunu Python'da yapmak için, verileri Python'un sahip olduğu csv modülü gibi özellikleri kullanarak yüklemeniz ve verileri her zamanki gibi eklemeniz yeterlidir .

Bu şekilde, sqlite3'ün görünüşte belgelenmemiş davranışına güvenmek yerine, hangi türlerin eklendiğini kontrol edebilirsiniz.


1
İnsertin hazırlanmasına gerek yoktur. SQL deyimlerinin ve derlenen sonuçların kaynağı bir önbellekte tutulur.
John Machin

@John Machin: SQLite'ın bunu nasıl yaptığına dair bir bağlantı var mı?
Marcelo Cantos

@Marcelo: NASIL yapıldığıyla ilgileniyorsanız (neden?), Sqlite kaynağına bakın veya sqlite mail listesinden sorun.
John Machin

@John Machin: Karşılaştığım tüm SQLite belgelerinde, hazırlıksız ifadelerin otomatik olarak önbelleğe alınmasıyla ilgili tek bir kelime olmadığı için ilgileniyorum. SQL ifadelerimi hazırlamam gerekip gerekmediği gibi basit bir şey keşfetmek için kaynak kodunu okumanın veya posta listelerini araştırmanın makul olduğunu düşünmüyorum. Bununla ilgili bilgi kaynağınız nedir?
Marcelo Cantos

4
@Marcelo: Aslında Python sqlite3 sarmalayıcı modülünde yapılır. docs.python.org/library/… "" "diyor sqlite3 modülü, SQL'in ek yükünü ayrıştırmaktan kaçınmak için dahili olarak bir ifade önbelleği kullanır. Bağlantı için önbelleğe alınan ifadelerin sayısını açıkça belirlemek istiyorsanız, cached_statements parametresini ayarlayabilirsiniz . Şu anda uygulanan varsayılan 100 ifadeyi önbelleğe
almaktır

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

Bernie'nin cevabı için çok teşekkürler ! Biraz ince ayar yapmak zorunda kaldı - işte benim için işe yarayan şey:

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

Metin dosyam (PC.txt) şöyle görünüyor:

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

7

Haklısın .import, gitmenin yolu bu, ama bu SQLite3.exe kabuğundan bir komut. Bu sorunun en iyi yanıtlarının çoğu yerel python döngülerini içerir, ancak dosyalarınız büyükse (benimki 10 ^ 6 ila 10 ^ 7 kayıtsa), her şeyi pandalarda okumaktan veya yerel bir python listesi anlama / döngüsü kullanmaktan kaçınmak istersiniz. (karşılaştırma için zamanlamamama rağmen).

Büyük dosyalar için, en iyi seçeneğin boş tabloyu kullanarak önceden oluşturmak, sqlite3.execute("CREATE TABLE...")CSV dosyalarınızdan başlıkları subprocess.run()çıkarmak ve ardından sqlite'ın import ifadesini çalıştırmak için kullanmak olduğuna inanıyorum . Son kısım olduğundan en uygun olduğuna inanıyorum, bununla başlayacağım.

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)

Açıklama
Komut satırından aradığınız komut şudur sqlite3 my.db -cmd ".mode csv" ".import file.csv table". subprocess.run()bir komut satırı işlemi çalıştırır. Argüman subprocess.run(), bir komut olarak yorumlanan ve ardından tüm argümanlarının izlediği bir dizeler dizisidir.

  • sqlite3 my.db veritabanını açar
  • -cmdVeritabanından sonra gelen flag, sqlite programına birden fazla follow on komutunu aktarmanıza izin verir. Kabukta, her komutun tırnak içinde olması gerekir, ancak burada, sıranın kendi öğesi olmaları gerekir.
  • '.mode csv' beklediğini yapar
  • '.import '+str(csv_file).replace('\\','\\\\')+' <table_name>'içe aktarma komutudur.
    Ne yazık ki, alt süreç tüm takipleri -cmdalıntı dizeleri olarak aktardığından, bir Windows dizin yolunuz varsa ters eğik çizgileri ikiye katlamanız gerekir.

Sıyırma Başlıkları

Aslında sorunun ana noktası değil, ama işte kullandım. Yine, tüm dosyaları hiçbir noktada belleğe okumak istemedim:

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

4

Guy L çözümüne (Sevdim) dayalıdır, ancak çıkış yapılmış alanları işleyebilir.

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

Bunu kullanarak blazeve odoverimli bir şekilde yapabilirsiniz

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

Odo, csv dosyasını data.dbşema altında (sqlite veritabanı) olarak saklayacaktır.data

Ya da ododoğrudan kullanmadan kullanıyorsunuz blaze. Her iki yol da iyidir. Bu dokümantasyonu okuyun


2
bz tanımlanmamış: P
holms

ve muhtemelen iç hatası nedeniyle çok eski bir pakettir: AttributeError: 'SubDiGraph' nesnesinin 'edge' niteliği yoktur
holms

Ayrıca aynı öznitelik hatası alıyorum: GitHub'da bunun için yorumlar var gibi görünüyor
user791411

2

CSV dosyasının bir python programının parçası olarak içe aktarılması gerekiyorsa, basitlik ve verimlilik os.systemiçin aşağıdakilerin önerdiği satırlar boyunca kullanabilirsiniz :

import os

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

rc = os.system(cmd)

print(rc)

Buradaki nokta, veritabanının dosya adını belirterek, okunurken herhangi bir hata olmadığı varsayılarak verilerin otomatik olarak kaydedilmesidir.


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
Lütfen kodunuzu doğru bir şekilde biçimlendirin ve bir açıklama ekleyin
çalıştırılabilir

1

Basitlik açısından, projenizin Makefile dosyasındaki sqlite3 komut satırı aracını kullanabilirsiniz.

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

make test.sql3daha sonra var olan bir test.csv dosyasından tek bir "test" tablosu ile sqlite veritabanını oluşturur. daha sonra make test.dumpiçeriği doğrulayabilirsiniz.


1

Hafızanın bitmemesi için csv'den veritabanına veri aktarımını parçalar halinde bölmenin gerekli olabileceğini buldum. Bu şu şekilde yapılabilir:

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.