mygems

Exécuter des requêtes SQL sur un fichier CSV sans base de données (2026 guide)

Inutile d'avoir Postgres pour un GROUP BY. Interrogez un CSV sur place avec DuckDB, SQLite, q ou votre téléphone, et voyez quel outil convient à quel cas.

Par uos ·

En bref : pointez DuckDB vers le fichier.

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

Pas de schéma, pas d'import, pas de serveur, pas de CREATE TABLE. Le CSV reste exactement où il est, et la ligne d'en-tête devient les noms de colonnes. Si vous n'installez qu'un seul outil aujourd'hui, installez celui-là.

Le reste de cette page explique pourquoi ça marche, que faire quand DuckDB n'est pas disponible, et comment gérer les deux pièges habituels : les guillemets autour d'un nom de fichier, et les colonnes dont le nom contient des espaces.

Pourquoi il n'y a pas besoin de charger le fichier quelque part

Le réflexe de « d'abord mettre les données dans une base » date de l'époque où les moteurs SQL ne savaient lire que leur propre stockage. Les moteurs modernes n'ont plus cette limite. DuckDB, SQLite et plusieurs petits outils en ligne de commande traitent un CSV comme une table le temps d'une requête, déduisent les types en échantillonnant le fichier, répondent, puis oublient tout.

Cela change complètement le calcul. Une question ponctuelle sur un fichier reçu par e-mail ne justifie plus un schéma, un script de chargement et une étape de nettoyage. Elle justifie une ligne.

Charger les données dans une vraie base reste la bonne option dans exactement deux cas : les données seront interrogées à répétition par plusieurs personnes, ou elles ont besoin de contraintes et de relations qu'un simple fichier plat ne peut pas exprimer. Ni l'un ni l'autre ne décrit « quel est le total par région dans cet export ».

Les options, honnêtement

OutilInstallationIdéal pourFaible sur
DuckDBUn seul binaire, aucune dépendanceTout jusqu'à plusieurs dizaines de GoPresque rien pour cette tâche
SQLite + .importDéjà présent sur macOS et LinuxGarder la table pour plus tardLes types : tout arrive en TEXT
qpip install q-text-as-dataDes one-liners rapides dans un pipeline shellLes gros fichiers ; il est plus lent
csvsql (csvkit)pip install csvkitGénérer un CREATE TABLE pour une vraie baseLa vitesse : le plus lent d'ici
Pandas + pandasqlVous avez déjà pandasQuand le résultat alimente d'autre code PythonLe dialecte SQL est celui de SQLite, pas de Postgres
Appli mobile avec SQLApp Store / PlayLe fichier est arrivé par e-mail, sur votre téléphoneTout ce que vous voudriez automatiser

Le classement honnête est court : DuckDB pour presque tout, SQLite quand vous ne pouvez rien installer, et une appli mobile quand vous n'êtes pas devant un ordinateur. Les autres sont corrects et ont leurs moments, mais aucun ne bat DuckDB sur ce travail précis.

DuckDB, en détail

L'installation tient en un seul binaire : brew install duckdb, ou téléchargez-le et placez-le dans votre path. Ensuite :

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

Deux choses à savoir tout de suite :

Le nom du fichier demande des guillemets simples. FROM orders.csv est interprété comme un schéma orders et une table csv, et le message d'erreur ne le dit pas clairement. FROM 'orders.csv' est la bonne forme.

Les noms de colonnes avec espaces demandent des guillemets doubles. SELECT "Order Date" FROM …. Avec des guillemets simples, vous obtenez la chaîne littérale, en silence, pour chaque ligne, ce qui donne l'impression que la requête a fonctionné.

Utilisez un glob pour tout un dossier quand l'export est arrivé en morceaux mensuels :

SELECT * FROM 'exports/*.csv'

Quand la détection des types se trompe, en général sur une colonne d'identifiants avec des zéros en tête ou sur des dates dans un format qu'elle ne reconnaît pas, prenez la main sur la lecture plutôt que de lutter contre elle :

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

read_csv accepte aussi delim, quote, escape, ignore_errors = true et sample_size = -1 (analyser tout le fichier avant de choisir les types, au lieu des 20 480 premières lignes). Ce dernier est la solution quand une colonne paraît numérique pendant dix mille lignes puis tombe sur N/A.

Réécrire le résultat sans quitter SQL :

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

Et si le fichier doit être interrogé plus de quelques fois, convertissez-le une fois avec COPY (SELECT * FROM 'orders.csv') TO 'orders.parquet' et interrogez le Parquet à la place. Il est typé, compressé, et généralement de cinq à vingt fois plus rapide à parcourir.

SQLite, quand vous ne pouvez rien installer

sqlite3 est livré avec macOS et la plupart des distributions Linux : c'est donc l'option qui marche sur une machine verrouillée.

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

Le piège, et il compte : .import met toutes les colonnes en TEXT. Le tri devient alors lexicographique, donc 100 passe avant 20, et SUM() réserve des surprises. Faites un cast au moment de la requête (CAST(amount AS REAL)), ou créez la table avec de vrais types avant d'importer.

Contrairement à DuckDB, cela laisse un shop.db derrière lui : c'est voulu si vous voulez retrouver la table demain, et du désordre sinon.

q, pour un pipeline shell

Quand la requête n'est qu'une étape dans une chaîne de commandes shell, q lit l'entrée standard comme n'importe quel outil Unix :

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

-H indique que la première ligne est un en-tête, -d, fixe le séparateur, et - désigne stdin. C'est du SQLite en dessous, donc le dialecte est celui de SQLite. C'est aussi l'option raisonnable la plus lente sur un gros fichier : correct pour quelques centaines de milliers de lignes, pénible au-delà.

Quand le fichier est sur votre téléphone

Rien de tout cela n'aide quand l'export est une pièce jointe dans votre messagerie et que vous n'êtes pas à un bureau. Ça arrive souvent, et la réponse habituelle, « envoyez-le-vous et regardez plus tard », est mauvaise.

CSV File Viewer - Smart CSViPhone et iPad · Android

Exécute du vrai SQL sur le fichier, sur iOS et Android, avec un générateur de filtres visuel pour quand vous préférez ne pas taper une requête sur un clavier de téléphone, et un assistant en langage courant pour quand vous ne connaissez pas encore les noms des colonnes.

Ce que ça fait

Le dialecte est celui de SQLite : les requêtes de cet article se transposent donc avec les réserves habituelles. Pas de fonctions de fenêtrage comme dans les exemples DuckDB, et les mêmes habitudes de CAST s'appliquent.

Des requêtes à garder sous la main

Ce que contient ce fichier :

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

Où sont les valeurs nulles, colonne par colonne étant fastidieux, donc :

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

Doublons sur une clé :

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

Joindre deux exports est la requête qui justifie le plus souvent tout l'exercice, car le faire avec RECHERCHEV entre deux feuilles est bien pire :

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

Un total mensuel à partir d'une colonne de dates :

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

Quand ça se passe mal

Tout est revenu dans une seule colonne. Le séparateur n'est pas une virgule ; le point-virgule est la norme dans une bonne partie de l'Europe. read_csv('f.csv', delim = ';'). Si le fichier est plus étrange que ça, il est peut-être réellement corrompu et pas seulement inhabituel.

Une colonne numérique ne se somme pas. Elle a été déduite comme du texte, parce qu'une valeur non numérique s'y trouve. Trouvez le coupable avec SELECT amount FROM 'f.csv' WHERE TRY_CAST(amount AS DOUBLE) IS NULL LIMIT 10;. C'est généralement un symbole monétaire, un séparateur de milliers ou la chaîne N/A.

Les zéros en tête ont disparu. La colonne a été lue comme un nombre. Déclarez-la VARCHAR dans columns, et ne laissez aucun tableur s'en approcher ensuite.

La ligne d'en-tête est répétée dans tout le fichier. Plusieurs exports ont été concaténés. WHERE id <> 'id' est la solution brutale, et généralement la bonne.

Mémoire insuffisante. DuckDB travaille en flux, donc cela vient normalement d'un ORDER BY ou d'un DISTINCT sur tout le fichier. Donnez-lui un répertoire temporaire (SET temp_directory = '/tmp/duckdb') et il écrira sur le disque. Ou lisez comment ouvrir un fichier CSV de 1 Go pour la question plus large de ce qui tient à quelle échelle.

Questions

Peut-on exécuter du SQL sur un fichier CSV sans l'importer ?
Oui. DuckDB interroge un CSV sur place avec `SELECT * FROM 'file.csv'`, en déduisant les noms de colonnes de la ligne d'en-tête et les types par échantillonnage. Rien n'est importé, aucune table n'est créée et le fichier reste inchangé. SQLite le permet aussi, mais son étape .import crée bien une table persistante.
Quel est le moyen le plus rapide d'interroger un gros CSV en SQL ?
DuckDB, de loin parmi les options sans configuration. Il lit le fichier en parallèle et travaille en flux au lieu de tout charger en mémoire, il gère donc des fichiers bien plus gros que la RAM. Si vous interrogez plusieurs fois le même fichier, convertissez-le une fois en Parquet avec COPY … TO 'file.parquet', et les parcours seront de cinq à vingt fois plus rapides.
Comment interroger une colonne CSV dont le nom contient un espace ?
Mettez-la entre guillemets doubles : SELECT "Order Date" FROM 'orders.csv'. Les guillemets simples produisent à la place un littéral de chaîne, qui renvoie le même texte pour chaque ligne au lieu d'une erreur, si bien que la requête a l'air d'avoir fonctionné.
Puis-je exécuter des requêtes SQL sur un fichier CSV depuis mon téléphone ?
Oui. Smart CSV Viewer exécute du SQL sur un CSV sous iOS et Android, avec un générateur de filtres visuel comme alternative à la saisie de requêtes sur un clavier de téléphone. Le moteur est SQLite, donc le dialecte est celui de SQLite et non celui de DuckDB.
Pourquoi ma colonne numérique refuse-t-elle de se sommer ?
Elle a été déduite comme du texte parce qu'une valeur non numérique s'y trouve quelque part : un symbole monétaire, un séparateur de milliers ou un marqueur comme N/A. Repérez les lignes fautives avec TRY_CAST(col AS DOUBLE) IS NULL, puis nettoyez-les ou lisez la colonne explicitement avec l'argument `columns` de read_csv.
Quand faut-il plutôt charger un CSV dans une vraie base de données ?
Dans deux cas : les données seront interrogées à répétition par plusieurs personnes, ou elles ont besoin de contraintes et de relations qu'un simple fichier plat ne peut pas exprimer. Une question ponctuelle sur un fichier reçu par e-mail n'a pas besoin d'un schéma ni d'un script de chargement.

uos — Je développe Smart CSV Viewer, qui met un moteur SQL sur un téléphone, donc la question de ce qui mérite une base de données revient souvent.