# Guía 1: SQL Intermedio (MariaDB 10)
> Entorno: VM Ubuntu 24.04 + LAMP + phpMyAdmin + MariaDB 10.
## 0) Antes de empezar (setup)
### 0.1. Acceso al cliente de base de datos
Usaremos el cliente `mysql` para ejecutar sentencias SQL. Puedes conectarte así:
```bash
mysql -h localhost -u root -p
# o, si ya tienes un usuario creado:
mysql -h localhost -u cbuser -p
```
El cliente acepta opciones cortas (`-h`, `-u`, `-p`) o largas (`--host`, `--user`, `--password`). `localhost` puede usar socket Unix; para forzar TCP usa `127.0.0.1` o `--protocol=tcp`.   
> Tip: puedes guardar credenciales en `~/.my.cnf` (grupo `[client]`) y luego ejecutar `mysql` sin opciones. Protege ese archivo con `chmod 600 ~/.my.cnf`.   
También puedes usar **phpMyAdmin** desde el navegador como interfaz alternativa.  
### 0.2. Crear base de datos y usuario de práctica
Conéctate como `root` y crea un usuario y una BD de trabajo:
```sql
-- como root en el cliente mysql
CREATE DATABASE ubdemo;
CREATE USER 'ubuser'@'localhost' IDENTIFIED BY 'pass_demo';
GRANT ALL ON ubdemo.* TO 'ubuser'@'localhost';
FLUSH PRIVILEGES;
```
`CREATE USER` crea la cuenta; `GRANT` asigna privilegios (a nivel global, BD, tabla, columna o rutina).   
Desconéctate y prueba el acceso nuevo:
```bash
mysql -h localhost -u ubuser -p
```
## 1) Fundamentos de SQL (DDL/LDD y DML/LMD)
### 1.1. ¿Qué es SQL?
SQL se compone, entre otras cosas, de:
- **LDD/DDL** (Lenguaje de Definición de Datos): define esquemas, borra relaciones y modifica esquemas.
- **LMD/DML** (Lenguaje de Manipulación de Datos): incluye el lenguaje de consultas y comandos para insertar, borrar y modificar tuplas.  
El DDL permite especificar esquemas, dominios (tipos), restricciones de integridad, índices y aspectos de seguridad.  
### 1.2. Tipos de datos básicos (dominios)
Algunos tipos estándar:
- `CHAR(n)`, `VARCHAR(n)` (cadenas de longitud fija/variable)
- `INT`, `SMALLINT`
- `NUMERIC(p,d)` (coma fija), `REAL/DOUBLE`, `FLOAT(n)`
- Casi todos los tipos admiten el valor especial **NULL**.   
> Cadenas entre comillas simples. La comparación de cadenas puede ser sensible o no a mayúsculas según el SGBD; MySQL/MariaDB suelen ser insensibles por defecto (collation).  
### 1.3. Restricciones de integridad y claves
- **PRIMARY KEY**: identifica tuplas, debe ser única y no nula.
- **FOREIGN KEY**: sus valores deben coincidir con la clave primaria (o única) de otra tabla.
- **NOT NULL**: prohíbe valores nulos.   
Ejemplo (esquema “Universidad” clásico):
```sql
CREATE TABLE departamento(
  nombre_dept VARCHAR(20),
  edificio     VARCHAR(15),
  presupuesto  NUMERIC(12,2),
  PRIMARY KEY (nombre_dept)
);
CREATE TABLE asignatura(
  asignatura_id VARCHAR(7),
  nombre        VARCHAR(50),
  nombre_dept   VARCHAR(20),
  creditos      NUMERIC(2,0),
  PRIMARY KEY (asignatura_id),
  FOREIGN KEY (nombre_dept) REFERENCES departamento
);
CREATE TABLE profesor(
  ID           VARCHAR(5),
  nombre       VARCHAR(20) NOT NULL,
  nombre_dept  VARCHAR(20),
  sueldo       NUMERIC(8,2),
  PRIMARY KEY (ID),
  FOREIGN KEY (nombre_dept) REFERENCES departamento
);
``` 
> Los SGBD impiden violar claves primarias/foráneas durante inserciones/actualizaciones.  
### 1.4. DML esencial
- **INSERT**: añade filas.
- **DELETE**: elimina filas.
- **UPDATE**: modifica filas.
- **SELECT**: consulta (lo veremos en la sección 2).
Ejemplos:
```sql
-- INSERT: columnas explícitas (recomendado)
INSERT INTO profesor (id, nombre, nombre_dept, sueldo)
VALUES ('45565', 'Katz', 'Informática', 75000);
-- INSERT múltiple
INSERT INTO departamento (nombre_dept, edificio, presupuesto) VALUES
('Electrónica','Watson',1500000), ('Finanzas','Taylor',1200000);
-- UPDATE: cuidado con el WHERE (si lo omites, actualizas todo)
UPDATE profesor
SET sueldo = sueldo * 1.05  -- +5%
WHERE nombre_dept = 'Informática';
-- DELETE: elimina filas
DELETE FROM profesor
WHERE id = '45565';
```
> Nota sobre **NULL**: SQL incorpora un tercer valor lógico (“unknown”) al trabajar con nulos; tenlo en cuenta en comparaciones y `WHERE`.  
## 2) Consultas SQL básicas
Para avanzar en esta guía deberás borrar las tablas creadas anteriormente o en su defecto crear nueva base de datos.
Comandos para **crear una BBDD nueva**:
```SQL
CREATE DATABASE prueba CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'tu_usuario'@'localhost' IDENTIFIED BY 'pass_usuario'; 
GRANT ALL PRIVILEGES ON prueba.* TO 'dev'@'localhost'; 
FLUSH PRIVILEGES; 
EXIT;
```
**NOTA:**  Recuerde el usuario y contraseña que esté empelando.
Para acceder a mysql con el usuario y contraseña de la base datos (considere que debería adaptar acorde usted haya creado la BBDD y el usuario):
```bash
mysql -h localhost -u ubuser -p
```
Aquí los comandos que se podrían emplear inicialmente si usamos una base de datos existente:
```SQL
-- Para ver las bases de datos que tiene el usuario
SHOW DATABASES;
-- Para usar una base de datos llamada 'ubdemo'
USE database_name; --en nuestro caso ubdemo
-- Luego de haber elegido la BBDD con 'USE' se podría ver las tablas que existen en la BBDD
SHOW TABLES;
SHOW TABLES FROM database_name;  
SHOW TABLES IN database_name;
-- Para ver las estructura interna de una de las tablas se usa
DESCRIBE NombreTabla;
-- Borra solo los registros de profesor, tabla sigue existiendo
DELETE FROM profesor WHERE nombre_dept = 'Historia';
-- Borra la tabla profesor (¡se pierde estructura y datos!)
DROP TABLE profesor; --MUCHO OJO
-- Borra tablas si existen (para reintentar esta sección de prácticas)
DROP TABLE IF EXISTS matricula, seccion, prerreq, imparte, estudiante, profesor, asignatura, departamento;
```
Aquí los comandos para agregar las tablas y cargar la información inicial.
**Siga leyendo cada linea y bloque:**
```SQL
-- Departamentos
CREATE TABLE departamento (
  nombre_dept   VARCHAR(20)  NOT NULL,
  edificio      VARCHAR(15),
  presupuesto   DECIMAL(12,2) CHECK (presupuesto >= 0),
  PRIMARY KEY (nombre_dept)
) ENGINE=InnoDB;
-- Asignaturas
CREATE TABLE asignatura (
  asignatura_id VARCHAR(7)   NOT NULL,
  nombre        VARCHAR(50)  NOT NULL,
  nombre_dept   VARCHAR(20)  NOT NULL,
  creditos      TINYINT UNSIGNED NOT NULL,
  PRIMARY KEY (asignatura_id),
  CONSTRAINT fk_asig_dept
    FOREIGN KEY (nombre_dept) REFERENCES departamento(nombre_dept)
      ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=InnoDB;
-- Profesores
CREATE TABLE profesor (
  id            VARCHAR(5)    NOT NULL,
  nombre        VARCHAR(30)   NOT NULL,
  nombre_dept   VARCHAR(20)   NOT NULL,
  sueldo        DECIMAL(9,2)  CHECK (sueldo >= 0),
  PRIMARY KEY (id),
  CONSTRAINT fk_prof_dept
    FOREIGN KEY (nombre_dept) REFERENCES departamento(nombre_dept)
      ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=InnoDB;
-- Secciones (ofertas de una asignatura en un semestre y año)
CREATE TABLE seccion (
  asignatura_id VARCHAR(7)  NOT NULL,
  secc_id       SMALLINT    NOT NULL,
  semestre      ENUM('Primavera','Verano','Otoño') NOT NULL,
  anio          YEAR        NOT NULL,
  edificio      VARCHAR(15),
  aula          VARCHAR(10),
  fran_horaria  CHAR(1),
  PRIMARY KEY (asignatura_id, secc_id, semestre, anio),
  CONSTRAINT fk_sec_asig
    FOREIGN KEY (asignatura_id) REFERENCES asignatura(asignatura_id)
      ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=InnoDB;
-- Quién imparte qué sección
CREATE TABLE imparte (
  id            VARCHAR(5) NOT NULL,
  asignatura_id VARCHAR(7) NOT NULL,
  secc_id       SMALLINT   NOT NULL,
  semestre      ENUM('Primavera','Verano','Otoño') NOT NULL,
  anio          YEAR NOT NULL,
  PRIMARY KEY (id, asignatura_id, secc_id, semestre, anio),
  CONSTRAINT fk_imp_prof   FOREIGN KEY (id)            REFERENCES profesor(id),
  CONSTRAINT fk_imp_sec    FOREIGN KEY (asignatura_id, secc_id, semestre, anio)
                           REFERENCES seccion(asignatura_id, secc_id, semestre, anio)
) ENGINE=InnoDB;
-- Estudiantes
CREATE TABLE estudiante (
  id            VARCHAR(5)  NOT NULL,
  nombre        VARCHAR(30) NOT NULL,
  tot_creditos  SMALLINT UNSIGNED DEFAULT 0,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
-- Matrículas (estudiante toma sección)
CREATE TABLE matricula (
  id            VARCHAR(5)  NOT NULL,
  asignatura_id VARCHAR(7)  NOT NULL,
  secc_id       SMALLINT    NOT NULL,
  semestre      ENUM('Primavera','Verano','Otoño') NOT NULL,
  anio          YEAR NOT NULL,
  nota          DECIMAL(3,1),
  PRIMARY KEY (id, asignatura_id, secc_id, semestre, anio),
  CONSTRAINT fk_mat_est   FOREIGN KEY (id) REFERENCES estudiante(id),
  CONSTRAINT fk_mat_sec   FOREIGN KEY (asignatura_id, secc_id, semestre, anio)
                          REFERENCES seccion(asignatura_id, secc_id, semestre, anio)
) ENGINE=InnoDB;
-- Prerrequisitos (relación muchos-a-muchos sobre asignatura)
CREATE TABLE prerreq (
  asignatura_id VARCHAR(7) NOT NULL,
  prerreq_id    VARCHAR(7) NOT NULL,
  PRIMARY KEY (asignatura_id, prerreq_id),
  CONSTRAINT fk_pre_a  FOREIGN KEY (asignatura_id) REFERENCES asignatura(asignatura_id),
  CONSTRAINT fk_pre_b  FOREIGN KEY (prerreq_id)    REFERENCES asignatura(asignatura_id)
) ENGINE=InnoDB;
```
### ¿Por qué necesito especificar `ENGINE=InnoDB` y qué pasa si lo omito?
- En MariaDB/MySQL, cada tabla se crea con un **motor de almacenamiento**.
- `InnoDB` es el motor recomendado porque soporta:
    - ✅ **Claves foráneas (FOREIGN KEY)**
    - ✅ **Transacciones ACID**
    - ✅ **Bloqueo por fila** (mejor concurrencia)
Si lo omites:
- En versiones modernas, el **motor por defecto es InnoDB** (no pasa nada).
- En versiones antiguas (antes de MySQL 5.5), el motor por defecto era **MyISAM** (no soporta claves foráneas ni transacciones). Tus `FOREIGN KEY` se **ignorarían** silenciosamente.
👉 Siempre explícitalo: garantiza consistencia en cualquier entorno.
Ahora carguemos los datos dentro de las tablas:
```SQL
-- Datos mínimos para empezar a consultar
INSERT INTO departamento VALUES
('Biología','Watson', 1200000),
('Informática','Taylor', 2000000),
('Física','Packard', 1900000),
('Historia','Painter', 900000),
('Música','Smith', 600000);
INSERT INTO asignatura VALUES
('BIO-101','Introducción a la Biología','Biología',4),
('BIO-301','Genética','Biología',4),
('CS-101','Introducción a la Informática','Informática',4),
('CS-315','Robótica','Informática',3),
('CS-347','Fundamentos de bases de datos','Informática',3),
('PHY-101','Fundamentos de Física','Física',4);
```
**OJO:** Aquí, preste atención a la siguiente sección de código y los comentarios. 
```SQL
INSERT INTO profesor VALUES
('10101','Srinivasan','Informática',65000),
('12121','Wu','Finanzas',90000),      -- no existe depto Finanzas, por ello dará un error en este bloque de código. Revise los datos de tabla departamento para identificar el error.
('22222','Einstein','Física',95000),
('76766','Crick','Biología',72000),
('83821','Brandt','Informática',92000);
-- Debido al error del profesor Wu, el bloque de código anterior no se ejecutará y dará un error. 
-- Posibles soluciones
	1) Agregar 'Finanzas' en la tabla 'departamento'
	2) Modificar la tupla 12121 del profesor Wu y cambiar 'finanzas' por un departamento que sí existe. 
-- En nuestro caso, se deberá emplear la opción 2. Editar a mano el código con el que se está agregando datos a 'profesores', específicamente el caso del profesor Wu cambiando el departamento de 'Finanzas' a 'Informática'.
--Para continuar, haga esa modificación manualmente y envíe el código para cargar los valores a la tabla de 'profesores'.
```
**NOTA:** En caso de necesitar modificar un atributo/campo de una entidad/tabla, aquí se muestra el código para hacerlo
```SQL
-- Considera que en caso de necesitar cambiar el dpto de un profesor se debería hacer un 'UPDATE' de esta manera:
-- Aquí un ejemplo de actualización de dpto en el caso del profesor Wu cuyo Id es 12121:
UPDATE profesor SET nombre_dept='Física' WHERE id='12121';
-- De esta manera, el profesor Wu, cambiaría de departamento de 'informática' a 'física'.
```
**Continuemos con la carga de datos:**
```SQL
INSERT INTO seccion VALUES
('CS-101',1,'Otoño',2009,'Packard','101','H'),
('CS-101',1,'Primavera',2010,'Packard','101','F'),
('CS-347',1,'Otoño',2009,'Taylor','3128','A'),
('BIO-101',1,'Verano',2009,'Painter','514','B'),
('PHY-101',1,'Otoño',2009,'Watson','100','A');
INSERT INTO imparte VALUES
('10101','CS-101',1,'Otoño',2009),
('83821','CS-347',1,'Otoño',2009),
('22222','PHY-101',1,'Otoño',2009);
INSERT INTO estudiante VALUES
('00128','Zhang',30), ('12345','Shankar',60), ('19991','Brandt',45);
INSERT INTO matricula VALUES
('00128','CS-101',1,'Otoño',2009,4.0),
('12345','CS-347',1,'Otoño',2009,3.7),
('19991','BIO-101',1,'Verano',2009,3.3);
INSERT INTO prerreq VALUES
('BIO-301','BIO-101'),
('CS-315','CS-101'),
('CS-347','CS-101');
```
### 2.1. Plantilla general
Una consulta típica usa **SELECT–FROM–WHERE**. SQL incluye estas cláusulas como núcleo del lenguaje de consultas.  
```sql
SELECT lista_de_columnas          -- proyección
FROM   tabla_o_tablas             -- origen de datos
WHERE  condiciones_de_filtrado;   -- selección
```
#### Alias (renombrado)
##### ¿Qué es un alias en SQL?
Un **alias** es un **nombre alternativo** (temporal) que le damos a una **columna** o a una **tabla** dentro de una consulta.
Se define con la palabra clave `AS` (aunque en muchos motores como MariaDB se puede omitir `AS` y poner solo el nombre).
👉 Los alias **no cambian** el nombre real de la tabla o columna en la base de datos; solo existen mientras se ejecuta la consulta.
##### Alias en columnas
Sirven para:
- Hacer más legibles los resultados.
- Dar nombres más claros en reportes.
- Usar nombres más cortos al manipular funciones o cálculos.
```sql
-- Ejemplo: promedio de sueldos por departamento
SELECT nombre_dept AS departamento,
       AVG(sueldo) AS promedio_sueldo
FROM profesor
GROUP BY nombre_dept;
```
Posible salida:
|departamento|promedio_sueldo|
|---|---|
|Informática|82333.33|
|Biología|72000.00|
|Física|95000.00|
> Sin alias, la columna aparecería como `AVG(sueldo)`, lo cual es menos legible.
##### Alias en tablas
Sirven para:
- Escribir **menos** cuando una tabla tiene un nombre largo.
- Evitar ambigüedad cuando se consultan varias tablas que tienen columnas con el mismo nombre.
- Hacer más claras las auto–joins (cuando una tabla se une consigo misma).
```sql
-- Ejemplo con alias cortos
SELECT p.nombre, e.asignatura_id
FROM profesor AS p
JOIN enseña   AS e ON p.id = e.id;
```
- `profesor` → `p`
- `enseña` → `e`
De esta forma, en lugar de escribir `profesor.id` y `enseña.id`, basta con `p.id` y `e.id`.
##### Auto–join con alias
Los alias son **imprescindibles** cuando una tabla se une consigo misma:
Ejemplo: encontrar profesores cuyo sueldo es mayor que el de algún profesor de Biología.
```sql
SELECT t.nombre AS profesor,
       t.sueldo,
       s.nombre AS colega_biologia,
       s.sueldo AS sueldo_bio
FROM profesor AS t
JOIN profesor AS s
  ON t.sueldo > s.sueldo
WHERE s.nombre_dept = 'Biología';
```
- La misma tabla `profesor` aparece dos veces, pero con alias distintos:
    - `t` = “tabla principal”
    - `s` = “subtabla de referencia”
Sin alias, sería imposible distinguir a qué instancia de `profesor` nos referimos.
##### Alias sin `AS`
MariaDB y MySQL permiten omitir `AS`:
```sql
-- Con AS
SELECT nombre AS profesor FROM profesor;
-- Sin AS
SELECT nombre profesor FROM profesor;
```
Ambas son equivalentes, pero **usar `AS` se recomienda didácticamente** porque hace explícito que estamos renombrando.
#####  Buenas prácticas con alias
1. **Nombres cortos para tablas** → `p`, `e`, `s` (cuando escribes mucho).
2. **Nombres descriptivos para columnas** → `promedio_sueldo`, `total_asignaturas`.
3. **Evita alias crípticos** que dificulten la lectura.
4. Usa alias siempre que hagas **subconsultas**:
    ```sql
    SELECT depto, promedio
    FROM (
      SELECT nombre_dept AS depto, AVG(sueldo) AS promedio
      FROM profesor
      GROUP BY nombre_dept
    ) AS resumen;
    ```
### 2.2. Proyección, origen y selección
**Ejemplo simple 1 — listado de profesores de Biología**
```sql
SELECT nombre
FROM profesor
WHERE nombre_dept = 'Biología';
```
**Ejemplo simple 2 — asignaturas de Otoño 2009**
```sql
SELECT asignatura_id
FROM seccion
WHERE semestre = 'Otoño' AND año = 2009; -- Aquí hay un error, identifiquelo!!!
```
**Orden y eliminación de duplicados**
```sql
SELECT DISTINCT nombre_dept
FROM profesor
ORDER BY nombre_dept;
```
(El estándar contempla `ORDER BY`; `DISTINCT` elimina duplicados.)  
> Cadenas y mayúsculas: recuerda la nota de sensibilidad a mayúsculas según collation.  
```MySQL
-- Departamentos sin duplicados
SELECT DISTINCT nombre_dept
FROM profesor
ORDER BY nombre_dept ASC;   -- ASC/DSC
-- Paginación simple
SELECT id, nombre FROM estudiante
ORDER BY id
LIMIT 10 OFFSET 0;          -- página 1
```
### 2.3. Predicados comunes
#### ¿Qué son los “predicados comunes” en SQL?
Un **predicado** es una expresión que el motor evalúa a **TRUE / FALSE / UNKNOWN** (lógica de 3 valores) y que se usa en `WHERE`, `HAVING`, `ON`, `CHECK` y `CASE`.  
Ej.: `sueldo > 80000` es un predicado; si `sueldo` es `NULL`, el resultado es `UNKNOWN`.
> Regla práctica: en `WHERE` solo pasan las filas cuyo predicado es **TRUE**.  
> `FALSE` y `UNKNOWN` (por `NULL`) se descartan.
A continuación, los **predicados más usados**, con ejemplos sobre el esquema “universidad” que hemos trabajado.
#### a) Comparación: =, <> (o !=), <, <=, >, >=
```sql
-- Profesores con sueldo alto
SELECT id, nombre, sueldo
FROM profesor
WHERE sueldo >= 90000;
```
- Con `NULL`, cualquier comparación produce `UNKNOWN` → la fila no pasa el `WHERE`.
#### b) `BETWEEN` (rango **incluye** extremos)
```sql
-- Sueldos entre 70k y 90k (70k y 90k incluidos)
SELECT nombre, sueldo
FROM profesor
WHERE sueldo BETWEEN 70000 AND 90000;
-- Negación
SELECT nombre, sueldo
FROM profesor
WHERE sueldo NOT BETWEEN 70000 AND 90000;
```
#### c) `IN` / `NOT IN` (pertenencia a conjunto)
```sql
-- Profesores de Biología o Informática
SELECT nombre, nombre_dept
FROM profesor
WHERE nombre_dept IN ('Biología','Informática');
```
> ⚠️ **Cuidado con `NOT IN` + `NULL`**  
> `x NOT IN (subconsulta)` puede devolver **cero filas** si la subconsulta trae algún `NULL`.  
> Prefiere `NOT EXISTS` (ver más abajo) o filtra `NULL` en la subconsulta:
```sql
-- Profesores que NO imparten ninguna sección (patrón correcto)
SELECT p.id, p.nombre
FROM profesor p
WHERE NOT EXISTS (
  SELECT 1
  FROM imparte i
  WHERE i.id = p.id
);
```
#### d) `LIKE` (patrones de cadena)
- `%` = cualquier cadena (incluida vacía)
- `_` = un solo carácter
```sql
-- Nombres que empiezan por 'S'
SELECT nombre FROM profesor
WHERE nombre LIKE 'S%';
-- Escapar el comodín (buscar literalmente '50%')
SELECT texto
FROM notas
WHERE texto LIKE '50\%%' ESCAPE '\';
```
> Mayúsculas/minúsculas: en MariaDB suelen ser **insensibles** por colación.  
> Para forzar sensibilidad usa `COLLATE utf8mb4_bin` o `LIKE BINARY 'Patrón'`.
```sql
-- Búsqueda sensible a mayúsculas
SELECT nombre
FROM profesor
WHERE nombre COLLATE utf8mb4_bin LIKE 'S%';
```
#### e) `IS NULL` / `IS NOT NULL` (nulos explícitos)
```sql
-- Registros con datos faltantes
SELECT id, nombre, sueldo
FROM profesor
WHERE sueldo IS NULL OR nombre_dept IS NULL;
```
> No uses `= NULL` ni `<> NULL`: siempre dan `UNKNOWN`.
#### f) `EXISTS` / `NOT EXISTS` (subconsultas correlacionadas)
```sql
-- Asignaturas que tienen al menos una sección abierta en 2009
SELECT a.asignatura_id, a.nombre
FROM asignatura a
WHERE EXISTS (
  SELECT 1
  FROM seccion s
  WHERE s.asignatura_id = a.asignatura_id
    AND s.anio = 2009
);
```
- `EXISTS` es **verdadero** si la subconsulta devuelve **al menos una fila** (no importa su contenido).
- Patrón ideal para “hay/no hay relación” y para evitar el problema de `NOT IN + NULL`.
#### g) `ANY` / `ALL` (cuantificadores sobre subconsulta)
```sql
-- Profesores con sueldo mayor que el de TODOS los de Biología
SELECT nombre, sueldo
FROM profesor
WHERE sueldo > ALL (
  SELECT sueldo FROM profesor WHERE nombre_dept = 'Biología'
);
-- Mayor que AL MENOS UNO de Biología
SELECT nombre, sueldo
FROM profesor
WHERE sueldo > ANY (
  SELECT sueldo FROM profesor WHERE nombre_dept = 'Biología'
); -- ANY ≈ SOME
```
#### h) `REGEXP` (expresiones regulares de MariaDB)
```sql
-- Asignaturas que empiezan por 'CS-' seguidas de dígitos
SELECT asignatura_id, nombre
FROM asignatura
WHERE asignatura_id REGEXP '^CS-[0-9]+