3 Punkte von GN⁺ 2023-09-16 | 1 Kommentare | Auf WhatsApp teilen
  • Die eurofxref-hist.zip der EZB ist nur ein einfaches Bündel aus Wechselkurs-CSV-Dateien, aber mit curl, gunzip und sqlite3 lässt sich sofort das Datum 2000-10-26 finden, an dem der Dollar gegenüber dem Euro am stärksten war
  • Das Original liegt in einem Wide Format vor, bei dem nach Date für jede Währung eine eigene Spalte folgt; für Analysen ist das unpraktisch und muss in ein Long Format als Date,Currency,Rate umgewandelt werden
  • Wegen des trailing comma am Ende jeder Zeile liest der CSV-Parser eine leere Spalte ein; in Pandas muss deshalb mit .iloc[:,:-1] die letzte Spalte entfernt werden, damit das Ergebnis von melt sauber ist
  • Das bereinigte CSV kann per HTTP PUT zu csvbase hochgeladen und dann mit Werkzeugen wie gnuplot, DuckDB und sqlite3 weiterverwendet werden, etwa für Diagramme, gleitende Durchschnitte und das Laden von HTTP-CSV
  • Öffentlich verfügbare Daten, die sich ohne Zugangsverhandlung, Authentifizierung, Quoten oder komplexe API-Dokumentation abrufen lassen, verhalten sich wie eine Open API; selbst eine einfache ZIP-Datei kann zur Grundlage des Datenaustauschs in Finanzanwendungen werden

Wechselkurse mit einer einzigen ZIP-Datei abfragen

  • Die EZB veröffentlicht historische Wechselkursdaten zwischen dem Euro und anderen Währungen als offizielle ZIP-Datei
  • Die folgende Pipeline lädt die Daten herunter, entpackt sie, liest das CSV in eine SQLite-In-Memory-Datenbank ein, sortiert nach dem USD-Wert und ermittelt das erste Datum
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \
| gunzip \
| sqlite3 ':memory:' '.import /dev/stdin stdin' \
  "select Date from stdin order by USD asc limit 1;"
  • Die Ausgabe ist 2000-10-26
  • curl -s reduziert störende Meldungen auf stderr, und gunzip entpackt die ZIP-Datei
  • Unter Mac OS oder BSD unterstützt das BSD-basierte gunzip keine ZIP-Dateien; stattdessen muss bsdtar -xOf - verwendet werden
  • sqlite3 ':memory:' nutzt eine In-Memory-Datenbank, und .import /dev/stdin stdin lädt die Standardeingabe in die Tabelle stdin

CSV-Form bereinigen und Pandas melt

  • Der Header des ursprünglichen CSV hat die Form Date,USD,JPY,BGN,CYP,CZK,DKK,..., also Wide Format, bei dem auf die Datumsspalte je Währung eine eigene Spalte folgt
  • Für Filter und Aggregationen ist ein Long Format als Date,Currency,Rate viel einfacher zu handhaben
  • Die Umwandlung von Wide Format in Long Format wird oft melt genannt
  • Die meisten SQL-Datenbanken kennen keine Operation, die direkt einem melt entspricht; deshalb ist Pandas für die Datenaufbereitung nützlich
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).melt("Date").to_csv(sys.stdout, index=False)'
  • Die EZB-Datei enthält am Ende jeder Zeile ein trailing comma, weshalb der CSV-Parser zusätzlich eine leere letzte Spalte einliest
  • Diese leere Spalte erzeugt am Ende des melt-Ergebnisses nutzlose Zeilen und muss daher entfernt werden
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).iloc[:, :-1].melt("Date")\
.to_csv(sys.stdout, index=False)'
  • .iloc[:, :-1] wählt alle Zeilen und alle Spalten außer der letzten aus
  • Die EZB-Wechselkursdaten brauchen zwar eine kleine Formatbereinigung, lassen sich aber ohne Zugangsverhandlung, Bezahlung, Gespräche mit dem Vertrieb, das Einreichen von E-Mail-Adresse, Firmenname und Jobtitel, Quoten, Authentifizierung oder das Lesen von API-Dokumentation sofort nutzen
  • Weil nur das grundlegende Formatproblem behandelt werden muss, ist diese Veröffentlichung im Vergleich zu vielen anderen offenen Datensätzen relativ gut nutzbar

Bereinigte Daten zu csvbase hochladen

  • Das bereinigte CSV kann in eine csvbase table hochgeladen werden, um wiederholte Bereinigung zu vermeiden
  • Hängt man an die bestehende Pipeline noch ein weiteres curl an, lässt sich das CSV per HTTP PUT hochladen
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).iloc[:, :-1].melt("Date")\
.to_csv(sys.stdout, index=False)' | \
curl -n --upload-file - \
'https://csvbase.com/calpaterson/eurofxref-hist?public=yes'
  • --upload-file - lädt die über stdin empfangenen Daten zur angegebenen URL hoch
  • Wenn die Tabelle in csvbase noch nicht existiert, wird sie neu angelegt; existiert sie bereits, werden die Daten in diese Tabelle geschrieben
  • -n verwendet die Zugangsdaten aus ~/.netrc

Wechselkursdiagramm mit gnuplot zeichnen

  • Die bereinigte csvbase-Tabelle kann per curl als CSV abgerufen und mit grep, cut und gnuplot verbunden werden
curl -s https://csvbase.com/calpaterson/eurofxref-hist | \
grep USD | \
cut -d, -f 2,4 | \
gnuplot -e "set datafile separator ','; set term dumb; \
plot '-' using 1:2 with lines title 'usd'"
  • Dieser Befehl stellt mehr als 6.000 Datenpunkte als ASCII-Art in einem 80x25-Zeichen-Terminal so dar, dass sie einigermaßen lesbar sind
  • Die gnuplot-Konfiguration ist darauf abgestimmt, CSV-Eingaben als Liniendiagramm aus Datum und Wechselkurs zu zeichnen
    • set datafile separator ',': legt fest, dass die Eingabe CSV ist
    • set term dumb: zeichnet als ASCII-Art
    • plot -: liest die Daten von stdin
    • using 1:2 with lines: zeichnet eine Linie aus Spalte 1 und 2, also Datum und Kurs
    • title 'usd': setzt den Namen der Linie auf usd
  • Die Ausgabe kann auch als SVG erfolgen; damit es wie eine Zeitreihe aussieht, muss angegeben werden, dass die x-Achse Zeit enthält, außerdem das Zeitformat und eine Drehung der x-Achsenbeschriftung
  • Für wiederholte Nutzung lässt sich das in eine Bash-Funktion plot_timeseries_to_svg packen

Gleitenden Durchschnitt mit DuckDB berechnen

  • Um den Trend des USD-Wechselkurses zu sehen, lässt sich mit DuckDB ein gleitender Durchschnitt berechnen
curl -s https://csvbase.com/calpaterson/eurofxref-hist | \
duckdb -csv -c "select Date, avg(value) over \
(order by date rows between 100 preceding and current row) \
as rolling from read_csv_auto('/dev/stdin')
where variable = 'USD';" | \
plot_timeseries_to_svg rolling
  • Falls duckdb nicht verfügbar ist, lässt sich dieselbe Abfrage auch ohne große Mühe für sqlite3 umschreiben
  • DuckDB ähnelt SQLite, ist aber nicht zeilenorientiert, sondern spaltenorientiert
  • DuckDB kann CSV direkt per HTTP einlesen und daraus eine Tabelle erstellen
CREATE TABLE eurofxref_hist AS SELECT * FROM
read_csv_auto("https://csvbase.com/calpaterson/eurofxref-hist";);
  • DuckDB erkennt Typen ziemlich gut, passt sich an die Größe des Terminals an und zeigt große Ergebnisse standardmäßig gekürzt an
  • Bei großen Abfragen kann es einen Fortschrittsbalken anzeigen und auch Markdown-Tabellen ausgeben

Wie offene Daten wie eine Open API funktionieren

  • Schon mit dem CSV in der ZIP-Datei und Werkzeugen, die sich einfach per brew install oder apt install installieren lassen, ist eine Menge möglich
  • eurofxref-hist.zip ist als organisationsübergreifendes Datenaustauschprotokoll extrem einfach gehalten
  • Die ZIP-Datei wirkt klein und unscheinbar, wird aber täglich von vielen Finanzanwendungen genutzt
  • Dass die EZB das trailing comma beibehält, könnte daran liegen, dass dessen Entfernung heute viel bestehenden Code kaputt machen würde
  • Wenn offene Daten sehr einfach bereitgestellt werden, können sie die Rolle einer Open API übernehmen
  • Wenn viele APIs eher Datenaustausch als Remote Function Calls sind, unterscheiden sie sich funktional nicht stark von offenen Daten, die sich leicht abrufen lassen

Die einfachen URLs und HTTP-Verben von csvbase

  • csvbase verwendet für jede Tabelle genau eine URL
https://csvbase.com/<username>/<table_name>;
  • Das Beispiel lautet wie folgt
https://csvbase.com/calpaterson/eurofxref-hist
  • Jede URL unterstützt vier zentrale HTTP-Verben
    • GET: liefert das CSV; im Browser kann stattdessen eine Webseite zurückgegeben werden
    • PUT: erstellt mit einem neuen CSV eine neue Tabelle oder überschreibt eine bestehende
    • POST: fügt einer bestehenden Tabelle in großem Umfang CSV-Zeilen hinzu
    • DELETE: löscht die betreffende Tabelle
  • Für die Authentifizierung wird HTTP Basic Auth verwendet

Notizen zur Datenaufbereitung und zu Pipelines

  • SQL-Datenbanken mit Funktionen, die einem melt entsprechen, sind etwa Snowflake mit UNPIVOT und MS SQL Server mit PIVOT/UNPIVOT
  • Ein wichtiger Grund für den Einsatz von R und Pandas ist ihre Stärke bei der Datenaufbereitung
  • Bash-Pipelines arbeiten als Multiprozess-Struktur, in der jedes Programm als eigenständiger Prozess parallel läuft
  • Im Oktober 2000 lag der Dollarkurs gegenüber dem Euro bei 0.8252, was bedeutete, dass man für 1 US-Dollar 1,21 Euro kaufen konnte
  • Der Euro wurde im Januar 1999 ohne Banknoten und Münzen eingeführt, existierte anfangs nur innerhalb des Bankensystems, und Scheine sowie Münzen kamen erst später hinzu

1 Kommentare

 
GN⁺ 2023-09-16
Hacker-News-Kommentare
  • Ich erinnere mich an diese Datei aus meiner Zeit bei der EZB vor etwa 15 Jahren.
    Diese Datei war mit Abstand die am häufigsten heruntergeladene Datei auf der EZB-Website, und viele Menschen und Finanzinstitute luden sie täglich herunter, um ihre eigenen Systeme zu aktualisieren.
    In den paar Minuten direkt nach der festgelegten täglichen Veröffentlichungszeit gab es jeweils einen deutlichen Traffic-Peak, und es war eine bewusste Entscheidung, dass nach dem Entpacken eine einfache CSV-Datei herauskam.
    Dadurch ließ sich die Datei stabil, schnell und mit wenigen Ressourcen ausliefern, und das kleine Team, das damals für die öffentliche Website der EZB zuständig war, konnte auf diese technische Entscheidung, diese Daten als einzelne statische Datei bereitzustellen, sehr stolz sein.

    • Solche Ansätze tragen einen großen Teil des Datenaustauschs, aber wer nie mit alten Systemen zu tun hatte, kennt sie kaum.
      Sie sind nicht schick und es gibt kein Framework.
      Vor etwa 15 Jahren habe ich bei einem alten Großunternehmen, dessen Produkte wohl jeder schon einmal gekauft hatte, den Datenaustausch zwischen dem Produktdatensystem und untergeordneten/parallelen Systemen aus Fusionen und Übernahmen betreut; größtenteils waren das Massenimporte/-exporte von Dateien mit fester Breite oder Trennzeichen über SFTP-Server.
      Das Produkt war damals bereits 15 Jahre alt, und es liefen etwa 20 bis 30 solcher Datenquellen oder Exporte hin und her, aber das funktionierte sehr gut.
      Wahrscheinlich wird es heute noch ohne große Änderungen genutzt, und das damalige Frontend war ein altes Smalltalk-System, das gerade neu geschrieben wurde.
    • Ich kenne diese Datei auch und war einer dieser Nutzer.
      Von den Datenquellen, die wir verwendeten, war sie am angenehmsten zu handhaben.
    • Wenn ich mir vorstelle, welche Hürden ich in meinem Unternehmen nehmen müsste, um sich ändernde Daten als statische CSV/ZIP-Datei hochzuladen, ist das erstaunlich.
      Der Architekt würde sagen, ZIP sei kein Format, das zur Spezifikation für diesen Zweck passt, Compliance würde eine Prüfung auf Abfluss personenbezogener Daten verlangen, und Risk würde fordern, dass böswillige Akteure die Datei nicht herunterladen können.
      Das Web-Team würde vermutlich sagen, dass für jede Ergänzung der Website ein genehmigter Change-Prozess nötig ist.
    • Ich frage mich, warum man sich dafür entschieden hat, sie als ZIP-Datei bereitzustellen, statt einfach eine CSV hochzuladen und es der HTTP-Kompression zu überlassen.
    • Es ist ein wenig beängstigend, sich die Folgen vorzustellen, wenn diese Datei auf irgendeine Weise beschädigt wäre, etwa wenn die Spaltenüberschriften falsch wären.
  • Ein einfacher Datei-Download und eine CSV-Datei sind großartig.
    Ich wünschte, mehr Stellen würden Daten in einem so einfachen Format veröffentlichen, und jedes Mal, wenn ich bei Downloads von US-Regierungsdaten einen „Warenkorb“ füllen muss, sterbe ich innerlich ein bisschen.
    Es gibt auch viele Wrapper-Tools, die diese konkrete Pipeline vereinfachen, und wenn man eine Web-Ansicht und etwas fortgeschrittenere Funktionen braucht, ist auch etwas wie Datasette gut.

    • Der Vorteil, eine gezippte CSV-Datei über das Web bereitzustellen, ist, dass man sie, abgesehen von der Datenbank, nirgends speichern muss und sie direkt sehr schnell streamen kann.
      Man kann die ZIP-Datei als Stream lesen, die CSV zeilenweise verarbeiten und transformieren und sie dann bei Postgres mit COPY FROM stdin in die Datenbank laden.
    • Es ist erstaunlich, dass SQLite CSV als Eingabe akzeptieren und direkt darauf Abfragen ausführen kann.
      Das wirkt so logisch und nützlich, und trotzdem bin ich bisher nicht darauf gestoßen.
      Da es viele Berichte als CSV gibt, möchte ich das schnell ausprobieren, um Abfragen flott laufen zu lassen.
    • CSV ist großartig, wenn man absolut garantieren kann, dass es „die eine CSV“ gibt und niemand sie jemals in einem Tabellenkalkulationsprogramm öffnet, bearbeitet oder von Hand in einem Texteditor repariert.
      Zum Beispiel gehen die Arten der Anführungszeichenbehandlung auseinander, wie bei "Look, this contains \"quotes\"!",012345 und "Look, this contains ""quotes""!",012345; noch kaputtere Beispiele wären "Look, this contains "quotes"!",012345 oder Look, this contains "quotes"!,012345.
      Als Spuren einer Tabellenkalkulation können auch führende Nullen abgeschnitten werden, etwa "Look, this contains ""quotes""!",12345.
      Theoretisch kann auch JSON von Hand bearbeitet und halb kaputt gemacht werden, aber in der Praxis habe ich kaum erlebt, dass jemand das mit JSON-Dateien tut; und Werte wie Seriennummern bleiben in JSON eher Strings, statt von einer „hilfsbereiten“ App als Integer behandelt zu werden, bei dem führende Nullen abgeschnitten werden.
    • Ich habe nie darüber nachgedacht, wie absurd das Warenkorb-Modell bei Regierungsdaten ist.
      Warum gibt es das überhaupt, gibt es dafür irgendeinen legitimen Grund?
    • Das Dokumentformat selbst scheint nicht das Kernproblem zu sein.
      Selbst wenn man die CSV in ein gezipptes JSON-Dokument umwandelt, bleiben die Vorteile dieselben.
      Das eigentliche Problem ist, dass einem zu viele Hürden in den Weg gelegt werden, um schlicht eine einzelne statisch bereitgestellte Datei herunterzuladen.
  • Ich habe einmal eine API für eine Regierungsbehörde gebaut, bei der sich die Daten nur einmal im Jahr änderten oder nur sehr selten überarbeitet wurden.
    Der gesamte Datensatz hätte in eine einzige ZIP-Datei unter 1 MB gepasst, aber als der Solution Architect die Anforderungen definierte, wurde die Sache groß.
    Weil sich die Daten genau in dem Moment der Anfrage geändert haben könnten, durfte kein Cache verwendet werden, wodurch die API langsam wurde; außerdem entstand ein übermäßig komplexes Webhook-System, um Abonnenten über Datenänderungen zu informieren.
    Eine einzelne ZIP-Datei wäre vielleicht zu simpel gewesen, aber sie lag auch nicht weit von dem entfernt, was tatsächlich nötig gewesen wäre.

    • Wenn sich Daten unter 1 MB ein- oder zweimal im Jahr ändern, ist die richtige API meiner Ansicht nach ein statischer Webserver mit sauberer Unterstützung für ETag/If-Modified-Since.
      Wenn man es etwas schicker machen will, fügt man einen Webhook hinzu, der ausgelöst wird, wenn sich die Datei ändert, damit Clients wissen, wann sie sie erneut herunterladen sollen, statt einmal täglich zu pollen.
      Oder es reicht sogar ein Skript, das bei einer Änderung eine vorab festgelegte E-Mail an eine Mailingliste sendet.
    • Man legt die ZIP-Datei auf einen Webserver mit ETag-Unterstützung und pollt sie immer dann, wenn Zugriff nötig ist.
      Wenn sich gegenüber vorher nichts geändert hat, bekommt man eine leere HTTP-304-Antwort; wenn sie sich geändert hat, lädt man die ZIP-Datei unter 1 MB mit neuem ETag erneut herunter. Ich weiß nicht, was hier fehlen sollte.
    • Wenn sich Daten nur einmal im Jahr oder selten ändern, nutzen auch die Anwender, die diese Daten verwenden, die API wahrscheinlich nur selten, sodass Geschwindigkeit möglicherweise kein großes Problem ist.
      Caches erhöhen die Komplexität und bringen das Risiko mit sich, dass man sie manuell revalidieren muss; daher könnte der Solution Architect durchaus recht gehabt haben.
  • Es ist eine schreckliche API, wenn man eine 565-KB-Datei herunterladen muss, nur um einen einzigen Ergebniswert 2000-10-26 zu bekommen.
    Wenn man große Datenmengen abrufen und den Nutzern wieder bereitstellen will, ist eine als ZIP gebündelte CSV großartig, und ich ziehe sie einem Protobuf für Echtzeit-Zugzeiten im öffentlichen Nahverkehr, das mehrere Sprachen schlecht unterstützt, deutlich vor.
    Aber wenn man sie wie eine API zum Abrufen eines einzelnen Werts behandelt, ist das eine enorme Verschwendung, und ich hoffe, niemand baut das auf diese Weise in eine App ein.
    Der Artikel selbst ist toll, aber der Titel fühlt sich zu sehr wie eine provokante Behauptung an.

    • Das sind historische Daten.
      Es gibt absolut keinen Grund, sie öfter als einmal am Tag anzufragen, und Leute, die solche Daten verwenden, wollen wahrscheinlich sehr unterschiedliche Filter oder Aggregationen.
      Wenn es darum geht, aktuelle Wechselkurse zu erhalten, ist das tatsächlich schlechtes Design, aber für diesen Zweck gibt es andere Dienste, und diese Datei passt gut zum typischen Anwendungsfall.
    • Wenn du hoffst, dass niemand so etwas in eine App einbaut, habe ich schlechte Nachrichten.
      Nicht direkt mit APIs zu tun, aber als ich früher eine Anwendung für Landverwaltung betreute, lief sie bis zu einer neuen Version auch in langsamen Außenstellen, die vielleicht nur ISDN-ähnliche Leitungen hatten, problemlos; die neue Version dagegen überhaupt nicht.
      Der Anbieter sagte, man solle sie auf einem RDP-Server ausführen, was ich für absurd hielt. Bei der Untersuchung stellte sich heraus, dass ein bestimmter Aufruf ohne jeden Grund SELECT * FROM sometable machte, während andere Aufrufe im selben Lauf ordentliche SQL-Select-Klauseln verwendeten.
      Als wir dem Anbieter das sagten, war er zunächst sehr verwirrt, wie wir das herausgefunden hatten, und veröffentlichte schließlich eine neue Version, die auch über langsame Leitungen nutzbar war.
      Es ist schwer zu verstehen, warum ihre eigenen Tests das nicht gefunden haben und warum sie den Kunden stattdessen eine teure Lösung aufdrängen wollten.
    • Das fühlt sich an, als hätte ein Argument von vor 20 Jahren angerufen und wolle sein Argument zurück.
      Wenn man heutzutage auch nur ein bisschen JavaScript gesehen hat, sind 565 KB und die Logik, darin einen großen Wert zu finden, nach jedem vernünftigen Maßstab winzig.
    • Viele Leute in diesem Thread scheinen die unterschiedlichen Definitionen von „API“ einfach zu übergehen.
      Manche sehen schon „eine Methode, Daten zu bekommen, selbst wenn man den gesamten Datensatz ungefiltert erhält“ als API. Für mich ist der Download einer ganzen Tabelle eher der Download eines Datenmodells, auf das noch keine Logik angewendet wurde; eine API ist die Logik, die Teile des Modells auf die mich interessierende Weise filtert und zurückgibt.
    • Ich weiß nicht, warum man annimmt, dass 565 KB wichtig sind.
      Ich habe viel Finanzsoftware sowohl im Backend als auch im Frontend gebaut, und im Frontend ist es leider üblich, schon vor dem Erreichen der eigentlichen Daten ungefähr diese Menge an „Daten“ zu übertragen.
      Im Backend ist es nur eine Designentscheidung, und nichts ist schneller, als wenn ein nächtlicher Cronjob Wechselkurse parst, daraus eine zweckoptimierte todays-rates.json erzeugt und sie als statische Datei an Mobile-, Web- und Microservice-Apps ausliefert.
      Nirgends steht, dass eine Mobile-App dieses ZIP-CSV-over-HTTP unbedingt direkt konsumieren muss.
  • Für Leute, die sich darüber beschweren, dass sie jedes Mal eine große Datei abrufen müssen, wenn sie nur ein kleines Stück Daten brauchen, gibt es eine sehr einfache Optimierung.
    Wenn garantiert ist, dass die Datei append-only ist, und man statt einer ZIP-Datei Kompression wie HTTP gzip/brotli verwendet, kann man per Range Request nur die neuen Daten seit der letzten Aktualisierung abrufen.
    Mit einem zusätzlichen Checksum-Header zur Sicherheit ergibt das eine ziemlich effiziente und dennoch sehr einfache inkrementelle API.
    Natürlich muss man Zustand speichern und die Kosten für den ersten Download sowie die Zustandsverwaltung tragen, und es ist ineffizient, wenn man genau einmal nur den EUR/JPY-Wechselkurs vom 22.08.2007 braucht.

    • Genau. Ich plane eine Client-Bibliothek, die mit ETag und anderen Techniken genau so etwas macht.
      Das ist noch sehr in Arbeit, aber der aktuelle Code in „Research-Qualität“ ist hier: https://pypi.org/project/csvbase-client/
    • Noch zu Range Requests: Wenn der Server Range Requests für ZIP-Dateien erlaubt und die ZIP-Datei sehr groß ist, man darin aber nur ein paar Dateien braucht, kann man statt des gesamten ZIPs nur das zentrale Verzeichnis und die komprimierten Daten der benötigten Dateien abrufen.
      https://github.com/gtsystem/python-remotezip
    • Oder man stellt einfach mehrere Diff-Dateien bereit.
      Schon ein Patch pro Tag könnte die Bandbreite, die ich zum Aktuellhalten meiner Datei brauche, stark reduzieren.
      Das gilt für den Fall, dass ein paar hundert KB zusätzlicher Download pro Tag eine Rolle spielen; meistens ist das wahrscheinlich nicht der Fall.
  • Im sqlite-Beispiel ist ein Tippfehler.
    Im Screenshot fehlt es nicht, aber man muss sqlite das Argument -csv hinzufügen.

    • Seltsam. Ich hatte dieses Argument ursprünglich drin, habe es dann entfernt, weil es offenbar nicht nötig war, und in meiner Umgebung funktionierte es.
      Ich werde es wieder hinzufügen und den Cache invalidieren. Nachdem die Kinder im Bett sind, schaue ich nach, was schiefgelaufen ist.
      Update: Der Grund, warum es in meiner Umgebung funktionierte, war die Einstellung .separator ',' in ~/.sqliterc.
      Offenbar hatte ich das irgendwann als Standard gesetzt, nachdem mir klar geworden war, dass ich hauptsächlich CSV-Dateien importiere.
  • Kurz als Abschweifung: Auch wenn der Euro anfangs nur elektronisch existierte, gab es feste Wechselkurse zu den bestehenden Währungen der Eurozonen-Mitgliedstaaten.
    Insbesondere war er an die etablierte und vertrauenswürdige Deutsche Mark gebunden.
    Um also zu erklären, „warum der frühe Euro schwach war“, müsste man auch erklären, warum die damalige DEM schwach war; die Erklärung in dem Absatz scheint diesen Test nicht zu bestehen.

  • Bei kleinen Problemen, bei denen man jedes Mal die gesamte Datenbank herunterladen und sie read-only verarbeiten kann, sollte man den Wert von Einfachheit nicht unterschätzen.
    Ich mag SQLite, weil es wie eine .json- oder .csv-Datei portabel ist, aber zugleich besser darauf vorbereitet ist, wie eine Datenbank interaktiv genutzt zu werden.

    • Mit clickhouse-local kann man auch alte CSV-Dateien wie eine Datenbank behandeln.
  • Der Kernpunkt liegt hier:
    Dinge, die man in diesem Fall nicht tun musste: Zugriffsrechte aushandeln, zum Beispiel bezahlen oder mit einem Vertriebsmitarbeiter sprechen; E-Mail-Adresse, Firmenname und Jobtitel in die Lead-Datenbank von irgendwem eintragen; Quoten einhalten; sich authentifizieren; API-Dokumentation lesen; sich mit Problemen befassen, die über grundlegendes Format und grundlegende Struktur hinausgehen.

    • Beim Punkt, dass man keine „Quoten einhalten“ muss, bin ich skeptisch.
      Bandbreite ist nicht kostenlos.
  • SQLite kann ZIP-Dateien lesen und schreiben.
    https://sqlite.org/zipfile.html
    Ich frage mich, ob man statt mit gunzip mit sqlite3 dekomprimieren kann.

    • Guter Punkt.
      Wenn es in Ordnung ist, die Datei auf der Festplatte zu speichern, könnte man so vorgehen:

sqlite3 -newline '' ':memory:' "SELECT data FROM zipfile('eurofxref-hist.zip')" \
| sqlite3 -csv ':memory:' '.import /dev/stdin stdin' \
"select ...;"

Das ohne temporäre Datei zu machen, ist knifflig. Beispielsweise funktioniert `readfile('/dev/stdin')` nicht, weil SQLite versucht, `seek()` zu verwenden.  
Eine sehr hässliche Methode, bei der man die ZIP-Datei mit `xxd` in Hex umwandelt und in ein String-Literal der SQL-Abfrage einfügt, ist möglich:

`curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \`  
`| { printf "SELECT data FROM zipfile(x'"; xxd -p | tr -d '\n'; printf "')"; } \`  
`| sqlite3 -newline '' \`  
`| sqlite3 -csv ':memory:' '.import /dev/stdin stdin' \`  
`"select ...;"`