mygems

Cómo ejecutar consultas SQL en un archivo CSV sin una base de datos (2026 guide)

No necesitas Postgres para un GROUP BY. Consulta un CSV directamente con DuckDB, SQLite, q o tu celular, y mira cuál conviene en cada situación.

Por uos ·

La versión corta: apunta DuckDB al archivo.

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

Sin esquema, sin importar, sin servidor, sin CREATE TABLE. El CSV se queda exactamente donde está y la fila de encabezados pasa a ser los nombres de las columnas. Si hoy instalas una sola cosa, instala esta.

El resto de esta página explica por qué funciona, qué hacer cuando no puedes usar DuckDB y cómo resolver los dos problemas con los que tropieza todo el mundo: poner comillas al nombre del archivo y las columnas cuyos nombres tienen espacios.

Por qué no necesitas cargar el archivo en ningún lado

El reflejo de «primero meter los datos en una base de datos» viene de cuando los motores SQL solo sabían leer su propio almacenamiento. Los motores modernos ya no tienen esa limitación. DuckDB, SQLite y varias herramientas pequeñas de línea de comandos tratan un CSV como una tabla durante una sola consulta, infieren los tipos tomando una muestra, responden y se olvidan de todo.

Eso cambia por completo la cuenta. Una pregunta puntual sobre un archivo que te llegó por correo ya no justifica un esquema, un script de carga y un paso de limpieza. Justifica una línea.

Cargar los datos en una base de datos de verdad sigue siendo lo correcto en exactamente dos casos: los datos los van a consultar varias personas de forma repetida, o necesitan restricciones y relaciones que un solo archivo plano no puede expresar. Ninguno describe «cuál es el total por región en esta exportación».

Las opciones, con honestidad

HerramientaInstalaciónIdeal paraDébil en
DuckDBUn solo binario, sin dependenciasTodo hasta decenas de GBCasi nada en esta tarea
SQLite + .importYa viene en macOS y LinuxConservar la tabla para despuésTipos: todo entra como TEXT
qpip install q-text-as-dataComandos rápidos de una línea en un pipeline de shellArchivos grandes; es más lento
csvsql (csvkit)pip install csvkitGenerar un CREATE TABLE para una base de datos realVelocidad: es el más lento de aquí
Pandas + pandasqlYa tienes pandasCuando el resultado alimenta más código en PythonEl dialecto SQL es el de SQLite, no el de Postgres
App de celular con SQLApp Store / PlayEl archivo llegó a tu correo, en el celularCualquier cosa que quieras automatizar

El ranking honesto es corto: DuckDB para casi todo, SQLite cuando no puedes instalar nada, y una app de celular cuando no estás frente a una computadora. Las demás están bien y tienen sus momentos, pero ninguna le gana a DuckDB en esta tarea concreta.

DuckDB a fondo

La instalación es un solo binario: brew install duckdb, o descárgalo y ponlo en tu path. Luego:

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

Hay dos cosas que debes saber desde el principio:

El nombre del archivo necesita comillas simples. FROM orders.csv se interpreta como un esquema llamado orders y una tabla llamada csv, y el mensaje de error no lo deja claro. FROM 'orders.csv' es lo correcto.

Los nombres de columna con espacios necesitan comillas dobles. SELECT "Order Date" FROM …. Con comillas simples obtienes la cadena literal, en silencio, en todas las filas, lo que parece que la consulta funcionó.

Usa un glob para leer todo un directorio cuando la exportación llegó en partes mensuales:

SELECT * FROM 'exports/*.csv'

Cuando la inferencia de tipos se equivoca, normalmente en una columna de IDs con ceros a la izquierda o en fechas con un formato que no reconoce, toma el control de la lectura en lugar de pelear con ella:

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

read_csv también acepta delim, quote, escape, ignore_errors = true y sample_size = -1 (escanear todo el archivo antes de decidir los tipos, en vez de las primeras 20 480 filas). Esta última es la solución cuando una columna parece numérica durante diez mil filas y luego se topa con N/A.

Guarda la respuesta en un archivo sin salir de SQL:

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

Y si vas a consultar el archivo más de un puñado de veces, conviértelo una vez con COPY (SELECT * FROM 'orders.csv') TO 'orders.parquet' y consulta el Parquet. Tiene tipos, está comprimido y normalmente se escanea de cinco a veinte veces más rápido.

SQLite, cuando no puedes instalar nada

sqlite3 viene con macOS y con la mayoría de las distribuciones de Linux, así que es la opción que funciona en una máquina restringida.

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

El inconveniente, y es importante: .import convierte todas las columnas en TEXT. El orden pasa a ser lexicográfico, así que 100 va antes que 20, y SUM() da sorpresas. Haz el cast al consultar (CAST(amount AS REAL)), o crea la tabla con tipos reales antes de importar.

A diferencia de DuckDB, esto deja un shop.db atrás, lo cual es justo lo que quieres si vas a necesitar la tabla mañana, y un estorbo si no.

q, para un pipeline de shell

Cuando la consulta es un paso dentro de una cadena de comandos de shell, q lee la entrada estándar como cualquier otra herramienta de Unix:

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

-H indica que la primera fila es un encabezado, -d, fija el delimitador y - es stdin. Por debajo es SQLite, así que el dialecto es el de SQLite. También es la opción razonable más lenta con un archivo grande: bien para unos cientos de miles de filas, penosa más allá de eso.

Cuando el archivo está en tu celular

Nada de lo anterior sirve cuando la exportación es un adjunto en tu correo y no estás en un escritorio. Pasa seguido, y la respuesta habitual, «envíatelo y míralo después», es mala.

CSV File Viewer - Smart CSViPhone y iPad · Android

Ejecuta SQL de verdad sobre el archivo en iOS y Android, con un constructor visual de filtros para cuando prefieres no escribir una consulta en el teclado del celular, y un asistente en lenguaje normal para cuando todavía no conoces los nombres de las columnas.

Qué hace

El dialecto es el de SQLite, así que las consultas de este artículo se trasladan con las salvedades de siempre: no hay funciones de ventana como en los ejemplos de DuckDB, y se aplican los mismos hábitos con CAST.

Consultas para tener a mano

Qué hay en este archivo:

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

Dónde están los nulos, porque ir columna por columna es tedioso:

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

Duplicados en una clave:

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

Unir dos exportaciones es la consulta que más a menudo justifica todo el ejercicio, porque hacerlo con BUSCARV entre dos hojas es mucho peor:

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

Un total mensual a partir de una columna de fechas:

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

Cuando algo sale mal

Todo llegó en una sola columna. El delimitador no es una coma; el punto y coma es lo habitual en buena parte de Europa. read_csv('f.csv', delim = ';'). Si el archivo es más raro que eso, quizá esté realmente dañado y no solo sea inusual.

Una columna numérica no suma. Se infirió como texto porque aparece algo no numérico en ella. Encuentra al culpable con SELECT amount FROM 'f.csv' WHERE TRY_CAST(amount AS DOUBLE) IS NULL LIMIT 10;. Suele ser un símbolo de moneda, un separador de miles o la cadena N/A.

Desaparecieron los ceros a la izquierda. La columna se leyó como número. Declárala VARCHAR en columns, y no dejes que una hoja de cálculo se acerque después.

La fila de encabezados se repite a lo largo del archivo. Se concatenaron varias exportaciones. WHERE id <> 'id' es la solución burda y normalmente la correcta.

Se quedó sin memoria. DuckDB procesa en flujo, así que normalmente esto significa un ORDER BY o un DISTINCT sobre todo el archivo. Dale un directorio temporal (SET temp_directory = '/tmp/duckdb') y escribirá en disco. O lee cómo abrir un archivo CSV de 1 GB para ver la pregunta más amplia de qué aguanta qué a gran escala.

Preguntas

¿Se puede ejecutar SQL sobre un archivo CSV sin importarlo?
Sí. DuckDB consulta un CSV directamente con `SELECT * FROM 'file.csv'`, infiriendo los nombres de columna de la fila de encabezados y los tipos con una muestra. No se importa nada, no se crea ninguna tabla y el archivo no cambia. SQLite también puede, pero su paso .import sí crea una tabla persistente.
¿Cuál es la forma más rápida de consultar un CSV grande con SQL?
DuckDB, por un margen enorme entre las opciones que no requieren configuración. Lee el archivo en paralelo y trabaja en flujo en lugar de cargarlo todo en memoria, así que maneja archivos mucho más grandes que la RAM. Si vas a consultar el mismo archivo varias veces, conviértelo una vez a Parquet con COPY … TO 'file.parquet' y los escaneos serán de cinco a veinte veces más rápidos.
¿Cómo consulto una columna de un CSV cuyo nombre tiene un espacio?
Ponla entre comillas dobles: SELECT "Order Date" FROM 'orders.csv'. Las comillas simples producen un literal de cadena, que devuelve el mismo texto en cada fila en lugar de un error, así que la consulta parece haber funcionado.
¿Puedo ejecutar consultas SQL sobre un archivo CSV en mi celular?
Sí. Smart CSV Viewer ejecuta SQL sobre un CSV en iOS y Android, con un constructor visual de filtros como alternativa a escribir consultas en el teclado del celular. El motor es SQLite, así que el dialecto es el de SQLite y no el de DuckDB.
¿Por qué mi columna numérica no se deja sumar?
Se infirió como texto porque en algún lugar aparece algo no numérico: un símbolo de moneda, un separador de miles o un marcador como N/A. Encuentra las filas problemáticas con TRY_CAST(col AS DOUBLE) IS NULL y luego límpialas, o lee la columna de forma explícita con el argumento `columns` de read_csv.
¿Cuándo debería cargar un CSV en una base de datos de verdad?
En dos casos: los datos los van a consultar varias personas de forma repetida, o necesitan restricciones y relaciones que un solo archivo plano no puede expresar. Una pregunta puntual sobre un archivo que te llegó por correo no necesita un esquema ni un script de carga.

uos — Desarrollo Smart CSV Viewer, que pone un motor SQL en el celular, así que la pregunta de qué merece una base de datos surge a menudo.