SQL (Structured Query Language) es el lenguaje estándar para definir, consultar y administrar bases de datos relacionales. Es declarativo: indicas qué datos quieres, no cómo obtenerlos. Todos los ejemplos de esta página tienen un botón ▶ Ejecutar: se ejecutan de verdad en tu navegador.
Las bases de datos de ejemplo
Todo el curso usa cuatro bases de datos. PK subrayada, FK en cursiva (pasa el ratón para ver a qué apunta).
Almacén
Ciclismo
Teatros
Empresa
SQL se divide en: DDL (definición: CREATE, ALTER, DROP), DML (manipulación: SELECT, INSERT, UPDATE, DELETE) y DCL (control: GRANT, REVOKE, COMMIT, ROLLBACK).
SELECT básico
SELECT [ALL | DISTINCT] columnas | *
FROM tabla [alias]
[WHERE condición]
[GROUP BY columnas [HAVING condición]]
[ORDER BY columna [ASC | DESC]]
[LIMIT n]
Orden lógico de ejecución: FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT. (Por eso un alias del SELECT se puede usar en ORDER BY pero no en WHERE.)
SELECT * FROM pieza;
SELECT codigo, descripcion FROM pieza WHERE color = 'ROJO';
SELECT DISTINCT ciudad FROM proveedor;
SELECT codigo, peso * 1000 AS peso_gramos FROM pieza ORDER BY peso_gramos DESC LIMIT 3;
Condiciones y predicados
- Comparación:
= <> < > <= >=; lógicosAND OR NOT(usa paréntesis). IN (lista),BETWEEN a AND b(incluye los extremos),LIKE(%= cualquier cadena,_= un carácter),IS [NOT] NULL.- NULL = valor desconocido:
x = NULLnunca es verdadero. Con<>tampoco se obtienen las filas con NULL.
SELECT dni, nombre FROM proveedor WHERE ciudad IN ('VALENCIA', 'ALICANTE');
SELECT codigo, peso FROM pieza WHERE peso BETWEEN 20 AND 25;
SELECT nombre FROM proveedor WHERE nombre LIKE 'N%' OR nombre LIKE '%Costa';
-- La trampa del NULL: las piezas sin color NO aparecen
SELECT codigo, color FROM pieza WHERE color <> 'ROJO';
-- Para incluirlas hay que pedirlo
SELECT codigo, color FROM pieza WHERE color <> 'ROJO' OR color IS NULL;
Funciones y agregación
Funciones de agregación: COUNT(*) cuenta filas; COUNT(col) cuenta valores no nulos; SUM, AVG, MIN, MAX ignoran los nulos. Sin GROUP BY devuelven una sola fila para toda la tabla; con GROUP BY, una por grupo.
SELECT COUNT(*), COUNT(color), MIN(peso), MAX(peso), AVG(peso), SUM(peso) FROM pieza;
SELECT codigo, COUNT(*) AS ofertas, AVG(precio) AS precio_medio FROM oferta GROUP BY codigo;
WHERE vs HAVING: WHERE filtra filas antes de agrupar (no admite agregados); HAVING filtra grupos después.
SELECT codigo, AVG(precio) AS precio_medio
FROM oferta
WHERE precio <= 12 -- filtra filas antes de agrupar
GROUP BY codigo
HAVING COUNT(*) >= 2; -- filtra grupos
Otras funciones útiles: texto (UPPER, LOWER, LENGTH, SUBSTR/SUBSTRING, TRIM), números (ROUND, ABS, FLOOR, POWER), fechas (YEAR, DATEDIFF, NOW) y CASE.
SELECT UPPER(nombre) AS nombre, LENGTH(nombre) AS letras, ROUND(descuento / 100.0, 2) AS desc FROM proveedor;
SELECT descripcion,
CASE WHEN peso < 5 THEN 'Ligera' WHEN peso < 50 THEN 'Media' ELSE 'Pesada' END AS categoria
FROM pieza;
Varias tablas: JOIN
Si pones varias tablas en el FROM sin condición obtienes el producto cartesiano (todas las combinaciones). La condición de unión relaciona la clave ajena con la clave primaria. Forma recomendada:
FROM tablaA a JOIN tablaB b ON a.clave = b.clave_ajena
| Tipo | Devuelve |
|---|---|
| INNER JOIN (JOIN) | Solo las filas con coincidencia en ambas tablas |
| LEFT JOIN | Todas las de la izquierda; con NULL donde no hay coincidencia |
| RIGHT JOIN | Todas las de la derecha |
-- Producto cartesiano (11 pedidos x 5 clientes = 55 filas)
SELECT COUNT(*) FROM pedido, cliente;
SELECT p.numero, p.fecha, c.nombre
FROM pedido p JOIN cliente c ON p.cliente = c.dni
WHERE c.ciudad = 'VALENCIA';
-- LEFT JOIN: el pedido sin cliente asignado también aparece
SELECT p.numero, c.nombre FROM pedido p LEFT JOIN cliente c ON p.cliente = c.dni;
-- Tres tablas
SELECT pr.nombre, pi.descripcion, o.precio
FROM oferta o
JOIN proveedor pr ON o.dni = pr.dni
JOIN pieza pi ON o.codigo = pi.codigo
ORDER BY o.precio;
-- Auto-join: la misma tabla dos veces con alias distintos
SELECT e.nombre AS empleado, j.nombre AS jefe
FROM empleado e JOIN empleado j ON e.nssjefe = j.nss;
-- Incluir departamentos sin empleados
SELECT d.nombre, COUNT(e.nss) AS num_empleados
FROM departamento d LEFT JOIN empleado e ON e.dep = d.coddep
GROUP BY d.coddep, d.nombre;
Subconsultas
Una consulta dentro de otra, entre paréntesis, en WHERE (también en FROM). Según lo que devuelve:
- Una lista →
IN/NOT IN. - Un valor (escalar) → comparaciones (
=, >…). - Comparar con todos/alguno →
> ALL (…),> ANY (…). - Correlacionada: usa columnas de la consulta externa; se «ejecuta» una vez por fila externa.
SELECT nombre, apellido, ciudad
FROM empleado
WHERE dep IN (SELECT coddep FROM departamento WHERE nombre = 'INVESTIGACION');
SELECT dni, nombre FROM proveedor
WHERE dni NOT IN (SELECT dni FROM oferta);
-- Escalar: piezas más pesadas que la media
SELECT codigo, descripcion, peso FROM pieza WHERE peso > (SELECT AVG(peso) FROM pieza);
-- ALL: mayor que TODAS las piezas rojas (equivale a > MAX)
SELECT codigo, descripcion FROM pieza
WHERE peso > ALL (SELECT peso FROM pieza WHERE color = 'ROJO');
-- Correlacionada: ofertas por encima de la media de su propia pieza
SELECT o.dni, o.codigo, o.precio FROM oferta o
WHERE o.precio > (SELECT AVG(o2.precio) FROM oferta o2 WHERE o2.codigo = o.codigo);
EXISTS y NOT EXISTS
EXISTS (subconsulta) es verdadero si la subconsulta devuelve al menos una fila; responde a «¿existe alguna fila que…?». La subconsulta suele estar correlacionada y se escribe con SELECT 1.
-- Proveedores que ofrecen alguna pieza
SELECT p.dni, p.nombre FROM proveedor p
WHERE EXISTS (SELECT 1 FROM oferta o WHERE o.dni = p.dni);
-- ... y los que no ofrecen ninguna
SELECT p.dni, p.nombre FROM proveedor p
WHERE NOT EXISTS (SELECT 1 FROM oferta o WHERE o.dni = p.dni);
«Para todos…» (división): se expresa con doble negación: «los equipos en los que todos los ciclistas tienen menos de 30 años» = «los equipos para los que no existe un ciclista con 30 o más».
SELECT e.nomeq FROM equipo e
WHERE NOT EXISTS (SELECT 1 FROM ciclista c WHERE c.nomeq = e.nomeq AND c.edad >= 30);
IN vs EXISTS: IN compara un valor con una lista; EXISTS comprueba la existencia de filas. Cuidado con NOT IN si la lista puede contener NULL:
SELECT 'se muestra' AS caso WHERE 5 NOT IN (1, 2);
SELECT 'se muestra' AS caso WHERE 5 NOT IN (1, NULL); -- ¡no devuelve nada!
INSERT, UPDATE, DELETE y CREATE TABLE
Cada ejemplo se ejecuta sobre una copia nueva de la base de datos, así que puedes repetirlos sin romper nada.
INSERT INTO departamento (coddep, nombre, nssdire) VALUES (6, 'CALIDAD', NULL);
SELECT * FROM departamento;
UPDATE empleado SET salario = salario * 1.10 WHERE dep = 1; -- ¡sin WHERE se actualizaría todo!
SELECT nombre, salario FROM empleado WHERE dep = 1;
DELETE FROM empleado WHERE dep IS NULL;
SELECT COUNT(*) AS empleados FROM empleado;
CREATE TABLE proyecto (
codigo INTEGER PRIMARY KEY,
nombre VARCHAR(30) NOT NULL,
presupuesto REAL CHECK (presupuesto > 0)
);
INSERT INTO proyecto VALUES (1, 'WEB', 5000.5), (2, 'APP', 12000);
SELECT * FROM proyecto;
CREATE TABLE oferta (
dni INTEGER NOT NULL REFERENCES proveedor(dni),
codigo CHAR(5) NOT NULL REFERENCES pieza(codigo),
precio REAL,
PRIMARY KEY (dni, codigo) -- clave primaria compuesta
);
-- Integridad referencial: FOREIGN KEY (...) REFERENCES ... ON DELETE CASCADE | SET NULL | RESTRICT
Método para construir una consulta y errores típicos
- ¿Qué datos piden? y ¿de qué tablas salen? →
FROM/JOIN. - ¿Qué columnas o cálculos? →
SELECT(fíjate en los nombres que piden). - ¿Qué condiciones? →
WHERE(filas) yHAVING(grupos). - Prueba con pocos datos y refina. Pregúntate: ¿necesito
DISTINCT? ¿puede haber NULL? ¿«ninguno» o «todos» →NOT EXISTS?
- Olvidar la condición del
JOIN(producto cartesiano: filas de más). - Agregados en
WHERE(van enHAVING), o columnas sin agregar que faltan en elGROUP BY. = NULLen vez deIS NULL;NOT INcon una lista que contiene NULL.- Duplicados inesperados al unir tablas: falta
DISTINCTo la agrupación correcta. - Mostrar columnas distintas de las que piden.
SELECT *en un examen casi nunca es la respuesta.
Notas de compatibilidad (SQLite en el navegador)
- La web usa SQLite. SQLite no tiene
ALL/ANY, pero la web los traduce automáticamente aMAX/MIN/IN, y añadeYEAR, MONTH, DAY, DATEDIFF, NOW, CURDATE, FLOOR, CEIL, POWER, CONCAT, SUBSTRINGpara que los ejemplos de los apuntes funcionen. - El texto distingue mayúsculas en la comparación (
=), peroLIKEen SQLite no distingue mayúsculas de minúsculas (en otros SGBD sí). - En el examen, usa la sintaxis estándar que ves en los apuntes; en esta práctica lo importante es el resultado.
Zona libre
Prueba tus propias consultas sobre cualquiera de las bases de datos:
Quiz
Las opciones se barajan cada vez; repite el quiz hasta que no falles ninguna.