Pětiminutovka: SQL nad csv

23. 2. 2014 21:59 Petr Blahoš

Nedávno jsem narazil na zajímavý projekt textql, který umožňuje spouštět SQL dotazy nad csv souborem. Samozřejmě mi, stejně jako vám, hned došlo, jak to dělá. Tak se na dvě minutky zamyslete… Taky Vám to vyšlo tak, že načtení csv souboru je banální, a pak se to prostě napere do sqlite databáze, a je hotovo?

Načtení

Jestli jsem to po zběžném pohledu do kódu textql pochopil dobře, tak nepozná, zda mají data hlavičku, ani nedělá automatickou detekci formátu. My v pythonu máme naštěstí modul csv, který nám s něčím z toho pomůže. Takže:

import csv
import sys

with open(sys.argv[1], 'rb') as csvfile:
    sniff_data = csvfile.read(8192)
    csvfile.seek(0)

    sniffer = csv.Sniffer()
    has_header = sniffer.has_header(sniff_data)
    dialect = sniffer.sniff(sniff_data)

Takže nejprve si přečteme kousek souboru. Uděláme si sniffer. Ten nám na základě souboru řekne, jestli má soubor asi hlavičku, a pak zkusí rozpoznat dialekt – vpodstatě hádá oddělovače sloupců a tak. No a když máme dialekt, tak si uděláme Reader, a načteme:

    reader = csv.reader(csvfile, dialect)

    for row in reader:
        if table is None:
            if has_header:
                table = mk_table_from_header(row)
                continue
            else:
                table = mk_table_dummy_columns(row)
        insert_row(table, row)

SQLite

Já moc v sql ani sqlite nejedu, ale jak jsem pochopil, tak sqlite nepotřebuje u sloupců typy – co se tam vloží, to tam bude. Takže v mk_table_from_header si vytáhneme jména sloupců – z hlavičky, a uděláme tabulku, naopak v mk_table_dummy_columns si je prostě očíslujeme jako col1, col2, …

import sqlite3

db_con = sqlite3.connect(":memory:")
db_con.text_factory = str

def mk_table_from_header(row):
    columns = set()
    column_names = []
    for i in row:
        name = get_unused_column_name(safe_column_name(i), columns)
        columns.add(name)
        column_names.append(name)
    cur = db_con.cursor()
    cur.execute("""create table t1(%s)""" % ", ".join(column_names))
    cur.close()
    return ("t1", column_names)

def mk_table_dummy_columns(row):
    column_names = [ "col_%d" % (i+1) for i in range(len(row)) ]
    cur = db_con.cursor()
    cur.execute("""create table t1(%s)""" % ", ".join(column_names))
    cur.close()
    return ("t1", column_names)

Tím insert_row vás ani nebudu obtěžovat.

Is it any good?

Ač si autor textql na otázku Is it any good? sám odpovídá Yes, já tento názor moc nesdílím. Nenapadá mě žádný způsob, jak to smysluplně použít. Pro koho to má být? BFU nebude umět použít SQL, programátor asi bude mít s daty nějaký další úmysl, tak proč si to nenapíše celé v pythonu, nebo třeba mém oblíbeném awk?

Proto se dál nebudu zabývat takovýma drobnostma, jako že jestli je hodnota číslo, tak si ji převedeme na číslo, abychom mohli počítat, a tak. Jenom sem dám odkaz na kompletní příklad, kdyby to náhodou někoho zajímalo.

Sdílet