Asignatura: Bases de Datos
SQL (Structured Query Language) es el lenguaje estándar para interactuar con bases de datos relacionales. Es un lenguaje declarativo: describes qué quieres obtener, no cómo conseguirlo.
| Motor | Notas clave |
|---|---|
| MySQL | Open source, muy usado en web |
| PostgreSQL | Open source, el más estándar y potente |
| SQLite | Ligero, sin servidor, ideal para apps |
| SQL Server | Microsoft, entornos empresariales |
| Oracle DB | Empresarial, tiene sintaxis propia |
⚠️ Nota: Aunque el estándar SQL es común, cada motor tiene pequeñas diferencias de sintaxis (dialectos).
El lenguaje se divide en 4 sublengprocedures según su propósito:
| Categoría | Significado | ¿Para qué sirve? | Comandos principales |
|---|---|---|---|
| DDL | Data Definition Language | Definir estructura de la BD | CREATE, ALTER, DROP, TRUNCATE |
| DML | Data Manipulation Language | Manipular datos | INSERT, UPDATE, DELETE, SELECT |
| DCL | Data Control Language | Control de permisos | GRANT, REVOKE |
| TCL | Transaction Control Language | Control de transacciones | COMMIT, ROLLBACK, SAVEPOINT |
La anatomía de una consulta SQL completa sigue este orden obligatorio:
SELECT columnas -- 1. Qué mostrar
FROM tabla -- 2. De dónde
JOIN otra_tabla ON ... -- 3. Unir tablas (opcional)
WHERE condicion -- 4. Filtrar filas
GROUP BY columna -- 5. Agrupar
HAVING condicion_grupo -- 6. Filtrar grupos
ORDER BY columna ASC|DESC -- 7. Ordenar
LIMIT n OFFSET m; -- 8. Paginar
🔑 Regla de oro: El orden de escritura no es el orden de ejecución. La BD ejecuta:
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.
| Palabra clave | ¿Para qué sirve? | Ejemplo |
|---|---|---|
SELECT |
Seleccionar columnas | SELECT nombre, edad FROM tabla |
SELECT * |
Seleccionar todo | SELECT * FROM usuarios |
DISTINCT |
Eliminar duplicados | SELECT DISTINCT ciudad FROM t |
LIMIT |
Limitar filas devueltas | LIMIT 10 |
OFFSET |
Saltar N filas | LIMIT 10 OFFSET 20 |
AS |
Alias (renombrar) | SELECT nombre AS n FROM t |
-- Ejemplo completo de SELECT con alias y DISTINCT
SELECT DISTINCT
u.nombre AS nombre_cliente,
u.ciudad AS ciudad
FROM usuarios u
LIMIT 5;
= -- Igual
<> / != -- Distinto
> < -- Mayor / Menor
>= <= -- Mayor o igual / Menor o igual
| Operador | ¿Qué hace? | Ejemplo |
|---|---|---|
IN |
Compara contra una lista | WHERE id IN (1, 2, 3) |
NOT IN |
Excluye una lista | WHERE id NOT IN (4, 5) |
BETWEEN |
Rango inclusivo de valores | WHERE edad BETWEEN 18 AND 65 |
LIKE |
Búsqueda por patrón de texto | WHERE nombre LIKE 'A%' |
IS NULL |
Comprueba si es nulo | WHERE telefono IS NULL |
IS NOT NULL |
Comprueba que no sea nulo | WHERE email IS NOT NULL |
EXISTS |
Comprueba si subquery devuelve algo | WHERE EXISTS (SELECT ...) |
ANY / ALL |
Comparación con subquery | WHERE precio > ALL (SELECT ...) |
| Patrón | Significado | Ejemplo |
|---|---|---|
'A%' |
Empieza por A | 'Ana', 'Alex' |
'%z' |
Termina en z | 'López' |
'%mar%' |
Contiene "mar" en cualquier posición | 'Amara' |
'_a_' |
Exactamente 3 chars, el del medio es a | 'bar', 'par' |
-- Combinar condiciones con AND, OR, NOT
SELECT * FROM productos
WHERE (precio BETWEEN 10 AND 50)
AND categoria IN ('ropa', 'calzado')
AND nombre LIKE '%sport%'
AND stock IS NOT NULL;
-- Orden ascendente (por defecto)
SELECT nombre, edad FROM usuarios ORDER BY edad ASC;
-- Orden descendente
SELECT nombre, salario FROM empleados ORDER BY salario DESC;
-- Ordenar por múltiples columnas
SELECT apellido, nombre FROM usuarios ORDER BY apellido ASC, nombre ASC;
🔑 Diferencia clave:
-WHEREfiltra antes de agrupar (trabaja sobre filas individuales).
-HAVINGfiltra después de agrupar (trabaja sobre grupos).
-- Cuántos clientes hay por ciudad
SELECT ciudad, COUNT(*) AS total_clientes
FROM usuarios
GROUP BY ciudad
HAVING COUNT(*) > 5 -- Solo ciudades con más de 5 clientes
ORDER BY total_clientes DESC;
| Función | ¿Qué hace? | Ejemplo |
|---|---|---|
COUNT(*) |
Cuenta filas | COUNT(*) |
COUNT(col) |
Cuenta valores no nulos | COUNT(email) |
SUM(col) |
Suma | SUM(precio) |
AVG(col) |
Media aritmética | AVG(salario) |
MAX(col) |
Valor máximo | MAX(fecha_pedido) |
MIN(col) |
Valor mínimo | MIN(stock) |
Los JOINs permiten combinar datos de varias tablas usando una condición de relación (ON).
| Tipo | ¿Qué devuelve? |
|---|---|
INNER JOIN |
Solo las filas con coincidencia en ambas tablas |
LEFT JOIN |
Todas las filas de la izquierda + coincidencias |
RIGHT JOIN |
Todas las filas de la derecha + coincidencias |
FULL OUTER JOIN |
Todas las filas de ambas tablas |
CROSS JOIN |
Producto cartesiano (todas las combinaciones posibles) |
SELF JOIN |
Una tabla unida consigo misma |
Tabla A Tabla B
●──●──● ●──●──●
INNER: solo ●──● (intersección)
LEFT: A completo + intersección
RIGHT: B completo + intersección
FULL: A + B + intersección
-- INNER JOIN: pedidos con datos del cliente
SELECT u.nombre, p.fecha, p.total
FROM usuarios u
INNER JOIN pedidos p ON u.id = p.usuario_id;
-- LEFT JOIN: todos los usuarios, hayan pedido o no
SELECT u.nombre, p.total
FROM usuarios u
LEFT JOIN pedidos p ON u.id = p.usuario_id;
-- Los usuarios sin pedidos tendrán NULL en p.total
-- SELF JOIN: empleados con su manager (misma tabla)
SELECT e.nombre AS empleado, m.nombre AS manager
FROM empleados e
LEFT JOIN empleados m ON e.manager_id = m.id;
Una subconsulta es una consulta anidada dentro de otra.
-- Subconsulta en WHERE
SELECT nombre FROM usuarios
WHERE id IN (
SELECT usuario_id FROM pedidos
WHERE total > 1000
);
-- Subconsulta como tabla derivada (en FROM)
SELECT ciudad, promedio
FROM (
SELECT ciudad, AVG(salario) AS promedio
FROM empleados
GROUP BY ciudad
) AS resumen_ciudades
WHERE promedio > 50000;
-- Subconsulta correlacionada (hace referencia a la consulta exterior)
SELECT nombre, salario
FROM empleados e
WHERE salario > (
SELECT AVG(salario)
FROM empleados
WHERE departamento_id = e.departamento_id
);
-- Insertar una fila
INSERT INTO usuarios (nombre, email, edad)
VALUES ('Ana García', 'ana@email.com', 28);
-- Insertar múltiples filas
INSERT INTO productos (nombre, precio) VALUES
('Camiseta', 19.99),
('Pantalón', 34.99),
('Zapatos', 59.99);
-- Insertar desde otra tabla
INSERT INTO archivo_clientes
SELECT * FROM clientes WHERE fecha_baja IS NOT NULL;
-- Actualizar filas específicas (siempre usar WHERE)
UPDATE usuarios
SET email = 'nuevo@email.com', edad = 29
WHERE id = 42;
-- ⚠️ Sin WHERE actualiza TODAS las filas
UPDATE productos SET iva = 0.21; -- Afecta a TODOS los productos
-- Borrar filas específicas
DELETE FROM pedidos WHERE fecha < '2020-01-01';
-- ⚠️ Sin WHERE borra TODOS los registros (la tabla sigue existiendo)
DELETE FROM tabla_temporal;
CREATE TABLE usuarios (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
edad INT CHECK (edad >= 0 AND edad <= 120),
ciudad VARCHAR(50) DEFAULT 'Madrid',
creado_en TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
| Restricción | ¿Qué garantiza? | Ejemplo |
|---|---|---|
PRIMARY KEY |
Identifica únicamente cada fila | id INT PRIMARY KEY |
FOREIGN KEY |
Integridad referencial entre tablas | REFERENCES tabla(columna) |
UNIQUE |
No permite valores duplicados | email VARCHAR UNIQUE |
NOT NULL |
El campo es obligatorio | nombre VARCHAR NOT NULL |
CHECK |
Valida con una condición | CHECK (edad >= 18) |
DEFAULT |
Valor por defecto si no se especifica | DEFAULT 'activo' |
-- Tabla con FOREIGN KEY
CREATE TABLE pedidos (
id INT PRIMARY KEY AUTO_INCREMENT,
usuario_id INT NOT NULL,
total DECIMAL(10,2) NOT NULL,
fecha DATE DEFAULT CURRENT_DATE,
FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE CASCADE
);
-- Añadir columna
ALTER TABLE usuarios ADD COLUMN telefono VARCHAR(20);
-- Modificar columna
ALTER TABLE usuarios MODIFY COLUMN nombre VARCHAR(200);
-- Eliminar columna
ALTER TABLE usuarios DROP COLUMN telefono;
-- Eliminar tabla (estructura + datos)
DROP TABLE IF EXISTS tabla_antigua;
-- Vaciar tabla (datos, conserva estructura) — más rápido que DELETE
TRUNCATE TABLE sesiones_temporales;
| Función / Keyword | Motor | ¿Qué hace? | Ejemplo |
|---|---|---|---|
CASE WHEN |
Estándar | Condicional IF/ELSE | CASE WHEN x>1 THEN 'A' ELSE 'B' END |
IF() |
MySQL | Condicional simple | IF(activo=1, 'Sí', 'No') |
COALESCE() |
Estándar | Primer valor no NULL de la lista | COALESCE(tel_movil, tel_fijo, 'Sin teléfono') |
NULLIF() |
Estándar | Devuelve NULL si los dos son iguales | NULLIF(dividendo, 0) (evita división por 0) |
IFNULL() |
MySQL | Sustituye NULL por un valor | IFNULL(descuento, 0) |
DECODE() |
Oracle | Tipo switch | DECODE(estado, 1, 'Activo', 0, 'Inactivo') |
-- CASE WHEN: categorizar salarios
SELECT nombre, salario,
CASE
WHEN salario < 20000 THEN 'Junior'
WHEN salario BETWEEN 20000 AND 45000 THEN 'Mid'
WHEN salario > 45000 THEN 'Senior'
ELSE 'No definido'
END AS nivel
FROM empleados;
-- COALESCE: teléfono de contacto con fallback
SELECT nombre, COALESCE(tel_movil, tel_fijo, email, 'Sin contacto') AS contacto
FROM clientes;
| Operador | ¿Qué hace? | Elimina duplicados |
|---|---|---|
UNION |
Une resultados de dos consultas | ✅ Sí |
UNION ALL |
Une resultados de dos consultas | ❌ No |
INTERSECT |
Devuelve filas comunes a ambas consultas | ✅ Sí |
EXCEPT/MINUS |
Filas del primero que no están en el segundo | ✅ Sí |
-- Clientes de Madrid O Barcelona
SELECT nombre FROM clientes WHERE ciudad = 'Madrid'
UNION
SELECT nombre FROM clientes WHERE ciudad = 'Barcelona';
-- Empleados que también son clientes
SELECT email FROM empleados
INTERSECT
SELECT email FROM clientes;
⚠️ Las consultas unidas con
UNIONdeben tener el mismo número de columnas y tipos compatibles.
Una transacción es un grupo de operaciones que se ejecutan como una unidad atómica (todas o ninguna). Esto garantiza la propiedad ACID.
| Propiedad | Significado |
|---|---|
| Atomicidad | Todo o nada. Si falla una parte, se revierte todo. |
| Consistencia | La BD pasa de un estado válido a otro estado válido. |
| Aislamiento | Las transacciones concurrentes no se interfieren entre sí. |
| Durabilidad | Una vez confirmada, la transacción persiste aunque el sistema falle. |
-- Ejemplo: transferencia bancaria segura
BEGIN TRANSACTION;
UPDATE cuentas SET saldo = saldo - 500 WHERE id = 1; -- Débito
UPDATE cuentas SET saldo = saldo + 500 WHERE id = 2; -- Crédito
-- Si algo falla, deshacemos todo
ROLLBACK;
-- Si todo va bien, confirmamos
COMMIT;
-- SAVEPOINT: punto de control intermedio
SAVEPOINT antes_de_actualizar;
UPDATE productos SET precio = precio * 1.10;
ROLLBACK TO antes_de_actualizar; -- Vuelve al savepoint, no al inicio
-- Dar permisos
GRANT SELECT, INSERT ON base_datos.tabla TO 'usuario'@'host';
GRANT ALL PRIVILEGES ON mi_bd.* TO 'admin'@'localhost';
-- Revocar permisos
REVOKE INSERT ON base_datos.tabla FROM 'usuario'@'host';
-- Ver cómo ejecuta la BD la consulta (detectar consultas lentas)
EXPLAIN SELECT * FROM pedidos WHERE usuario_id = 5;
Busca en la salida: type = ALL significa full table scan → necesitas un índice.
-- Crear índice (acelera búsquedas en esa columna)
CREATE INDEX idx_email ON usuarios(email);
-- Índice compuesto
CREATE INDEX idx_ciudad_edad ON usuarios(ciudad, edad);
-- Eliminar índice
DROP INDEX idx_email ON usuarios;
💡 Regla práctica: Crea índices en columnas que uses en
WHERE,JOIN ONuORDER BY. Demasiados índices ralentizan losINSERT/UPDATE.
| Operación | ¿Borra datos? | ¿Borra estructura? | ¿Se puede deshacer? | ¿Activa triggers? |
|---|---|---|---|---|
DELETE |
✅ Sí (filas) | ❌ No | ✅ Sí (con ROLLBACK) | ✅ Sí |
TRUNCATE |
✅ Sí (todos) | ❌ No | ⚠️ Depende del motor | ❌ No |
DROP |
✅ Sí | ✅ Sí | ❌ No | ❌ No |
SQL se organiza en 4 capas (DDL, DML, DCL, TCL): Cada una controla un aspecto distinto de la base de datos: estructura, datos, permisos y transacciones. Entender qué capa usas en cada momento evita errores graves e irreversibles.
El orden de cláusulas importa y el orden de ejecución es diferente al de escritura: Escribes SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT, pero la BD ejecuta FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT. Esto explica por qué no puedes usar un alias de SELECT en el WHERE.
Los JOINs y las subconsultas son la clave del poder de SQL: Las bases de datos relacionales separan la información a propósito. Los JOINs son el mecanismo para unirla en el momento exacto que necesitas, con control total sobre qué registros incluir (INNER, LEFT, FULL).