Vito_Leone
Anmeldungsdatum: 4. September 2009
Beiträge: 204
|
Hallo zusammen, ich bastel derzeit an einem Bash-Skript, mit dem ich auf eine sqlite-Datenbank zugreifen möchte. Für den Datenimport aus csv-Dateien verwende ich den Meta-Befehl .import . Ein Codeschnipsel: sqlite3 -batch -csv wtst.sqlite ".import raw.dat wtst" Häßlich ist, dass mir beim Import für jeden bereits vorhandenen Eintrag ein Fehler geworfen wird. Klar: Der Primary Key ist einzigartig und erfüllt hier zuverlässig seinen Zweck. Das ist ja auch gut so: wtst-data-raw-mod.dat:71193: INSERT failed: UNIQUE constraint failed: wtst.TIMESTAMP Jetzt fände ich es nett, wenn ich im Falle eines Konflikts den entsprechenden Eintrag updaten könnte. Ich bräuchte also eine Möglichkeit, den .import-Befehl zu pimpen, so dass er sich nicht an einem INSERT versucht, sondern stattdessen ein REPLACE durchführt. Vielleicht gibt es aber auch eine andere Möglichkeit, das Ganze zurealisieren? Ein anderer Befehl, der sich von mir gerade einfach nicht finden lassen will? Oder stoße ich hier an die Grenzen von sqlite? Ich würde mich freuen, wenn Ihr mir ein wenig auf die Sprünge helfen könntet. Mit Grüßen,
VL
|
noisefloor
Ehemaliger
Anmeldungsdatum: 6. Juni 2006
Beiträge: 29041
Wohnort: WW
|
Hallo, das Stichwort heißt: UPSERT (https://sqlite.org/lang_UPSERT.html 🇬🇧). Ob das beim .import auch funktioniert kann ich dir nicht sagen. Ansonsten würde ich zeilenweise über die CSV-Datei iterieren und jeweils ein UPSERT durchführen. Ist halt ein bisschen mehr Code, aber macht dann genau das, was du suchst. Gruß, noisefloor
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Dann bräuchte man erst mal ein aktuelles sqlite für Ubuntu 14.04, die UPSERT-Syntax ist erst ein paar Monate alt und sqlite3 in Version 3.24.0 erst ab Ubuntu 18.10 in den regulären Paketquellen: https://packages.ubuntu.com/cosmic/sqlite3 Der Ansatz artet etwas aus, wenn man die Felder in der Datenbank nicht fest im Skript vorgeben will (bzw. man die Feldnamen und den Primary Key kennen muss):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | #!/usr/bin/env python2
import argparse
import csv
import sqlite3
parser = argparse.ArgumentParser()
parser.add_argument("database", metavar="DATABASE", help="database file")
parser.add_argument("table", metavar="TABLE", help="database table")
parser.add_argument("csv_file", metavar="CSV", help="csv file")
parser.add_argument("-d", "--delimiter", default=',',
help="delimiter in csv file (defaults to ',')")
args = parser.parse_args()
with open(args.csv_file) as f, sqlite3.connect(args.database) as con:
other_columns = []
for r in con.execute("pragma table_info({})".format(args.table)).fetchall():
if r[-1] == 1:
pk = r[1]
continue
other_columns.append(r[1])
for row in csv.reader(f, delimiter=args.delimiter):
con.execute("""
INSERT INTO {}
VALUES ({})
ON CONFLICT({}) DO UPDATE
SET {}
""".format(args.table,
','.join('?' for _ in range(len(row))),
pk,
','.join("{}=excluded.{}".format(column, column)
for column in other_columns)),
row)
|
$ ./update_db_with_csv.py -h
usage: update_db_with_csv.py [-h] [-d DELIMITER] DATABASE TABLE CSV
positional arguments:
DATABASE database file
TABLE database table
CSV csv file
optional arguments:
-h, --help show this help message and exit
-d DELIMITER, --delimiter DELIMITER
delimiter in csv file (defaults to ',')
Der Aufruf wäre dann z.B:
./update_db_with_csv.py wtst.sqlite wtst raw.dat Wenn es außer dem Primary Key keine Felder mit einer "UNIQUE" Beschränkung gibt, wäre ein (von Vito_Leone fast selbst angedachtes) INSERT OR REPLACE (vgl. z.B. https://www.tutlane.com/tutorial/sqlite/sqlite-replace-statement) einfacher, das funktioniert auch mit älteren sqlite3-Versionen wie der in Ubuntu 14.04 (selbes Interface wie das obige Skript):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #!/usr/bin/env python2
import argparse
import csv
import sqlite3
parser = argparse.ArgumentParser()
parser.add_argument("database", metavar="DATABASE", help="database file")
parser.add_argument("table", metavar="TABLE", help="database table")
parser.add_argument("csv_file", metavar="CSV", help="csv file")
parser.add_argument("-d", "--delimiter", default=',',
help="delimiter in csv file (defaults to ',')")
args = parser.parse_args()
with open(args.csv_file) as f, sqlite3.connect(args.database) as con:
for row in csv.reader(f, delimiter=args.delimiter):
con.execute("""
INSERT OR REPLACE INTO {}
VALUES ({})
""".format(args.table,
','.join('?' for _ in range(len(row)))),
row)
|
Bzw. ganz hässlich, wenn man das unbedingt in der Shell machen will und dem Inhalt der CSV-Datei mit durch Kommata getrennten Feldern vertraut (und darin keine escapten Zeichen und Anführungszeichen vorkommen):
| while read -r line
do
sqlite3 wtst.sqlite "INSERT OR REPLACE INTO wtst VALUES ($line)"
done < <(sed 's/^/\"/;s/,/\",\"/g;s/$/\"/' raw.dat)
|
|
Vito_Leone
(Themenstarter)
Anmeldungsdatum: 4. September 2009
Beiträge: 204
|
Vielen Dank für Euer beider Antworten. Das lässt vermuten, dass der von mir wegen seiner Einfachheit angedachte einzeilige Import in seinen Möglichkeiten beschränkt ist. Demnach werde ich für die Update-Funktion ein paar Zeilen mehr verwenden müssen. Das von noisefloor ins Spiel gebrachte UPSERT kannte ich bisher nicht. Merci für's Zeigen. Da mir das UPSERT nicht zur Verfügung steht, bleibe ich demnach beim REPLACE bzw dem seahawk1986'schen "Original" INSERT OR REPLACE. Eine weitere Idee[1], die ich mir in der Zwischenzeit ergoogelt hatte, gefällt mir ganz gut. Ich werde diese meinen Bedürfnissen anpassen. Die Qualitätskontrolle der Loggerfiles überlasse ich dem Import von sqlite. Besten Dank! VL [1] https://stackoverflow.com/a/11089277
|