Programación · n.º 2 entre los lenguajes más usados

Aprende SQL desde cero

El idioma para guardar, consultar y cruzar datos en una base de datos.

  • 15 lecciones
  • 19 ejercicios interactivos
  • 11 conceptos clave
  • 58,4 % lo usa*

Qué es SQL y para qué sirve

SQL es el lenguaje con el que se habla con las bases de datos relacionales, las que guardan la información en tablas: clientes, pedidos, inventarios… Casi cualquier aplicación lo usa por debajo. Los ejemplos siguen el estándar y funcionan en SQLite; en PostgreSQL o MySQL cambian pocos detalles.

  • Consultar y analizar datos
  • Guardar los datos de webs y aplicaciones
  • Informes y cuadros de mando

Para empezar: Instala SQLite (gratuito) o usa la base de datos de tu proyecto y ve ejecutando los ejemplos en orden.

Nivel 1 Básico

Lección 1 · Básico

Qué es una base de datos y tu primera consulta

Una base de datos relacional guarda la información en tablas, como una hoja de cálculo: cada fila es un registro (un cliente) y cada columna un dato (su nombre, su correo). SQL es el lenguaje para pedirle cosas.

La orden más usada es SELECT, que devuelve datos. Las palabras clave se suelen escribir en mayúsculas para distinguirlas, aunque SQL no lo exige, y cada sentencia termina en punto y coma.

Ejemplo práctico
-- Una consulta que no necesita ninguna tabla
SELECT 'Hola, SQL' AS saludo, 2 + 3 AS suma;

-- Resultado:
-- saludo     | suma
-- Hola, SQL  | 5

Ejercicio

En una tabla de clientes, ¿qué representa cada fila?

Ver la solución

Respuesta: Un cliente con todos sus datos. Cada fila es un registro completo; cada columna, un dato concreto de todos los registros.

Lección 2 · Básico

Crear una tabla e insertar datos

CREATE TABLE define una tabla con sus columnas y el tipo de dato de cada una. La clave primaria (PRIMARY KEY) identifica cada fila sin repetirse.

INSERT INTO añade filas. Los textos van entre comillas simples y los números sin comillas.

Ejemplo práctico
CREATE TABLE productos (
  id INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL,
  precio REAL,
  stock INTEGER DEFAULT 0
);

INSERT INTO productos (nombre, precio, stock) VALUES
  ('Teclado', 25.90, 10),
  ('Ratón', 12.50, 0),
  ('Monitor', 149.00, 3);

Ejercicio

Completa la orden que añade una fila nueva.

 INTO productos (nombre, precio) VALUES ('Cable', 4.99);
Ver la solución

Respuesta: INSERT. INSERT INTO tabla (columnas) VALUES (valores) añade una fila.

Lección 3 · Básico

Filtrar y ordenar: WHERE y ORDER BY

WHERE se queda solo con las filas que cumplen una condición; puedes combinar varias con AND y OR. ORDER BY ordena el resultado, de menor a mayor por defecto o al revés con DESC.

En vez de * (todas las columnas), nombra las que necesitas: la consulta es más clara y más rápida.

Ejemplo práctico
SELECT nombre, precio
FROM productos
WHERE precio < 100 AND stock > 0
ORDER BY precio DESC;

-- nombre   | precio
-- Teclado  | 25.9

Ejercicio

Completa para ver solo los productos de menos de 10 €.

SELECT * FROM productos  precio < 10;
Ver la solución

Respuesta: WHERE. WHERE filtra las filas según la condición que escribas después.

Escribe el código

La tabla productos tiene las columnas nombre, categoria y precio. Escribe una consulta que devuelva el nombre y el precio de los productos que cuestan más de 10 €, del más caro al más barato.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
SELECT nombre, precio
FROM productos
WHERE precio > 10
ORDER BY precio DESC;

WHERE precio > 10 se queda con los productos de más de 10 € y ORDER BY precio DESC los ordena de mayor a menor (ASC, el orden por defecto, sería de menor a mayor).

Conceptos clave

Lección 4 · Básico

Modificar y borrar: UPDATE y DELETE

UPDATE cambia valores de filas existentes y DELETE las elimina. Los dos aceptan WHERE para decir a qué filas afectan.

Si olvidas el WHERE, la orden se aplica a toda la tabla. Antes de un UPDATE o un DELETE, prueba la misma condición con un SELECT para ver qué filas vas a tocar.

Ejemplo práctico
-- Sube un 10 % el precio del monitor
UPDATE productos SET precio = precio * 1.10 WHERE nombre = 'Monitor';

-- Primero comprueba qué vas a borrar…
SELECT * FROM productos WHERE stock = 0;
-- …y después bórralo
DELETE FROM productos WHERE stock = 0;

Ejercicio

¿Qué hace DELETE FROM clientes; sin WHERE?

Ver la solución

Respuesta: Borra todas las filas de la tabla. Sin condición, DELETE afecta a todas las filas. La tabla queda vacía (pero sigue existiendo).

Encuentra los errores

Estas órdenes deberían subir un 10 % el precio de los productos de Papelería y borrar solo los que no tienen stock. Tienen 2 errores que estropearían toda la tabla.

Ver la solución
  • Línea 3: Sin WHERE, UPDATE cambia todas las filas de la tabla, no solo las de Papelería. Corrección: SET precio = precio * 1.10 WHERE categoria = 'Papelería';
  • Línea 7: OR categoria = 'Informática' añade a la condición todos los productos de Informática, tengan stock o no. Corrección: WHERE stock = 0;

Conceptos clave

Nivel 2 Intermedio

Lección 5 · Intermedio

Contar y agrupar: GROUP BY

Las funciones de agregado resumen muchas filas en un número: COUNT() cuenta, SUM() suma, AVG() hace la media, MIN() y MAX() buscan los extremos.

GROUP BY calcula esos resúmenes por grupos, por ejemplo por categoría. Para filtrar grupos (no filas) se usa HAVING.

Ejemplo práctico
CREATE TABLE ventas (producto TEXT, categoria TEXT, importe REAL);
INSERT INTO ventas VALUES
  ('Teclado', 'Periféricos', 25.90),
  ('Ratón', 'Periféricos', 12.50),
  ('Monitor', 'Pantallas', 149.00);

SELECT categoria, COUNT(*) AS ventas, SUM(importe) AS total
FROM ventas
GROUP BY categoria
HAVING SUM(importe) > 30;

-- categoria    | ventas | total
-- Pantallas    | 1      | 149.0
-- Periféricos  | 2      | 38.4

Ejercicio

¿Qué función calcula la media de una columna?

Ver la solución

Respuesta: AVG(). AVG() (de average) devuelve la media de los valores.

Escribe el código

Con la tabla productos (nombre, categoria, precio), escribe una consulta que devuelva cada categoria y cuántos productos tiene, solo para las categorías con 2 productos o más.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
SELECT categoria, COUNT(*)
FROM productos
GROUP BY categoria
HAVING COUNT(*) >= 2;

GROUP BY categoria forma un grupo por categoría y COUNT(*) cuenta sus filas. Para filtrar grupos se usa HAVING; WHERE filtra filas antes de agrupar y no puede usar COUNT.

Conceptos clave

Lección 6 · Intermedio

Cruzar tablas: JOIN

Los datos se reparten en varias tablas para no repetirlos: los pedidos guardan el id del cliente, no su nombre. Esa columna que apunta a otra tabla es una clave foránea.

JOIN une las tablas en una consulta. INNER JOIN solo devuelve las filas con pareja en las dos; LEFT JOIN devuelve todas las de la izquierda, aunque no tengan pareja.

Ejemplo práctico
CREATE TABLE clientes (id INTEGER PRIMARY KEY, nombre TEXT);
CREATE TABLE pedidos (
  id INTEGER PRIMARY KEY,
  cliente_id INTEGER REFERENCES clientes(id),
  total REAL
);
INSERT INTO clientes VALUES (1, 'Ana'), (2, 'Luis');
INSERT INTO pedidos VALUES (1, 1, 30.0), (2, 1, 12.5);

SELECT c.nombre, COUNT(p.id) AS pedidos
FROM clientes AS c
LEFT JOIN pedidos AS p ON p.cliente_id = c.id
GROUP BY c.id;

-- Ana  | 2
-- Luis | 0   (con INNER JOIN, Luis no aparecería)

Ejercicio

Quieres ver todos los clientes, también los que nunca han comprado. ¿Qué JOIN usas?

Ver la solución

Respuesta: LEFT JOIN. LEFT JOIN conserva todas las filas de la tabla de la izquierda aunque no tengan pedidos.

Encuentra los errores

Esta consulta debería listar cada pedido con el nombre de su cliente, del pedido más reciente al más antiguo. Tiene 2 errores.

Ver la solución
  • Línea 3: Hay que unir el cliente_id del pedido con el id del cliente; comparar con pedidos.id (el número del pedido) no relaciona nada. Corrección: JOIN clientes ON clientes.id = pedidos.cliente_id
  • Línea 4: ASC ordena de la fecha más antigua a la más reciente; para empezar por la más reciente se usa DESC. Corrección: ORDER BY pedidos.fecha DESC;

Lección 7 · Intermedio

Subconsultas

Una subconsulta es un SELECT dentro de otro, entre paréntesis. Sirve para usar un resultado calculado como parte de la condición.

Por ejemplo, para ver los productos más caros que la media, primero se calcula la media y luego se compara cada precio con ella.

Ejemplo práctico
SELECT nombre, precio
FROM productos
WHERE precio > (SELECT AVG(precio) FROM productos);

Ejercicio

¿Dónde se escribe una subconsulta?

Ver la solución

Respuesta: Entre paréntesis dentro de otra consulta. La subconsulta va entre paréntesis y se ejecuta para dar un valor (o una lista) a la consulta principal.

Conceptos clave

Nivel 3 Avanzado

Lección 8 · Avanzado

Índices: consultas rápidas

Un índice es una estructura que la base de datos mantiene aparte para encontrar filas sin recorrer toda la tabla, como el índice de un libro. Acelera mucho las búsquedas por esa columna.

No es gratis: ocupa espacio y hace un poco más lentas las escrituras, porque el índice también se actualiza. Créalos en las columnas por las que buscas o unes tablas a menudo. Con EXPLAIN QUERY PLAN (SQLite) o EXPLAIN (PostgreSQL, MySQL) ves si una consulta lo usa.

Ejemplo práctico
CREATE INDEX idx_pedidos_cliente ON pedidos (cliente_id);

-- SQLite te dice cómo va a ejecutar la consulta
EXPLAIN QUERY PLAN
SELECT * FROM pedidos WHERE cliente_id = 1;
-- SEARCH pedidos USING INDEX idx_pedidos_cliente (cliente_id=?)

Ejercicio

¿Qué inconveniente tiene añadir un índice?

Ver la solución

Respuesta: Ocupa espacio y ralentiza un poco las escrituras. Cada inserción o cambio también actualiza el índice, y este ocupa espacio en disco.

Lección 9 · Avanzado

Transacciones: todo o nada

Una transacción agrupa varias órdenes para que se apliquen todas o ninguna. El ejemplo clásico es una transferencia: restar de una cuenta y sumar a otra tiene que ocurrir junto.

Se empieza con BEGIN, se confirma con COMMIT y, si algo falla, ROLLBACK deshace todo lo hecho desde el BEGIN.

Ejemplo práctico
CREATE TABLE cuentas (id INTEGER PRIMARY KEY, saldo REAL);
INSERT INTO cuentas VALUES (1, 100), (2, 50);

BEGIN;
UPDATE cuentas SET saldo = saldo - 30 WHERE id = 1;
UPDATE cuentas SET saldo = saldo + 30 WHERE id = 2;
COMMIT;   -- o ROLLBACK; para deshacer los dos cambios

Ejercicio

Completa la orden que deshace la transacción.

BEGIN;
UPDATE cuentas SET saldo = 0;
;
Ver la solución

Respuesta: ROLLBACK. ROLLBACK descarta todos los cambios hechos desde BEGIN.

Lección 10 · Avanzado

Consultas seguras: evita la inyección SQL

Si construyes una consulta pegando texto que escribe el usuario, alguien puede colar sus propias órdenes SQL: es la inyección SQL, uno de los fallos de seguridad más graves.

La solución es usar consultas parametrizadas: escribes huecos (?) y pasas los valores aparte, de modo que la base de datos nunca los interpreta como código. Todos los lenguajes tienen esta opción.

Ejemplo práctico
# En Python, con el módulo sqlite3 incluido
import sqlite3

con = sqlite3.connect("tienda.db")
nombre = input("Producto: ")

# MAL: con.execute(f"SELECT * FROM productos WHERE nombre = '{nombre}'")
# BIEN: el valor va aparte, nunca dentro del texto de la consulta
filas = con.execute("SELECT * FROM productos WHERE nombre = ?", (nombre,)).fetchall()
print(filas)

Ejercicio

¿Cuál es la forma correcta de evitar la inyección SQL?

Ver la solución

Respuesta: Usar consultas parametrizadas. Con parámetros, el valor nunca se mezcla con el código SQL, así que no puede alterar la consulta.

Lección 11 · Avanzado

CASE y COALESCE: decidir dentro de la consulta

CASE es el «si…, entonces…» de SQL: evalúa condiciones fila a fila y devuelve un valor distinto según cuál se cumpla. Sirve para clasificar datos sin cambiarlos en la tabla, como poner etiquetas de «barato» o «caro».

COALESCE devuelve el primer valor que no sea NULL de los que le pases. Se usa para mostrar algo comprensible cuando falta un dato, por ejemplo «Sin teléfono» en vez de una celda vacía.

Ejemplo práctico
CREATE TABLE clientes (nombre TEXT, telefono TEXT, gasto REAL);
INSERT INTO clientes VALUES ('Ana', '600111222', 320), ('Luis', NULL, 45), ('Marta', '611222333', 1200);

SELECT nombre,
       COALESCE(telefono, 'Sin teléfono') AS contacto,
       CASE
         WHEN gasto >= 1000 THEN 'Oro'
         WHEN gasto >= 200 THEN 'Plata'
         ELSE 'Bronce'
       END AS nivel
FROM clientes;
-- Ana   | 600111222    | Plata
-- Luis  | Sin teléfono | Bronce
-- Marta | 611222333    | Oro

Escribe el código

La tabla alumnos tiene nombre y nota. Devuelve el nombre y una segunda columna con 'Aprobado' si la nota es 5 o más y 'Suspenso' si es menor.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
SELECT nombre,
       CASE WHEN nota >= 5 THEN 'Aprobado' ELSE 'Suspenso' END
FROM alumnos;

CASE WHEN condición THEN valor ELSE otro END calcula la columna fila a fila. Con >= 5, el 5 también aprueba.

Lección 12 · Avanzado

Restricciones: datos válidos desde la tabla

Las restricciones son reglas que la propia base de datos hace cumplir. NOT NULL obliga a rellenar una columna, UNIQUE impide repetir un valor (como un correo) y CHECK comprueba una condición, como que un precio no sea negativo.

FOREIGN KEY garantiza que una fila apunte a otra que existe: no podrás crear un pedido de un cliente inexistente. Si una orden incumple una restricción, la base de datos la rechaza con un error y no guarda nada.

Ejemplo práctico
PRAGMA foreign_keys = ON;  -- en SQLite hay que activarlas

CREATE TABLE clientes (
  id     INTEGER PRIMARY KEY,
  correo TEXT NOT NULL UNIQUE
);

CREATE TABLE pedidos (
  id         INTEGER PRIMARY KEY,
  cliente_id INTEGER NOT NULL REFERENCES clientes(id),
  total      REAL CHECK (total >= 0)
);

INSERT INTO clientes (correo) VALUES ('ana@ejemplo.es');
INSERT INTO pedidos (cliente_id, total) VALUES (1, 25.50);  -- correcto

-- Cada una de estas se rechaza con un error:
-- INSERT INTO clientes (correo) VALUES ('ana@ejemplo.es');   correo repetido (UNIQUE)
-- INSERT INTO pedidos (cliente_id, total) VALUES (1, -5);    total negativo (CHECK)
-- INSERT INTO pedidos (cliente_id, total) VALUES (99, 10);   cliente 99 inexistente (FOREIGN KEY)

Ejercicio

La columna correo es UNIQUE. ¿Qué pasa si insertas un cliente con un correo que ya está en la tabla?

Ver la solución

Respuesta: La base de datos rechaza la inserción con un error. Las restricciones se comprueban en cada inserción o cambio: si se incumplen, la orden falla y la tabla no cambia.

Lección 13 · Avanzado

Consultas con nombre: WITH

Una consulta larga se lee mejor por partes. Con WITH nombre AS (SELECT …) defines una consulta auxiliar con nombre (una CTE) y después la usas como si fuera una tabla en la consulta principal.

Es especialmente útil cuando necesitas el mismo resultado intermedio varias veces, o cuando quieres comparar cada fila con un cálculo sobre toda la tabla, como «los pedidos por encima de la media».

Ejemplo práctico
CREATE TABLE pedidos (id INTEGER, cliente TEXT, total REAL);
INSERT INTO pedidos VALUES (1, 'Ana', 40), (2, 'Luis', 15), (3, 'Ana', 70), (4, 'Marta', 25);

WITH media AS (
  SELECT AVG(total) AS valor FROM pedidos
)
SELECT pedidos.id, pedidos.cliente, pedidos.total
FROM pedidos, media
WHERE pedidos.total > media.valor;
-- La media es 37.5: salen el pedido 1 (Ana, 40) y el 3 (Ana, 70)

Ejercicio

Completa para definir la consulta auxiliar media.

 media AS (SELECT AVG(total) AS valor FROM pedidos)
Ver la solución

Respuesta: WITH. WITH nombre AS (consulta) se escribe antes de la consulta principal, que puede usar nombre como una tabla.

Lección 14 · Avanzado

Funciones de ventana: rankings y acumulados

Las funciones de ventana calculan algo sobre un grupo de filas sin juntarlas en una sola, como haría GROUP BY: cada fila conserva sus datos y además recibe el resultado. Se reconocen por OVER (…).

ROW_NUMBER() y RANK() numeran las filas según un orden (perfecto para rankings); PARTITION BY reinicia la cuenta en cada grupo, y SUM(…) OVER (ORDER BY …) calcula un total acumulado.

Ejemplo práctico
CREATE TABLE ventas (vendedor TEXT, mes INTEGER, importe REAL);
INSERT INTO ventas VALUES ('Ana', 1, 300), ('Ana', 2, 450), ('Luis', 1, 500), ('Luis', 2, 200);

SELECT vendedor, mes, importe,
       RANK() OVER (PARTITION BY mes ORDER BY importe DESC) AS puesto,
       SUM(importe) OVER (PARTITION BY vendedor ORDER BY mes) AS acumulado
FROM ventas
ORDER BY vendedor, mes;
-- Ana  | 1 | 300 | 2 | 300
-- Ana  | 2 | 450 | 1 | 750
-- Luis | 1 | 500 | 1 | 500
-- Luis | 2 | 200 | 2 | 700

Escribe el código

La tabla jugadores tiene nombre y puntos. Devuelve el nombre, los puntos y una tercera columna con su posición (1 para quien más puntos tiene) usando ROW_NUMBER(), ordenado por esa posición.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
SELECT nombre, puntos,
       ROW_NUMBER() OVER (ORDER BY puntos DESC) AS posicion
FROM jugadores
ORDER BY posicion;

ROW_NUMBER() OVER (ORDER BY puntos DESC) numera las filas de más a menos puntos. Con empates, RANK() daría el mismo puesto a ambos.

Lección 15 · Avanzado

Vistas: guardar una consulta

Una vista es una consulta guardada con nombre en la base de datos. Se crea con CREATE VIEW nombre AS SELECT … y después se consulta como si fuera una tabla, pero no guarda datos propios: cada vez que la lees, ejecuta su consulta con los datos actuales.

Las vistas evitan repetir consultas largas y permiten dar acceso solo a una parte de los datos, por ejemplo, a los pedidos sin las columnas con datos personales.

Ejemplo práctico
CREATE TABLE pedidos (id INTEGER, cliente TEXT, estado TEXT, total REAL);
INSERT INTO pedidos VALUES (1, 'Ana', 'pendiente', 40), (2, 'Luis', 'enviado', 15), (3, 'Marta', 'pendiente', 22);

CREATE VIEW pendientes AS
  SELECT id, cliente, total FROM pedidos WHERE estado = 'pendiente';

SELECT * FROM pendientes;          -- pedidos 1 y 3

UPDATE pedidos SET estado = 'enviado' WHERE id = 1;
SELECT * FROM pendientes;          -- ahora solo el 3: la vista siempre está al día

Ejercicio

Después de crear una vista, cambias datos en la tabla original. ¿Qué devuelve la vista?

Ver la solución

Respuesta: Los datos actuales, porque vuelve a ejecutar su consulta. Una vista no copia datos: guarda la consulta y la ejecuta cada vez que la usas.

¿Has terminado? Sigue practicando

Repasa los conceptos en el diccionario, practica con estas herramientas o prueba otro lenguaje: las ideas que has aprendido (variables, condiciones, funciones, objetos…) te servirán en cualquiera.

Proyectos para practicar SQL

  • Texto y programación

    Base64, URL y JSON

    Codifica y decodifica Base64 y URL, y valida o formatea JSON con la línea del error.

  • Texto y programación

    Comparar dos textos

    Encuentra las diferencias entre dos versiones de un texto, línea a línea y palabra a palabra.

Fuentes consultadas

* Porcentaje de personas que dijeron haber usado SQL en la Stack Overflow Developer Survey 2026: Technology (pregunta sobre lenguajes de programación, de scripts y de marcado; todas las respuestas). El progreso de las lecciones se guarda solo en este navegador.