mygems

SQL-Abfragen auf einer CSV-Datei ohne Datenbank ausführen (2026 guide)

Für ein GROUP BY brauchst du kein Postgres. So fragst du eine CSV mit DuckDB, SQLite, q oder dem Handy direkt ab und welches Tool wann passt.

Von uos ·

Die Kurzfassung: Richte DuckDB direkt auf die Datei.

duckdb -c "SELECT region, SUM(amount) FROM 'sales.csv' GROUP BY region"

Kein Schema, kein Import, kein Server, kein CREATE TABLE. Die CSV-Datei bleibt genau dort, wo sie ist, und die Kopfzeile wird zu den Spaltennamen. Wenn du heute nur ein Tool installierst, nimm dieses.

Der Rest dieser Seite erklärt, warum das funktioniert, was du tun kannst, wenn du DuckDB nicht nutzen kannst, und wie du mit den zwei Dingen umgehst, über die jeder stolpert: Dateinamen in Anführungszeichen und Spalten, deren Namen Leerzeichen enthalten.

Warum du die Datei nirgends laden musst

Der Reflex, die Daten „erst mal in eine Datenbank zu bringen", stammt aus einer Zeit, in der SQL-Engines nur ihren eigenen Speicher lesen konnten. Moderne Engines haben diese Einschränkung nicht. DuckDB, SQLite und einige kleine CLI-Tools behandeln eine CSV-Datei für die Dauer einer Abfrage als Tabelle, erkennen die Typen anhand einer Stichprobe, liefern die Antwort und vergessen alles wieder.

Das ändert die Rechnung komplett. Eine einmalige Frage zu einer Datei, die dir jemand per E-Mail geschickt hat, ist kein Schema, kein Ladeskript und keinen Aufräumschritt mehr wert. Sie ist eine Zeile wert.

Eine echte Datenbank ist nur in genau zwei Fällen noch die richtige Wahl: Die Daten werden wiederholt von mehreren Personen abgefragt, oder sie brauchen Constraints und Beziehungen, die eine einzelne flache Datei nicht ausdrücken kann. Beides trifft auf „Was ist die Summe pro Region in diesem Export" nicht zu.

Die Optionen, ehrlich

ToolInstallationAm besten fürSchwach bei
DuckDBEine Binärdatei, keine AbhängigkeitenAlles bis zu mehreren zehn GBBei dieser Aufgabe kaum etwas
SQLite + .importAuf macOS und Linux schon daDie Tabelle für später behaltenTypen – alles landet als TEXT
qpip install q-text-as-dataSchnelle Einzeiler in einer Shell-PipelineGroße Dateien; es ist langsamer
csvsql (csvkit)pip install csvkitEin CREATE TABLE für eine echte Datenbank erzeugenGeschwindigkeit – hier das langsamste
Pandas + pandasqlPandas hast du schonWenn das Ergebnis in weiteren Python-Code fließtDer SQL-Dialekt ist der von SQLite, nicht der von Postgres
Handy-App mit SQLApp Store / PlayDie Datei kam per E-Mail auf dein HandyAlles, was du skripten willst

Die ehrliche Rangliste ist kurz: DuckDB für fast alles, SQLite, wenn du nichts installieren kannst, und eine Handy-App, wenn du nicht am Rechner sitzt. Die anderen sind in Ordnung und haben ihre Momente, aber bei genau dieser Aufgabe schlägt keines DuckDB.

DuckDB im Detail

Die Installation ist eine einzelne Binärdatei: brew install duckdb, oder lade sie herunter und leg sie in deinen Pfad. Dann:

duckdb
D SELECT * FROM 'orders.csv' LIMIT 10;

Zwei Dinge solltest du sofort wissen:

Der Dateiname braucht einfache Anführungszeichen. FROM orders.csv wird als Schema orders und Tabelle csv gelesen, und die Fehlermeldung macht das nicht offensichtlich. FROM 'orders.csv' ist richtig.

Spaltennamen mit Leerzeichen brauchen doppelte Anführungszeichen. SELECT "Order Date" FROM …. Einfache Anführungszeichen liefern stillschweigend für jede Zeile den Text selbst, und das sieht aus, als hätte die Abfrage funktioniert.

Ein ganzes Verzeichnis per Glob lesen, wenn der Export in Monatsstücken kam:

SELECT * FROM 'exports/*.csv'

Wenn die Typerkennung danebenliegt, meistens bei einer Spalte mit IDs mit führenden Nullen oder bei Datumsformaten, die sie nicht kennt, übernimm lieber selbst die Kontrolle über das Einlesen, statt dagegen anzukämpfen:

SELECT * FROM read_csv('orders.csv',
  header = true,
  columns = {'id': 'VARCHAR', 'ordered_at': 'DATE', 'amount': 'DOUBLE'},
  dateformat = '%d/%m/%Y'
)

read_csv akzeptiert außerdem delim, quote, escape, ignore_errors = true und sample_size = -1 (die ganze Datei scannen, bevor die Typen festgelegt werden, statt nur die ersten 20.480 Zeilen). Das Letzte ist die Lösung, wenn eine Spalte zehntausend Zeilen lang numerisch aussieht und dann auf N/A trifft.

Das Ergebnis zurückschreiben, ohne SQL zu verlassen:

COPY (SELECT * FROM 'orders.csv' WHERE amount > 1000)
TO 'large-orders.csv' (HEADER, DELIMITER ',');

Und wenn die Datei öfter als eine Handvoll Mal abgefragt wird, wandle sie einmal mit COPY (SELECT * FROM 'orders.csv') TO 'orders.parquet' um und frage stattdessen die Parquet-Datei ab. Sie ist typisiert, komprimiert und lässt sich meist fünf- bis zwanzigmal schneller scannen.

SQLite, wenn du nichts installieren kannst

sqlite3 wird mit macOS und den meisten Linux-Distributionen ausgeliefert, also funktioniert diese Option auch auf einem gesperrten Rechner.

sqlite3 shop.db
sqlite> .mode csv
sqlite> .import orders.csv orders
sqlite> SELECT region, COUNT(*) FROM orders GROUP BY region;

Der Haken, und er ist wichtig: .import macht jede Spalte zu TEXT. Sortiert wird dann lexikografisch, also steht 100 vor 20, und SUM() liefert Überraschungen. Caste bei der Abfrage (CAST(amount AS REAL)) oder lege die Tabelle mit echten Typen an, bevor du importierst.

Anders als DuckDB hinterlässt das eine shop.db. Das ist gewollt, wenn du die Tabelle morgen wieder brauchst, und Müll, wenn nicht.

q für eine Shell-Pipeline

Wenn die Abfrage nur ein Schritt in einer Kette von Shell-Befehlen ist, liest q die Standardeingabe wie jedes andere Unix-Tool:

q -H -d, "SELECT region, SUM(amount) FROM - GROUP BY region" < orders.csv

-H bedeutet, dass die erste Zeile eine Kopfzeile ist, -d, setzt das Trennzeichen, und - ist stdin. Darunter läuft SQLite, also gilt der SQLite-Dialekt. Außerdem ist es bei großen Dateien die langsamste vernünftige Option: in Ordnung für ein paar hunderttausend Zeilen, darüber mühsam.

Wenn die Datei auf deinem Handy liegt

Nichts davon hilft, wenn der Export ein Mailanhang ist und du nicht am Schreibtisch sitzt. Das kommt oft vor, und die übliche Antwort, „schick es dir selbst und schau später drauf", ist keine gute.

CSV File Viewer - Smart CSViPhone & iPad · Android

Führt echtes SQL auf der Datei aus, unter iOS und Android, mit einem visuellen Filter-Builder für den Fall, dass du auf einer Handytastatur keine Abfrage tippen willst, und einem Assistenten in normaler Sprache für den Fall, dass du die Spaltennamen noch nicht kennst.

Was es kann

Der Dialekt ist der von SQLite, also lassen sich die Abfragen aus diesem Artikel übertragen, mit den üblichen Einschränkungen: keine Fensterfunktionen aus den DuckDB-Beispielen, und die gleichen CAST-Gewohnheiten gelten.

Abfragen für den Vorrat

Was in dieser Datei steht:

SELECT * FROM 'file.csv' LIMIT 20;
DESCRIBE SELECT * FROM 'file.csv';
SELECT COUNT(*) FROM 'file.csv';

Wo die Nullwerte sind, Spalte für Spalte wäre mühsam, also so:

SELECT COUNT(*) - COUNT(email) AS missing_email,
       COUNT(*) - COUNT(phone) AS missing_phone
FROM 'contacts.csv';

Duplikate bei einem Schlüssel:

SELECT id, COUNT(*) AS n
FROM 'orders.csv'
GROUP BY id HAVING n > 1
ORDER BY n DESC;

Zwei Exporte verknüpfen ist die Abfrage, die den ganzen Aufwand am häufigsten rechtfertigt, denn das Gleiche mit SVERWEIS über zwei Tabellenblätter ist deutlich schlimmer:

SELECT o.id, o.amount, c.name
FROM 'orders.csv' o
JOIN 'customers.csv' c ON c.id = o.customer_id;

Eine Monatssumme aus einer Datumsspalte:

SELECT date_trunc('month', ordered_at) AS month, SUM(amount)
FROM 'orders.csv'
GROUP BY month ORDER BY month;

Wenn etwas schiefgeht

Alles kam als eine Spalte zurück. Das Trennzeichen ist kein Komma; Semikolons sind in weiten Teilen Europas Standard. read_csv('f.csv', delim = ';'). Wenn die Datei noch seltsamer ist, ist sie vielleicht wirklich defekt und nicht nur ungewöhnlich.

Eine numerische Spalte lässt sich nicht summieren. Sie wurde als Text erkannt, weil irgendwo etwas Nichtnumerisches drinsteht. Den Übeltäter findest du mit SELECT amount FROM 'f.csv' WHERE TRY_CAST(amount AS DOUBLE) IS NULL LIMIT 10;. Meist ist es ein Währungssymbol, ein Tausendertrennzeichen oder der String N/A.

Führende Nullen sind verschwunden. Die Spalte wurde als Zahl gelesen. Deklariere sie in columns als VARCHAR, und lass danach kein Tabellenkalkulationsprogramm in ihre Nähe.

Die Kopfzeile wiederholt sich in der ganzen Datei. Mehrere Exporte wurden aneinandergehängt. WHERE id <> 'id' ist die grobe Lösung und meist die richtige.

Der Speicher ist ausgegangen. DuckDB streamt, also steckt meist ein ORDER BY oder ein DISTINCT über die ganze Datei dahinter. Gib ihm ein temporäres Verzeichnis (SET temp_directory = '/tmp/duckdb'), dann lagert es auf die Platte aus. Oder lies wie du eine 1-GB-CSV-Datei öffnest, wenn es allgemeiner darum geht, was bei welcher Größe noch klappt.

Fragen

Kann man SQL auf einer CSV-Datei ausführen, ohne sie zu importieren?
Ja. DuckDB fragt eine CSV-Datei direkt mit `SELECT * FROM 'file.csv'` ab, erkennt die Spaltennamen aus der Kopfzeile und die Typen per Stichprobe. Nichts wird importiert, keine Tabelle angelegt, die Datei bleibt unverändert. SQLite kann das auch, aber der Schritt .import legt eine dauerhafte Tabelle an.
Wie frage ich eine große CSV-Datei am schnellsten mit SQL ab?
Mit DuckDB, und zwar mit großem Abstand unter den Optionen ohne Einrichtung. Es liest die Datei parallel und streamt, statt alles in den Speicher zu laden, deshalb kommt es mit Dateien klar, die viel größer als der RAM sind. Wenn du dieselbe Datei wiederholt abfragst, wandle sie einmal mit COPY … TO 'file.parquet' in Parquet um, dann laufen Scans fünf- bis zwanzigmal schneller.
Wie frage ich eine CSV-Spalte ab, deren Name ein Leerzeichen enthält?
Setz den Namen in doppelte Anführungszeichen: SELECT "Order Date" FROM 'orders.csv'. Einfache Anführungszeichen erzeugen stattdessen ein String-Literal, das für jede Zeile denselben Text zurückgibt, statt einen Fehler zu melden, sodass die Abfrage aussieht, als hätte sie funktioniert.
Kann ich SQL-Abfragen auf einer CSV-Datei auf meinem Handy ausführen?
Ja. Smart CSV Viewer führt SQL auf einer CSV-Datei unter iOS und Android aus, mit einem visuellen Filter-Builder als Alternative zum Tippen von Abfragen auf einer Handytastatur. Die Engine ist SQLite, also gilt der SQLite-Dialekt und nicht der von DuckDB.
Warum lässt sich meine numerische Spalte nicht summieren?
Sie wurde als Text erkannt, weil irgendwo darin etwas Nichtnumerisches steht: ein Währungssymbol, ein Tausendertrennzeichen oder ein Platzhalter wie N/A. Finde die betroffenen Zeilen mit TRY_CAST(col AS DOUBLE) IS NULL und bereinige sie, oder lies die Spalte ausdrücklich über das Argument `columns` von read_csv ein.
Wann sollte ich eine CSV-Datei doch in eine echte Datenbank laden?
In zwei Fällen: Die Daten werden wiederholt von mehreren Personen abgefragt, oder sie brauchen Constraints und Beziehungen, die eine einzelne flache Datei nicht ausdrücken kann. Eine einmalige Frage zu einer Datei, die dir jemand per E-Mail geschickt hat, braucht kein Schema und kein Ladeskript.

uos — Ich baue Smart CSV Viewer, der eine SQL-Engine aufs Handy bringt, deshalb stellt sich mir oft die Frage, was in eine Datenbank gehört.