SQL es el idioma en el que se le piden datos a una base de datos. Lo hablan los bancos, las tiendas en línea, la app de tu móvil y casi cualquier programa que guarde información, y es de los lenguajes que más se usan en el mundo: lo necesitan quienes programan, quienes analizan datos y quienes solo quieren sacar un informe sin esperar a nadie. Este curso empieza desde cero y no necesitas instalar nada: todas las consultas se pueden ejecutar en el editor de SQL del navegador, que ya trae cargada la base de ejemplo.
La ruta: ocho lecciones
Están pensadas para leerse en orden. Las dos primeras están en esta misma página y las demás tienen la suya. Todas usan la misma base de ejemplo, una pequeña tienda de informática, así que los resultados que ves en el texto son los que obtendrás tú. Debajo de casi cada consulta hay un enlace que la abre en el editor para que la cambies, y al final de cada lección hay retos con la solución escondida. Cuando el motor importa, un recuadro «En PostgreSQL y MySQL» muestra qué cambia en los otros dos motores más usados.
- 1Primeros pasosQué es una base de datos relacional, la base de ejemplo y la primera consulta.En esta página
- 2ConsultarSELECT, WHERE, ORDER BY, LIMIT y DISTINCT: elegir columnas, filtrar y ordenar.En esta página
- 3Agrupar y resumirCOUNT, SUM y AVG, GROUP BY, HAVING y el orden en que se ejecuta una consulta.Leer la lección →
- 4JOINUnir tablas por sus claves, encontrar lo que falta y por qué un JOIN puede duplicar filas.Leer la lección →
- 5Subconsultas y CTEConsultas dentro de consultas, WITH y la CTE recursiva para recorrer un organigrama.Leer la lección →
- 6Crear y modificarCREATE TABLE, restricciones, INSERT, UPDATE, DELETE y transacciones.Leer la lección →
- 7Índices y rendimientoQué es un índice, cómo leer un plan y una tabla de un millón de filas para medirlo.Leer la lección →
- 8Ventanas y SQL desde códigoROW_NUMBER, totales acumulados, LAG y consultas desde Python y Node sin inyección SQL.Leer la lección →
Lección 1. Primeros pasos
Tablas, filas y claves
Una base de datos relacional guarda la información en tablas, como hojas de cálculo con reglas estrictas: cada tabla tiene unas columnas fijas, con un nombre y un tipo, y cada fila es un registro. Una tabla clientes tiene una fila por cliente y columnas como nombre o pais.
Dos ideas sostienen todo lo demás. La clave primaria es la columna que identifica cada fila sin repetirse, casi siempre un id numérico. La clave foránea es una columna que guarda la clave primaria de otra tabla: cada pedido no repite el nombre y la ciudad del cliente, solo guarda su cliente_id. Así cada dato está en un solo sitio, y las tablas se relacionan por esas claves. De ahí el nombre.
Esta es la base que usaremos en todo el curso: 12 clientes de seis países, 15 productos en 5 categorías, 22 pedidos con 41 líneas de detalle y 8 empleados. Tiene algunos huecos puestos a propósito, porque en los datos reales siempre los hay: un cliente sin ciudad, otro sin correo, uno que nunca ha comprado, un producto sin categoría, otro que nunca se ha vendido y un pedido cancelado sin líneas. Irán saliendo a lo largo de las lecciones.
SQLite, PostgreSQL y MySQL
SQL es un estándar, pero cada motor de base de datos lo implementa con su propio dialecto. Los tres más usados son estos, y lo que aprendas aquí sirve en los tres:
| Motor | Cómo funciona | Dónde lo encontrarás |
|---|---|---|
| SQLite | Una biblioteca: la base entera es un archivo, sin servidor | Móviles, navegadores, aplicaciones de escritorio, prototipos |
| PostgreSQL | Un servidor al que se conectan los programas | Aplicaciones web y empresas; el más completo de los libres |
| MySQL | Un servidor, como PostgreSQL | Muchísima web clásica, WordPress incluido |
El curso usa SQLite, porque no hay que instalar ningún servidor y es el que corre dentro del editor del navegador. Todas las consultas se comprobaron con SQLite 3.46.1, y las diferencias con PostgreSQL 17 y MySQL 8.4 se comprobaron en esos motores.
Dónde escribir las consultas
La forma más rápida es el editor de SQL: escribes la consulta, pulsas «Ejecutar» y ves la tabla de resultados. Si prefieres trabajar en tu equipo, instala la consola sqlite3: en macOS ya viene instalada; en Linux está en los paquetes de cualquier distribución (en Debian y Ubuntu, sudo apt install sqlite3), y para Windows se descarga de sqlite.org. Guarda el script de la base, que tienes completo al final de esta lección, como tienda.sql, y crea con él la base:
sqlite3 tienda.db < tienda.sql sqlite3 tienda.db
La segunda orden abre la consola sobre el archivo tienda.db. Dentro, las órdenes que empiezan por punto son de la consola, no de SQL: .tables lista las tablas y .mode table hace que los resultados salgan en una tabla dibujada.
sqlite> .tables sqlite> .mode table sqlite> SELECT nombre, pais FROM clientes LIMIT 3;
sqlite3 3.46.1categorias clientes detalle empleados pedidos productos +----------------+----------+ | nombre | pais | +----------------+----------+ | Ana Torres | México | | Luis Fernández | España | | Camila Rojas | Colombia | +----------------+----------+
La primera consulta
Pedir datos se hace con SELECT: qué columnas quieres y de qué tabla. El asterisco significa «todas las columnas»:
SELECT * FROM categorias;
Ábrela en el editor y cámbiala →
resultado+----+----------------+ | id | nombre | +----+----------------+ | 1 | Portátiles | | 2 | Monitores | | 3 | Periféricos | | 4 | Almacenamiento | | 5 | Redes | +----+----------------+
En la práctica casi nunca se usa *: se piden las columnas que hacen falta, en el orden que conviene. El resultado de una consulta es siempre otra tabla, aunque tenga una sola fila o una sola columna.
SELECT nombre, ciudad, pais FROM clientes;
Ábrela en el editor y cámbiala →
resultado+----------------+------------------+-----------+ | nombre | ciudad | pais | +----------------+------------------+-----------+ | Ana Torres | Ciudad de México | México | | Luis Fernández | Madrid | España | | Camila Rojas | Bogotá | Colombia | | Diego Martínez | Guadalajara | México | | Valentina Díaz | Buenos Aires | Argentina | | Jorge Ramírez | Monterrey | México | | Lucía Gómez | Barcelona | España | | Mateo Herrera | NULL | Chile | | Sofía Castro | Lima | Perú | | Andrés Navarro | Ciudad de México | México | | Paula Morales | Madrid | España | | Tomás Ortega | Santiago | Chile | +----------------+------------------+-----------+
Fíjate en Mateo Herrera: su ciudad sale como NULL. NULL no es un texto vacío ni un cero, sino la ausencia de dato: «no sabemos». Se comporta de una forma muy particular, que veremos en la lección 2.
Cuatro reglas de escritura
- Mayúsculas: las palabras de SQL (
SELECT,FROM) se escriben en mayúsculas por costumbre, para distinguirlas de los nombres de tablas y columnas, pero da igual:selectfunciona exactamente igual. - Punto y coma: cierra cada sentencia. En el editor y en la consola es lo que separa una consulta de la siguiente.
- Comillas: los textos van entre comillas simples,
'México'. Las dobles se reservan para nombres de columnas o tablas raros, con espacios o tildes. - Comentarios: todo lo que va detrás de
--hasta el final de la línea se ignora.
Ver el script completo de la base «tienda» (para crearla en tu equipo)
CREATE TABLE categorias ( id INTEGER PRIMARY KEY, nombre VARCHAR(40) NOT NULL UNIQUE ); CREATE TABLE clientes ( id INTEGER PRIMARY KEY, nombre VARCHAR(60) NOT NULL, ciudad VARCHAR(40), pais VARCHAR(40) NOT NULL, email VARCHAR(80) UNIQUE, alta DATE NOT NULL ); CREATE TABLE productos ( id INTEGER PRIMARY KEY, nombre VARCHAR(60) NOT NULL, categoria_id INTEGER REFERENCES categorias(id), precio NUMERIC(10,2) NOT NULL CHECK (precio > 0), stock INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE pedidos ( id INTEGER PRIMARY KEY, cliente_id INTEGER NOT NULL REFERENCES clientes(id), fecha DATE NOT NULL, estado VARCHAR(20) NOT NULL ); CREATE TABLE detalle ( pedido_id INTEGER NOT NULL REFERENCES pedidos(id), producto_id INTEGER NOT NULL REFERENCES productos(id), cantidad INTEGER NOT NULL CHECK (cantidad > 0), precio_unitario NUMERIC(10,2) NOT NULL, PRIMARY KEY (pedido_id, producto_id) ); CREATE TABLE empleados ( id INTEGER PRIMARY KEY, nombre VARCHAR(60) NOT NULL, puesto VARCHAR(40) NOT NULL, jefe_id INTEGER REFERENCES empleados(id), salario NUMERIC(10,2) NOT NULL ); INSERT INTO categorias (id, nombre) VALUES (1, 'Portátiles'), (2, 'Monitores'), (3, 'Periféricos'), (4, 'Almacenamiento'), (5, 'Redes'); INSERT INTO clientes (id, nombre, ciudad, pais, email, alta) VALUES (1, 'Ana Torres', 'Ciudad de México', 'México', 'ana.torres@ejemplo.com', '2025-01-14'), (2, 'Luis Fernández', 'Madrid', 'España', 'luis.f@ejemplo.com', '2025-02-03'), (3, 'Camila Rojas', 'Bogotá', 'Colombia', 'camila.r@ejemplo.com', '2025-02-27'), (4, 'Diego Martínez', 'Guadalajara', 'México', 'diego.m@ejemplo.com', '2025-04-09'), (5, 'Valentina Díaz', 'Buenos Aires', 'Argentina', 'vale.diaz@ejemplo.com', '2025-05-21'), (6, 'Jorge Ramírez', 'Monterrey', 'México', 'jorge.r@ejemplo.com', '2025-06-30'), (7, 'Lucía Gómez', 'Barcelona', 'España', 'lucia.g@ejemplo.com', '2025-08-12'), (8, 'Mateo Herrera', NULL, 'Chile', 'mateo.h@ejemplo.com', '2025-09-05'), (9, 'Sofía Castro', 'Lima', 'Perú', NULL, '2025-11-18'), (10, 'Andrés Navarro', 'Ciudad de México', 'México', 'andres.n@ejemplo.com', '2026-01-07'), (11, 'Paula Morales', 'Madrid', 'España', 'paula.m@ejemplo.com', '2026-03-02'), (12, 'Tomás Ortega', 'Santiago', 'Chile', 'tomas.o@ejemplo.com', '2026-05-16'); INSERT INTO productos (id, nombre, categoria_id, precio, stock) VALUES (1, 'Portátil Aero 14', 1, 1299.99, 8), (2, 'Portátil Nova 16', 1, 1849.00, 3), (3, 'Monitor 27" 4K', 2, 429.90, 12), (4, 'Monitor 24" FHD', 2, 159.00, 20), (5, 'Teclado mecánico', 3, 89.50, 35), (6, 'Ratón inalámbrico', 3, 24.99, 60), (7, 'Auriculares con micrófono', 3, 59.00, 0), (8, 'SSD 1 TB', 4, 94.90, 40), (9, 'Disco externo 4 TB', 4, 119.00, 15), (10, 'Memoria USB 128 GB', 4, 14.50, 100), (11, 'Router Wi-Fi 7', 5, 249.00, 6), (12, 'Switch 8 puertos', 5, 39.90, 18), (13, 'Base para portátil', NULL, 29.00, 25), (14, 'Webcam 4K', 3, 139.00, 9), (15, 'Cable HDMI 2 m', NULL, 9.99, 200); INSERT INTO pedidos (id, cliente_id, fecha, estado) VALUES (1, 1, '2026-01-10', 'entregado'), (2, 2, '2026-01-15', 'entregado'), (3, 3, '2026-01-28', 'entregado'), (4, 1, '2026-02-04', 'entregado'), (5, 4, '2026-02-19', 'entregado'), (6, 5, '2026-03-03', 'entregado'), (7, 2, '2026-03-11', 'devuelto'), (8, 6, '2026-03-25', 'entregado'), (9, 7, '2026-04-08', 'entregado'), (10, 3, '2026-04-22', 'entregado'), (11, 8, '2026-05-06', 'entregado'), (12, 1, '2026-05-19', 'entregado'), (13, 9, '2026-06-02', 'cancelado'), (14, 10, '2026-06-17', 'entregado'), (15, 4, '2026-07-01', 'entregado'), (16, 11, '2026-07-14', 'entregado'), (17, 5, '2026-08-05', 'entregado'), (18, 2, '2026-08-21', 'entregado'), (19, 6, '2026-09-09', 'enviado'), (20, 10, '2026-09-23', 'enviado'), (21, 7, '2026-10-02', 'pendiente'), (22, 1, '2026-10-12', 'pendiente'); INSERT INTO detalle (pedido_id, producto_id, cantidad, precio_unitario) VALUES (1, 1, 1, 1299.99), (1, 6, 1, 24.99), (2, 3, 2, 429.90), (3, 5, 1, 89.50), (3, 6, 2, 24.99), (3, 10, 3, 14.50), (4, 8, 2, 94.90), (5, 2, 1, 1849.00), (5, 4, 1, 159.00), (6, 11, 1, 249.00), (6, 12, 2, 39.90), (7, 14, 1, 139.00), (8, 1, 2, 1299.99), (8, 3, 2, 429.90), (8, 5, 2, 89.50), (9, 9, 1, 119.00), (9, 10, 2, 14.50), (10, 7, 2, 59.00), (10, 12, 1, 39.90), (11, 4, 2, 159.00), (11, 6, 2, 24.99), (11, 13, 1, 29.00), (12, 3, 1, 429.90), (14, 8, 4, 94.90), (14, 9, 1, 119.00), (15, 5, 1, 89.50), (15, 6, 1, 24.99), (16, 1, 1, 1249.99), (16, 14, 1, 139.00), (17, 12, 1, 39.90), (17, 10, 2, 14.50), (18, 2, 1, 1799.00), (18, 3, 1, 429.90), (19, 11, 1, 249.00), (20, 4, 3, 159.00), (20, 10, 5, 14.50), (21, 8, 1, 94.90), (21, 7, 1, 59.00), (22, 5, 1, 89.50), (22, 6, 1, 24.99), (22, 12, 1, 39.90); INSERT INTO empleados (id, nombre, puesto, jefe_id, salario) VALUES (1, 'Elena Vargas', 'Directora', NULL, 6200.00), (2, 'Raúl Peña', 'Jefe de ventas', 1, 4100.00), (3, 'Marta Ruiz', 'Jefa de almacén', 1, 3900.00), (4, 'Iván Soto', 'Vendedor', 2, 2400.00), (5, 'Carla Medina', 'Vendedora', 2, 2550.00), (6, 'Hugo Salas', 'Vendedor', 2, 2300.00), (7, 'Nadia Fuentes', 'Mozo de almacén', 3, 1900.00), (8, 'Óscar Lara', 'Mozo de almacén', 3, 1850.00);
Lección 2. Consultar: elegir, filtrar y ordenar
Columnas calculadas y alias
En el SELECT no solo van columnas: también expresiones. Con AS se le da nombre a la columna del resultado, el alias. Aquí, el precio con un 16 % de IVA, redondeado a dos decimales con ROUND:
SELECT nombre,
precio,
ROUND(precio * 1.16, 2) AS precio_con_iva
FROM productos;Ábrela en el editor y cámbiala →
resultado+---------------------------+---------+----------------+ | nombre | precio | precio_con_iva | +---------------------------+---------+----------------+ | Portátil Aero 14 | 1299.99 | 1507.99 | | Portátil Nova 16 | 1849 | 2144.84 | | Monitor 27" 4K | 429.9 | 498.68 | | Monitor 24" FHD | 159 | 184.44 | | Teclado mecánico | 89.5 | 103.82 | | Ratón inalámbrico | 24.99 | 28.99 | | Auriculares con micrófono | 59 | 68.44 | | SSD 1 TB | 94.9 | 110.08 | | Disco externo 4 TB | 119 | 138.04 | | Memoria USB 128 GB | 14.5 | 16.82 | | Router Wi-Fi 7 | 249 | 288.84 | | Switch 8 puertos | 39.9 | 46.28 | | Base para portátil | 29 | 33.64 | | Webcam 4K | 139 | 161.24 | | Cable HDMI 2 m | 9.99 | 11.59 | +---------------------------+---------+----------------+
Hay un detalle de SQLite en la columna precio: el portátil Nova 16 sale a 1849 y el monitor de 27 pulgadas a 429.9, aunque en el script se escribieron como 1849.00 y 429.90. SQLite guarda el primero como el entero 1849, porque no pierde nada al hacerlo, y el segundo como un número decimal, que se muestra sin el cero final. Los tipos de SQLite son más flexibles que los de otros motores, y lo veremos en la lección 6. Para mostrar siempre dos decimales, printf('%.2f', precio).
WHERE: qué filas
WHERE se queda con las filas que cumplen una condición. Se pueden comparar números, textos y fechas con =, <> (distinto), <, >, <= y >=, y combinar condiciones con AND, OR y NOT:
SELECT nombre, precio, stock FROM productos WHERE precio < 100 AND stock > 30;
Ábrela en el editor y cámbiala →
resultado+--------------------+--------+-------+ | nombre | precio | stock | +--------------------+--------+-------+ | Teclado mecánico | 89.5 | 35 | | Ratón inalámbrico | 24.99 | 60 | | SSD 1 TB | 94.9 | 40 | | Memoria USB 128 GB | 14.5 | 100 | | Cable HDMI 2 m | 9.99 | 200 | +--------------------+--------+-------+
Cuidado al mezclar AND y OR: AND se evalúa primero, igual que la multiplicación antes que la suma. Si quieres los clientes de México o de España que se dieron de alta en 2026, los paréntesis no son opcionales:
-- Sin paréntesis: México (de cualquier año) O España de 2026 SELECT nombre, pais, alta FROM clientes WHERE pais = 'México' OR pais = 'España' AND alta >= '2026-01-01'; -- Con paréntesis: (México o España) Y de 2026 SELECT nombre, pais, alta FROM clientes WHERE (pais = 'México' OR pais = 'España') AND alta >= '2026-01-01';
Ábrela en el editor y cámbiala →
sin paréntesis+----------------+--------+------------+ | nombre | pais | alta | +----------------+--------+------------+ | Ana Torres | México | 2025-01-14 | | Diego Martínez | México | 2025-04-09 | | Jorge Ramírez | México | 2025-06-30 | | Andrés Navarro | México | 2026-01-07 | | Paula Morales | España | 2026-03-02 | +----------------+--------+------------+
con paréntesis+----------------+--------+------------+ | nombre | pais | alta | +----------------+--------+------------+ | Andrés Navarro | México | 2026-01-07 | | Paula Morales | España | 2026-03-02 | +----------------+--------+------------+
Las fechas de la base están en el formato AAAA-MM-DD, el del estándar ISO. Con ese formato, comparar fechas como textos da el orden correcto, que es lo que hace SQLite, porque no tiene un tipo de fecha propio.
IN, BETWEEN y LIKE
Tres atajos para condiciones frecuentes: IN para una lista de valores, BETWEEN para un intervalo (con los dos extremos incluidos) y LIKE para buscar dentro de un texto, donde % significa «cualquier cosa» y _ «un carácter cualquiera»:
SELECT nombre, pais FROM clientes WHERE pais IN ('Chile', 'Perú', 'Argentina');
SELECT nombre, precio FROM productos WHERE precio BETWEEN 50 AND 150;
SELECT nombre FROM productos WHERE nombre LIKE 'Monitor%';Ábrela en el editor y cámbiala →
IN+----------------+-----------+ | nombre | pais | +----------------+-----------+ | Valentina Díaz | Argentina | | Mateo Herrera | Chile | | Sofía Castro | Perú | | Tomás Ortega | Chile | +----------------+-----------+
BETWEEN+---------------------------+--------+ | nombre | precio | +---------------------------+--------+ | Teclado mecánico | 89.5 | | Auriculares con micrófono | 59 | | SSD 1 TB | 94.9 | | Disco externo 4 TB | 119 | | Webcam 4K | 139 | +---------------------------+--------+
LIKE+-----------------+ | nombre | +-----------------+ | Monitor 27" 4K | | Monitor 24" FHD | +-----------------+
En SQLite, LIKE no distingue mayúsculas de minúsculas, pero solo en las letras sin tilde: '%torres%' encuentra a Ana Torres, y '%DÍAZ%' no encuentra a Valentina Díaz, porque para SQLite la Í y la í son letras distintas.
SELECT nombre FROM clientes WHERE nombre LIKE '%torres%'; SELECT nombre FROM clientes WHERE nombre LIKE '%DÍAZ%';
Ábrela en el editor y cámbiala →
'%torres%'+------------+ | nombre | +------------+ | Ana Torres | +------------+
'%DÍAZ%'(sin filas)
En PostgreSQL y MySQL
Aquí cada motor hace una cosa distinta. PostgreSQL distingue siempre mayúsculas y minúsculas en LIKE (para no distinguirlas tiene ILIKE), así que '%torres%' no encuentra nada:
SELECT nombre FROM clientes WHERE nombre LIKE '%torres%';
PostgreSQL 17 nombre -------- (0 rows)
MySQL, con su configuración de fábrica (la intercalación utf8mb4_0900_ai_ci: ai es «sin tildes», ci, «sin mayúsculas»), no distingue ni mayúsculas ni tildes: '%diaz%' encuentra a Valentina Díaz.
SELECT nombre FROM clientes WHERE nombre LIKE '%diaz%';
MySQL 8.4+-----------------+ | nombre | +-----------------+ | Valentina Díaz | +-----------------+
NULL: el dato que falta
Para buscar los NULL hay que escribir IS NULL (o IS NOT NULL). Con el signo igual no funciona, y no da ningún error: simplemente no devuelve nada.
SELECT nombre, email FROM clientes WHERE email = NULL; SELECT nombre, email FROM clientes WHERE email IS NULL;
Ábrela en el editor y cámbiala →
= NULL(sin filas)
IS NULL+--------------+-------+ | nombre | email | +--------------+-------+ | Sofía Castro | NULL | +--------------+-------+
La razón es que NULL significa «desconocido», y comparar algo con un desconocido da otro desconocido, ni verdadero ni falso. WHERE solo se queda con las filas en las que la condición es verdadera. Esta lógica de tres valores tiene consecuencias sorprendentes, y varias están en las rarezas de SQL.
ORDER BY, LIMIT y DISTINCT
Sin ORDER BY, el orden de las filas no está garantizado: el motor las devuelve como le resulte cómodo. Para ordenar, ORDER BY con una o varias columnas, cada una con ASC (ascendente, la opción por omisión) o DESC. LIMIT corta el resultado, y con OFFSET se salta las primeras filas:
-- Los tres productos más caros SELECT nombre, precio FROM productos ORDER BY precio DESC LIMIT 3; -- Clientes por país y, dentro de cada país, por nombre SELECT pais, nombre FROM clientes ORDER BY pais, nombre LIMIT 5 OFFSET 2;
Ábrela en el editor y cámbiala →
los más caros+------------------+---------+ | nombre | precio | +------------------+---------+ | Portátil Nova 16 | 1849 | | Portátil Aero 14 | 1299.99 | | Monitor 27" 4K | 429.9 | +------------------+---------+
LIMIT 5 OFFSET 2+----------+----------------+ | pais | nombre | +----------+----------------+ | Chile | Tomás Ortega | | Colombia | Camila Rojas | | España | Lucía Gómez | | España | Luis Fernández | | España | Paula Morales | +----------+----------------+
Con LIMIT y OFFSET se hace la paginación de una web («página 2, de 10 en 10»), aunque con tablas grandes hay formas más eficientes, que están en el ejemplo de paginación. Por último, DISTINCT quita las filas repetidas del resultado:
SELECT DISTINCT pais FROM clientes ORDER BY pais;
Ábrela en el editor y cámbiala →
resultado+-----------+ | pais | +-----------+ | Argentina | | Chile | | Colombia | | España | | México | | Perú | +-----------+
Resumen de las lecciones 1 y 2
| Para | Escribe | Recuerda |
|---|---|---|
| Elegir columnas | SELECT nombre, pais FROM clientes | * trae todas; mejor pedir las que hacen falta |
| Calcular y renombrar | precio * 1.16 AS con_iva | el alias es el nombre de la columna del resultado |
| Filtrar | WHERE pais = 'México' AND alta >= '2026-01-01' | AND antes que OR: usa paréntesis |
| Una lista, un intervalo | IN ('Chile', 'Perú'), BETWEEN 50 AND 150 | BETWEEN incluye los extremos |
| Buscar en un texto | LIKE 'Monitor%' | cada motor trata distinto mayúsculas y tildes |
| El dato que falta | IS NULL, IS NOT NULL | = NULL nunca es verdadero |
| Ordenar y cortar | ORDER BY precio DESC LIMIT 3 | sin ORDER BY no hay orden garantizado |
| Quitar repetidos | SELECT DISTINCT pais | se aplica a la fila entera del resultado |
Retos
Reto 1. Muestra el nombre y el stock de los productos de los que no queda ninguna unidad o quedan menos de cinco, del que menos stock tiene al que más.
Ver la solución
SELECT nombre, stock FROM productos WHERE stock < 5 ORDER BY stock;
Ábrela en el editor y cámbiala →
resultado+---------------------------+-------+ | nombre | stock | +---------------------------+-------+ | Auriculares con micrófono | 0 | | Portátil Nova 16 | 3 | +---------------------------+-------+
«Ninguna o menos de cinco» es lo mismo que stock < 5. Los auriculares están agotados: buen candidato para un aviso de reposición.
Reto 2. ¿Qué clientes se dieron de alta en el segundo semestre de 2025? Muestra su nombre y la fecha de alta.
Ver la solución
SELECT nombre, alta FROM clientes WHERE alta BETWEEN '2025-07-01' AND '2025-12-31' ORDER BY alta;
Ábrela en el editor y cámbiala →
resultado+---------------+------------+ | nombre | alta | +---------------+------------+ | Lucía Gómez | 2025-08-12 | | Mateo Herrera | 2025-09-05 | | Sofía Castro | 2025-11-18 | +---------------+------------+
Como las fechas son textos AAAA-MM-DD, BETWEEN funciona. Si la columna guardara también la hora ('2025-12-31 18:30'), ese último día se quedaría fuera: es una de las rarezas.
Reto 3. Lista los productos cuyo nombre contiene «4K» o «TB», con su precio, del más barato al más caro.
Ver la solución
SELECT nombre, precio FROM productos WHERE nombre LIKE '%4K%' OR nombre LIKE '%TB%' ORDER BY precio;
Ábrela en el editor y cámbiala →
resultado+--------------------+--------+ | nombre | precio | +--------------------+--------+ | SSD 1 TB | 94.9 | | Disco externo 4 TB | 119 | | Webcam 4K | 139 | | Monitor 27" 4K | 429.9 | +--------------------+--------+
Cómo está hecho este curso
Cada consulta se ejecutó en SQLite 3.46.1 sobre la base «tienda» recién creada, y los resultados que ves están copiados de esa ejecución, en el formato de la consola sqlite3 con .mode table. Las diferencias con PostgreSQL 17 y MySQL 8.4 se comprobaron en esos motores, con sus mensajes literales. Debajo de casi cada consulta hay un enlace para abrirla en el editor de SQL y cambiarla.
¿Buscas una receta concreta en vez de una lección? En Ejemplos de SQL hay tareas resueltas: los N primeros de cada grupo, buscar duplicados, totales acumulados, recorrer jerarquías o consultar desde Python. Y si algo te da un resultado que no esperabas, mira las rarezas de SQL.
