Módulo 22: Álgebra relacional
Objetivos del módulo
- Distinguir los lenguajes de consulta formales: álgebra relacional (procedimental) y cálculo relacional (declarativo)
- Dominar los operadores primitivos: selección, proyección, unión, diferencia, producto cartesiano y renombrado
- Comprobar la compatibilidad de unión entre relaciones
- Manejar los operadores derivados: intersección, combinaciones (theta, equicombinación y natural), división, semicombinación y antisemicombinación
- Usar las combinaciones externas y el operador de agrupación
- Construir expresiones complejas y secuencias de operaciones con asignación
- Conocer las propiedades y equivalencias algebraicas y su uso en la optimización de consultas
- Expresar consultas en cálculo relacional de tuplas y de dominios
- Traducir cada operador del álgebra a SQL
- Resolver consultas reales sobre el esquema EMPRESA y predecir su resultado exacto
1. Lenguajes de consulta formales
1.1. Álgebra y cálculo
En 1972 Codd definió dos lenguajes formales para manipular relaciones:
| Lenguaje | Tipo | Cómo se expresa una consulta | Analogía |
|---|---|---|---|
| Álgebra relacional | Procedimental | Como una secuencia de operaciones que, aplicadas a relaciones, producen la relación resultado | Una receta de cocina: “primero filtra, luego combina, luego quédate con estas columnas” |
| Cálculo relacional (de tuplas o de dominios) | Declarativo (no procedimental) | Como una condición lógica que deben cumplir las tuplas del resultado | La descripción del plato: “quiero las tuplas que cumplan esto”, sin decir cómo obtenerlas |
SQL toma elementos de ambos: su estructura SELECT ... FROM ... WHERE se parece al cálculo de tuplas, mientras que operadores como UNION, INTERSECT, MINUS o JOIN vienen directamente del álgebra. Otros lenguajes históricos son QUEL (Ingres, basado en el cálculo de tuplas) y QBE (Query By Example, IBM, basado en el cálculo de dominios; su idea sobrevive en los diseñadores visuales de consultas de Access o LibreOffice Base).
📘 Concepto: El álgebra relacional es un conjunto de operadores que toman una o dos relaciones como operandos y devuelven una relación como resultado.
1.2. ¿Por qué estudiar el álgebra si existe SQL?
- Es la base teórica de SQL: entender el álgebra es entender qué hace realmente cada consulta.
- Es el lenguaje interno de los optimizadores: Oracle traduce tu
SELECTa un árbol de operadores algebraicos, lo reorganiza con las equivalencias de la sección 5 y elige el plan más barato. - Obliga a pensar en conjuntos, no en bucles fila a fila.
- Es materia obligada en exámenes de universidad y de ciclos formativos.
1.3. Propiedad de cierre y completitud
📘 Concepto: Propiedad de cierre (closure): el resultado de cualquier operación del álgebra es otra relación. Por eso las operaciones se pueden anidar y componer libremente, igual que en aritmética se escribe
(3 + 4) × 2.
Un lenguaje es relacionalmente completo si puede expresar cualquier consulta que exprese el álgebra relacional (o, equivalentemente, el cálculo relacional seguro). SQL es relacionalmente completo y además va más allá (agregados, ordenación, recursividad).
1.4. Notación del curso
| Operador | Símbolo | Notación | Tipo |
|---|---|---|---|
| Selección | σ | σ[condición](R) | Primitivo, unario |
| Proyección | π | π[atributos](R) | Primitivo, unario |
| Renombrado | ρ | ρ[S](R), ρ[S(B1, ..., Bn)](R) | Primitivo, unario |
| Unión | ∪ | R ∪ S | Primitivo, binario |
| Diferencia | − | R − S | Primitivo, binario |
| Producto cartesiano | × | R × S | Primitivo, binario |
| Intersección | ∩ | R ∩ S | Derivado |
| Combinación theta | ⋈ | R ⋈[condición] S | Derivado |
| Combinación natural | ⋈ | R ⋈ S | Derivado |
| División | ÷ | R ÷ S | Derivado |
| Semicombinación | ⋉ | R ⋉[condición] S | Derivado |
| Antisemicombinación | ▷ | R ▷[condición] S | Derivado |
| Combinaciones externas | ⟕ ⟖ ⟗ | R ⟕[condición] S | Extendido |
| Agrupación | γ | γ[atributos de grupo; funciones](R) | Extendido |
| Asignación | ← | T ← expresión | Auxiliar |
En las condiciones se usan los comparadores =, ≠, <, ≤, >, ≥ y los conectores ∧ (y), ∨ (o) y ¬ (no). Para los nulos se admiten IS NULL e IS NOT NULL. Cuando dos relaciones tienen atributos con el mismo nombre, se cualifican con el nombre de la relación: EMPLEADOS.dept_id.
💡 Consejo: En otros libros verás la condición como subíndice del símbolo (σ con «salario > 2000» escrito debajo) o con los operadores en palabras (
SELECT,PROJECT,JOIN). Son exactamente lo mismo; en este curso los corchetes facilitan escribirlo en texto plano.
1.5. Las relaciones de ejemplo
Todas las consultas trabajan sobre el esquema EMPRESA (módulo 16) con los datos de ../recursos/empresa_datos.sql. Estas son las relaciones que más se usan, para que puedas comprobar a mano cada resultado:
EMPLEADOS (se omiten email, teléfono y fecha de alta)
| emp_id | nombre | apellidos | oficio | salario | comision | jefe_id | dept_id |
|---|---|---|---|---|---|---|---|
| 100 | Ana | García López | DIRECTOR GENERAL | 6500 | NULL | NULL | 10 |
| 101 | Luis | Martínez Ruiz | DIRECTOR | 4800 | NULL | 100 | 20 |
| 102 | Marta | Sánchez Gil | DIRECTOR | 5000 | NULL | 100 | 30 |
| 103 | Pedro | Gómez Díaz | DIRECTOR | 4200 | NULL | 100 | 40 |
| 104 | Laura | Fernández Moreno | DIRECTOR | 4300 | NULL | 100 | 50 |
| 105 | Javier | López Navarro | DIRECTOR | 4000 | NULL | 100 | 60 |
| 110 | Carmen | Ruiz Ortega | VENDEDOR | 2200 | 500 | 101 | 20 |
| 111 | David | Torres Romero | VENDEDOR | 2100 | 800 | 101 | 20 |
| 112 | Elena | Navarro Castro | VENDEDOR | 2300 | 300 | 101 | 20 |
| 113 | Sergio | Jiménez Vidal | VENDEDOR | 1900 | 0 | 101 | 20 |
| 114 | Lucía | Romero Serrano | ADMINISTRATIVO | 1700 | NULL | 101 | 20 |
| 120 | Pablo | Moreno Herrera | ANALISTA | 3500 | NULL | 102 | 30 |
| 121 | Raquel | Molina Prieto | PROGRAMADOR | 2800 | NULL | 120 | 30 |
| 122 | Iván | Castro Méndez | PROGRAMADOR | 2600 | NULL | 120 | 30 |
| 123 | Sara | Ortiz Delgado | PROGRAMADOR | 2400 | NULL | 120 | 30 |
| 124 | Hugo | Rubio Marín | TÉCNICO SISTEMAS | 2500 | NULL | 102 | 30 |
| 130 | Isabel | Domínguez Vega | CONTABLE | 2600 | NULL | 103 | 40 |
| 131 | Jorge | Vázquez Ramos | ADMINISTRATIVO | 1650 | NULL | 103 | 40 |
| 140 | Nuria | Gil Soto | DISEÑADOR | 2300 | NULL | 104 | 50 |
| 141 | Álvaro | Peña Cano | ANALISTA | 2700 | NULL | 104 | 50 |
| 150 | Rosa | Iglesias Cruz | OPERARIO | 1600 | NULL | 105 | 60 |
| 151 | Manuel | Herrero Lozano | OPERARIO | 1600 | NULL | 105 | 60 |
| 152 | Cristina | Medina Blanco | ADMINISTRATIVO | 1750 | NULL | 105 | 60 |
| 160 | Tomás | Reyes Fuentes | BECARIO | 900 | NULL | 102 | NULL |
DEPARTAMENTOS
| dept_id | nombre | localidad | presupuesto |
|---|---|---|---|
| 10 | DIRECCIÓN | Madrid | 500000 |
| 20 | VENTAS | Madrid | 350000 |
| 30 | INFORMÁTICA | Valencia | 420000 |
| 40 | CONTABILIDAD | Sevilla | 150000 |
| 50 | MARKETING | Barcelona | 200000 |
| 60 | LOGÍSTICA | Zaragoza | 180000 |
| 70 | INVESTIGACIÓN | Bilbao | NULL |
PROYECTOS (se omiten las fechas)
| proyecto_id | nombre | dept_id | presupuesto |
|---|---|---|---|
| 1 | Migración a la nube | 30 | 120000 |
| 2 | Nueva tienda online | 30 | 90000 |
| 3 | Campaña Navidad 2025 | 50 | 40000 |
| 4 | Optimización de almacén | 60 | 35000 |
| 5 | Plan estratégico 2027 | 10 | NULL |
ASIGNACIONES (se omite la fecha)
| emp_id | proyecto_id | horas | funcion |
|---|---|---|---|
| 120 | 1 | 450 | Jefe de proyecto |
| 121 | 1 | 600 | Desarrollador |
| 124 | 1 | 380 | Administrador de sistemas |
| 120 | 2 | 300 | Jefe de proyecto |
| 122 | 2 | 520 | Desarrollador |
| 123 | 2 | 410 | Desarrollador |
| 140 | 2 | 120 | Diseñador |
| 104 | 3 | 80 | Responsable |
| 140 | 3 | 200 | Diseñador |
| 141 | 3 | 160 | Analista |
| 110 | 3 | 40 | Apoyo comercial |
| 105 | 4 | 60 | Responsable |
| 150 | 4 | 150 | Operario |
| 152 | 4 | 90 | Administrativo |
2. Operadores primitivos
2.1. Selección (σ)
📘 Concepto: La selección
σ[condición](R)devuelve las tuplas de R que cumplen la condición (para las que la condición es verdadera). Es un filtro horizontal: elige filas.
- Grado del resultado: el mismo que R (no cambia los atributos).
- Cardinalidad: menor o igual que la de R.
- La condición se evalúa tupla a tupla y solo puede referirse a atributos de esa tupla y a constantes.
💻 Ejemplo: Empleados del departamento 40.
σ[dept_id = 40](EMPLEADOS)
| emp_id | nombre | apellidos | oficio | salario | comision | jefe_id | dept_id |
|---|---|---|---|---|---|---|---|
| 103 | Pedro | Gómez Díaz | DIRECTOR | 4200 | NULL | 100 | 40 |
| 130 | Isabel | Domínguez Vega | CONTABLE | 2600 | NULL | 103 | 40 |
| 131 | Jorge | Vázquez Ramos | ADMINISTRATIVO | 1650 | NULL | 103 | 40 |
Condiciones compuestas: σ[oficio = 'VENDEDOR' ∧ comision > 400](EMPLEADOS) devuelve a Carmen (500) y David (800).
Propiedades:
- Conmutativa:
σ[c1](σ[c2](R)) = σ[c2](σ[c1](R)). - Cascada:
σ[c1 ∧ c2](R) = σ[c1](σ[c2](R)).
⚠️ Importante: Con nulos se aplica la lógica trivaluada (módulo 16): la selección solo devuelve las tuplas para las que la condición es V.
σ[comision < 500](EMPLEADOS)devuelve 2 tuplas (Elena y Sergio), no 22: las 20 comisiones nulas dan desconocido y se descartan.
2.2. Proyección (π)
📘 Concepto: La proyección
π[A1, ..., Ak](R)devuelve una relación con solo los atributos indicados de R, eliminando las tuplas duplicadas que puedan aparecer. Es un filtro vertical: elige columnas.
- Grado: k (el número de atributos de la lista).
- Cardinalidad: menor o igual que la de R. Es igual si la lista contiene una clave de R.
💻 Ejemplo: Localidades en las que hay departamentos.
π[localidad](DEPARTAMENTOS)
| localidad |
|---|
| Madrid |
| Valencia |
| Sevilla |
| Barcelona |
| Zaragoza |
| Bilbao |
Hay 7 departamentos pero 6 localidades: Madrid aparecía dos veces (DIRECCIÓN y VENTAS) y la proyección, al devolver un conjunto, la deja una sola vez.
⚠️ Importante: Esta es la diferencia más importante entre el álgebra y SQL.
SELECT localidad FROM departamentos;devuelve 7 filas con Madrid repetido; para obtener las 6 del álgebra hay que escribirSELECT DISTINCT localidad FROM departamentos;.
Combinando selección y proyección: “nombre y salario de los empleados del departamento 30”:
π[nombre, salario](σ[dept_id = 30](EMPLEADOS))
| nombre | salario |
|---|---|
| Marta | 5000 |
| Pablo | 3500 |
| Raquel | 2800 |
| Iván | 2600 |
| Sara | 2400 |
| Hugo | 2500 |
💡 Consejo: El orden importa:
σ[dept_id = 30](π[nombre, salario](EMPLEADOS))es incorrecta, porque después de proyectar ya no existe el atributodept_id. Selecciona primero y proyecta después, salvo que la condición use solo atributos proyectados.
2.3. Compatibilidad de unión
Los operadores de conjuntos (∪, −, ∩) solo se pueden aplicar a dos relaciones compatibles para la unión (union-compatible):
📘 Concepto: R(A1, …, An) y S(B1, …, Bn) son compatibles para la unión si tienen el mismo grado n y, para cada i, los dominios de Ai y Bi son compatibles (el mismo dominio o dominios comparables). Por convenio, el resultado toma los nombres de atributo del primer operando.
| ¿Compatibles? | Expresiones | Motivo |
|---|---|---|
| Sí | π[emp_id](ASIGNACIONES) y π[jefe_id](EMPLEADOS) | Grado 1 y ambos son identificadores de empleado |
| No | π[emp_id](EMPLEADOS) y π[emp_id, proyecto_id](ASIGNACIONES) | Grado distinto (1 y 2) |
| No (semánticamente) | π[dept_id](DEPARTAMENTOS) y π[proyecto_id](PROYECTOS) | Mismo grado y tipo numérico, pero dominios sin relación: la unión no tiene sentido |
2.4. Unión (∪)
📘 Concepto:
R ∪ Sdevuelve las tuplas que están en R, en S o en ambas (sin duplicados).
💻 Ejemplo: Empleados asignados al proyecto 1 o al proyecto 2.
π[emp_id](σ[proyecto_id = 1](ASIGNACIONES)) ∪ π[emp_id](σ[proyecto_id = 2](ASIGNACIONES))
El primer operando es {120, 121, 124}; el segundo, {120, 122, 123, 140}. La unión tiene 6 tuplas: 120, 121, 122, 123, 124 y 140 (el 120 aparece una sola vez).
| Propiedades: conmutativa y asociativa. Cardinalidad: máx( | R | , | S | ) ≤ | R ∪ S | ≤ | R | + | S | . |
2.5. Diferencia (−)
📘 Concepto:
R − Sdevuelve las tuplas que están en R pero no en S.
💻 Ejemplo: Departamentos sin empleados.
π[dept_id](DEPARTAMENTOS) − π[dept_id](σ[dept_id IS NOT NULL](EMPLEADOS))
El primer operando es {10, 20, 30, 40, 50, 60, 70}; el segundo, {10, 20, 30, 40, 50, 60}. Resultado: {70}.
⚠️ Importante: La diferencia no es conmutativa:
R − S ≠ S − R. Y es el operador clave para expresar negaciones (“que no…”, “sin…”, “nunca…”), que no se pueden expresar solo con σ, π, ∪ y ×.
2.6. Producto cartesiano (×)
📘 Concepto:
R × Sdevuelve todas las combinaciones de una tupla de R con una tupla de S. Si R tiene grado n y cardinalidad r, y S grado m y cardinalidad s, el producto tiene grado n + m y cardinalidad r × s.
💻 Ejemplo:
σ[localidad = 'Madrid'](DEPARTAMENTOS) × π[proyecto_id, nombre](PROYECTOS)combina los 2 departamentos de Madrid con los 5 proyectos: 10 tuplas de grado 4 + 2 = 6. Como ambas relaciones tienen un atributonombre, se cualifican:
| dept_id | DEPARTAMENTOS.nombre | localidad | presupuesto | proyecto_id | PROYECTOS.nombre |
|---|---|---|---|---|---|
| 10 | DIRECCIÓN | Madrid | 500000 | 1 | Migración a la nube |
| 10 | DIRECCIÓN | Madrid | 500000 | 2 | Nueva tienda online |
| … | … | … | … | … | … |
| 20 | VENTAS | Madrid | 350000 | 5 | Plan estratégico 2027 |
(10 tuplas.) El producto por sí solo rara vez tiene sentido: mezcla tuplas que no tienen nada que ver. Su utilidad es servir de base a la combinación: un producto seguido de una selección que empareja las tuplas relacionadas.
2.7. Renombrado (ρ)
📘 Concepto:
ρ[S](R)da el nombre S a la relación R;ρ[S(B1, ..., Bn)](R)además renombra sus atributos. No cambia las tuplas.
Es imprescindible para:
- Combinar una relación consigo misma (autocombinación):
ρ[J](EMPLEADOS)permite tener “dos copias” de EMPLEADOS, una para el empleado y otra para el jefe. - Hacer compatibles dos relaciones o controlar qué atributos coinciden en una combinación natural.
- Dar nombre a resultados intermedios.
2.8. Conjunto completo de operadores
El conjunto {σ, π, ∪, −, ×} (más ρ para resolver conflictos de nombres) es completo: cualquier otro operador del álgebra relacional clásica se puede expresar con ellos. Por eso los demás se llaman derivados: no añaden potencia expresiva, sino comodidad.
3. Operadores derivados
3.1. Intersección (∩)
📘 Concepto:
R ∩ Sdevuelve las tuplas que están a la vez en R y en S. Requiere compatibilidad de unión. Se deriva de la diferencia: R ∩ S = R − (R − S).
💻 Ejemplo: Empleados asignados a la vez a los proyectos 1 y 2: {120, 121, 124} ∩ {120, 122, 123, 140} = {120} (Pablo).
⚠️ Importante: No confundas “empleados asignados al proyecto 1 y al 2” (intersección de dos conjuntos) con
σ[proyecto_id = 1 ∧ proyecto_id = 2](ASIGNACIONES), que siempre es vacía: ninguna tupla tiene a la vez dos valores en el mismo atributo.
3.2. Combinación theta y equicombinación
📘 Concepto: La combinación theta (theta join) de R y S con una condición θ es el producto cartesiano seguido de una selección: R ⋈[θ] S = σθ. Si la condición solo usa igualdades, se llama equicombinación (equijoin).
💻 Ejemplo: Empleados con el nombre y la localidad de su departamento (equicombinación por la clave ajena):
EMPLEADOS ⋈[EMPLEADOS.dept_id = DEPARTAMENTOS.dept_id] DEPARTAMENTOS
El resultado tiene 23 tuplas de grado 11 + 4 = 15 (los atributos dept_id aparecen dos veces, con el mismo valor). Tomás (160) no aparece: su dept_id es nulo y NULL = valor nunca es verdadero. El departamento 70 tampoco aparece: ningún empleado lo referencia.
💻 Ejemplo: Combinación theta con desigualdad: empleados que ganan más que Pablo (120).
EMPLEADOS ⋈[EMPLEADOS.salario > P.salario] σ[emp_id = 120](ρ[P](EMPLEADOS))
Devuelve los 6 empleados con salario mayor que 3500: Ana, Luis, Marta, Pedro, Laura y Javier.
3.3. Combinación natural
📘 Concepto: La combinación natural
R ⋈ Ses una equicombinación sobre todos los atributos con el mismo nombre en R y S, en la que cada atributo común aparece una sola vez en el resultado. Si no hay atributos comunes, equivale al producto cartesiano.
💻 Ejemplo:
ASIGNACIONES ⋈ PROYECTOS. El único atributo común esproyecto_id, así que el resultado contiene las 14 asignaciones con los datos de su proyecto (el proyecto 5, sin asignaciones, no aparece), conproyecto_iduna sola vez: grado 5 + 6 − 1 = 10.
⚠️ Importante: La combinación natural compara por nombre, no por significado, y es una fuente clásica de errores. En EMPRESA,
EMPLEADOS ⋈ DEPARTAMENTOScomparadept_idy tambiénnombre(los dos tienen un atributonombre): exige que el empleado se llame igual que su departamento, y el resultado es vacío (0 tuplas). Lo mismo ocurre conEMPLEADOS ⋈ PROYECTOS(compartennombreydept_id) oDEPARTAMENTOS ⋈ PROYECTOS(compartendept_id,nombreypresupuesto).
Soluciones: usar una equicombinación con la condición explícita, o renombrar antes los atributos que no deben coincidir:
EMPLEADOS ⋈ ρ[D(dept_id, nombre_dep, localidad, presupuesto)](DEPARTAMENTOS)
Ahora el único atributo común es dept_id y el resultado tiene 23 tuplas, de grado 11 + 4 − 1 = 14.
💡 Consejo: En SQL pasa exactamente lo mismo con
NATURAL JOIN. Por eso en el trabajo profesional casi siempre se escribeJOIN ... ONcon la condición explícita (módulo 30).
3.4. Semicombinación (⋉) y antisemicombinación (▷)
📘 Concepto: La semicombinación (semijoin)
R ⋉[θ] Sdevuelve las tuplas de R que tienen alguna pareja en S según la condición, sin añadir atributos de S: R ⋉[θ] S = πatributos de R. La antisemicombinación (antijoin)R ▷[θ] Sdevuelve las tuplas de R que no tienen ninguna pareja en S: R ▷[θ] S = R − (R ⋉[θ] S).
💻 Ejemplo: Departamentos que tienen algún proyecto:
DEPARTAMENTOS ⋉[DEPARTAMENTOS.dept_id = PROYECTOS.dept_id] PROYECTOS
Resultado: 4 tuplas completas de DEPARTAMENTOS (10, 30, 50 y 60). Observa que el 30 tiene dos proyectos pero aparece una vez. Y la antisemicombinación, DEPARTAMENTOS ▷[DEPARTAMENTOS.dept_id = PROYECTOS.dept_id] PROYECTOS, devuelve los 3 restantes: 20, 40 y 70.
En SQL corresponden a EXISTS / IN y a NOT EXISTS / NOT IN (módulo 31). En otros textos la antisemicombinación se escribe también ⊳ o con el símbolo ⋉ tachado.
3.5. División (÷)
📘 Concepto: Sean R(A, B) y S(B), donde A y B son conjuntos de atributos. La división
R ÷ Sdevuelve los valores de A que están relacionados en R con todos los valores de S. Formalmente, una tupla t está en R ÷ S si para toda tupla s de S, la tupla (t, s) está en R.
La división responde a las consultas con “todos”: “empleados que trabajan en todos los proyectos de…”, “clientes que han comprado todos los productos de…”. Se deriva de los operadores primitivos así:
R ÷ S = π[A](R) − π[A]( (π[A](R) × S) − R )
Intuición: π[A](R) × S son todas las parejas que deberían existir si cada candidato estuviera relacionado con todo S; restándole R quedan las parejas que faltan; los candidatos con alguna pareja que falta se descartan.
Ejemplo paso a paso: empleados que trabajan en todos los proyectos del departamento 30
Paso 0. Preparar dividendo y divisor.
R ← π[emp_id, proyecto_id](ASIGNACIONES) (14 tuplas)
S ← π[proyecto_id](σ[dept_id = 30](PROYECTOS)) (2 tuplas: 1 y 2)
Paso 1. Candidatos. T1 ← π[emp_id](R): los 12 empleados con alguna asignación.
T1 = {104, 105, 110, 120, 121, 122, 123, 124, 140, 141, 150, 152}
Paso 2. Parejas que deberían existir. T2 ← T1 × S: cada candidato con cada proyecto del departamento 30, 12 × 2 = 24 tuplas: (104,1), (104,2), (105,1), (105,2), …, (152,1), (152,2).
Paso 3. Parejas que faltan. T3 ← T2 − R. De las 24 parejas, las que sí están en R son siete: (120,1), (121,1), (124,1), (120,2), (122,2), (123,2) y (140,2). Quedan 17 tuplas:
| emp_id | proyecto_id | emp_id | proyecto_id | |
|---|---|---|---|---|
| 104 | 1 | 123 | 1 | |
| 104 | 2 | 124 | 2 | |
| 105 | 1 | 140 | 1 | |
| 105 | 2 | 141 | 1 | |
| 110 | 1 | 141 | 2 | |
| 110 | 2 | 150 | 1 | |
| 121 | 2 | 150 | 2 | |
| 122 | 1 | 152 | 1 | |
| 152 | 2 |
Paso 4. Candidatos a los que les falta algo. T4 ← π[emp_id](T3) = {104, 105, 110, 121, 122, 123, 124, 140, 141, 150, 152}: 11 empleados.
Paso 5. Resultado. T1 − T4 = {120}. Pablo Moreno es el único que trabaja en los dos proyectos del departamento 30.
⚠️ Importante: Si el divisor S está vacío (por ejemplo, “todos los proyectos del departamento 70”, que no tiene ninguno), la división devuelve todos los candidatos πA: “para todo elemento de un conjunto vacío” es siempre verdadero. Ten en cuenta este caso límite al interpretar los resultados.
3.6. Combinaciones externas (⟕ ⟖ ⟗)
La combinación normal (interna) pierde las tuplas que no tienen pareja: en EMPLEADOS ⋈[...] DEPARTAMENTOS desaparecen Tomás (sin departamento) y el departamento 70 (sin empleados). Las combinaciones externas las conservan rellenando con nulos.
| Operador | Nombre | Conserva |
|---|---|---|
R ⟕[θ] S | Combinación externa izquierda (left outer join) | Todas las tuplas de R; las que no tienen pareja, con nulos en los atributos de S |
R ⟖[θ] S | Combinación externa derecha (right outer join) | Todas las tuplas de S, con nulos en los de R si no tienen pareja |
R ⟗[θ] S | Combinación externa completa (full outer join) | Todas las tuplas de R y de S |
💻 Ejemplo: Con la condición
EMPLEADOS.dept_id = DEPARTAMENTOS.dept_id:
| Expresión | Tuplas | Por qué |
|---|---|---|
EMPLEADOS ⋈[θ] DEPARTAMENTOS | 23 | Los empleados con departamento |
DEPARTAMENTOS ⟕[θ] EMPLEADOS | 24 | Las 23 anteriores + el departamento 70 con nulos en los atributos de empleado |
EMPLEADOS ⟕[θ] DEPARTAMENTOS | 24 | Las 23 + Tomás con nulos en los atributos de departamento |
EMPLEADOS ⟗[θ] DEPARTAMENTOS | 25 | Las 23 + Tomás + el departamento 70 |
Fragmento de π[DEPARTAMENTOS.dept_id, DEPARTAMENTOS.nombre, emp_id, EMPLEADOS.nombre](EMPLEADOS ⟗[θ] DEPARTAMENTOS):
| DEPARTAMENTOS.dept_id | DEPARTAMENTOS.nombre | emp_id | EMPLEADOS.nombre |
|---|---|---|---|
| 10 | DIRECCIÓN | 100 | Ana |
| 20 | VENTAS | 101 | Luis |
| … | … | … | … |
| 70 | INVESTIGACIÓN | NULL | NULL |
| NULL | NULL | 160 | Tomás |
(25 tuplas.) Las combinaciones externas no forman parte del álgebra original de Codd, que no tenía nulos; se añadieron después y hoy son imprescindibles.
3.7. Operadores extendidos: proyección generalizada y agrupación
Proyección generalizada. Permite expresiones aritméticas y renombrar con →: π[nombre, salario * 14 → salario_anual](EMPLEADOS).
Agrupación (γ). γ[G1, ..., Gk; F1(A1) → N1, ...](R) agrupa las tuplas de R por los valores de los atributos de agrupación G y calcula, para cada grupo, las funciones de agregado COUNT, SUM, AVG, MAX y MIN. El resultado tiene una tupla por grupo.
💻 Ejemplo: Número de empleados, salario medio y máximo de cada departamento.
γ[dept_id; COUNT(*) → num_emp, AVG(salario) → media, MAX(salario) → maximo](EMPLEADOS)
| dept_id | num_emp | media | maximo |
|---|---|---|---|
| 10 | 1 | 6500 | 6500 |
| 20 | 6 | 2500 | 4800 |
| 30 | 6 | 3133,33 | 5000 |
| 40 | 3 | 2816,67 | 4200 |
| 50 | 3 | 3100 | 4300 |
| 60 | 4 | 2237,5 | 4000 |
| NULL | 1 | 900 | 900 |
(7 tuplas.) Como en SQL, las tuplas con dept_id nulo forman su propio grupo (Tomás). Sin atributos de agrupación, γ devuelve una sola tupla: γ[; COUNT(*) → total](EMPLEADOS) = 24.
Otros operadores del álgebra extendida que encontrarás en la bibliografía: eliminación de duplicados δ y ordenación τ, necesarios cuando el álgebra trabaja con multiconjuntos (como SQL) en lugar de con conjuntos.
4. Expresiones y secuencias de operaciones
Una consulta compleja se puede escribir como una sola expresión anidada o como una secuencia de asignaciones T ← expresión, que da nombre a resultados intermedios. Ambas formas son equivalentes; la secuencia es más legible y más fácil de corregir.
💻 Ejemplo: Nombre y apellidos de los empleados del departamento de INFORMÁTICA asignados al proyecto “Nueva tienda online”.
En una sola expresión:
π[EMPLEADOS.nombre, apellidos](
σ[dept_id = 30](EMPLEADOS)
⋈[EMPLEADOS.emp_id = ASIGNACIONES.emp_id]
(ASIGNACIONES ⋈[ASIGNACIONES.proyecto_id = PROYECTOS.proyecto_id] σ[nombre = 'Nueva tienda online'](PROYECTOS))
)
En secuencia (más clara, y además no depende de saber que INFORMÁTICA es el 30):
T1 ← π[dept_id](σ[nombre = 'INFORMÁTICA'](DEPARTAMENTOS)) {30}
T2 ← π[proyecto_id](σ[nombre = 'Nueva tienda online'](PROYECTOS)) {2}
T3 ← π[emp_id](ASIGNACIONES ⋈ T2) {120, 122, 123, 140}
T4 ← EMPLEADOS ⋈ T1 (los 6 empleados del 30)
RESULTADO ← π[nombre, apellidos](T4 ⋈ T3)
| nombre | apellidos |
|---|---|
| Pablo | Moreno Herrera |
| Iván | Castro Méndez |
| Sara | Ortiz Delgado |
(3 tuplas.) Nuria (140) trabaja en el proyecto pero pertenece a MARKETING. Observa que las combinaciones naturales de la secuencia son seguras porque T1, T2 y T3 tienen un único atributo, que es justo el que debe coincidir.
💡 Consejo: Método para resolver cualquier consulta: 1) identifica qué relaciones contienen los datos pedidos y los datos de las condiciones; 2) selecciona cuanto antes en cada una; 3) combina por las claves ajenas; 4) proyecta al final los atributos pedidos. Si la consulta contiene “no”, “nunca” o “sin”, piensa en una diferencia; si contiene “todos”, en una división.
5. Propiedades, equivalencias y optimización
5.1. Reglas de equivalencia
Dos expresiones son equivalentes (≡) si devuelven la misma relación para cualquier extensión de la base de datos. Estas son las equivalencias más usadas:
| N.º | Equivalencia | Nombre |
|---|---|---|
| 1 | σ[c1 ∧ c2](R) ≡ σ[c1](σ[c2](R)) | Cascada (descomposición) de selecciones |
| 2 | σ[c1](σ[c2](R)) ≡ σ[c2](σ[c1](R)) | Conmutatividad de la selección |
| 3 | π[L1](π[L2](R)) ≡ π[L1](R), si L1 ⊆ L2 | Cascada de proyecciones |
| 4 | π[L](σ[c](R)) ≡ σ[c](π[L](R)), si c solo usa atributos de L | Conmutación de σ y π |
| 5 | R ⋈ S ≡ S ⋈ R, R × S ≡ S × R | Conmutatividad de ⋈ y × (salvo el orden de atributos) |
| 6 | (R ⋈ S) ⋈ T ≡ R ⋈ (S ⋈ T) | Asociatividad de ⋈ y × |
| 7 | σ[c](R ⋈ S) ≡ σ[c](R) ⋈ S, si c solo usa atributos de R | Empujar selecciones a través de ⋈ y × |
| 8 | σ[c1 ∧ c2](R ⋈ S) ≡ σ[c1](R) ⋈ σ[c2](S) (c1 de R, c2 de S) | Empujar selecciones a ambos lados |
| 9 | π[L](R ⋈[θ] S) ≡ π[L](π[L1](R) ⋈[θ] π[L2](S)), con L1 y L2 los atributos de L y de θ en cada relación | Empujar proyecciones |
| 10 | σ[θ](R × S) ≡ R ⋈[θ] S | Producto + selección = combinación |
| 11 | R ∪ S ≡ S ∪ R, R ∩ S ≡ S ∩ R (la − no) | Conmutatividad de ∪ y ∩ |
| 12 | σ[c](R ∪ S) ≡ σ[c](R) ∪ σ[c](S), igual con ∩ y − | Distribución de σ sobre operaciones de conjuntos |
| 13 | π[L](R ∪ S) ≡ π[L](R) ∪ π[L](S) | Distribución de π sobre ∪ (no sobre ∩ ni −) |
⚠️ Importante: La proyección no se distribuye sobre la diferencia. Contraejemplo: R = {(1,’a’)}, S = {(1,’b’)}.
π[1](R − S) = {1}, peroπ[1](R) − π[1](S) = {1} − {1} = ∅.
5.2. Uso en la optimización
El optimizador del SGBD recibe la consulta, la traduce a un árbol de consulta (hojas = relaciones, nodos internos = operadores) y lo transforma con las equivalencias anteriores siguiendo unas heurísticas:
- Descomponer las selecciones conjuntivas en cascada (regla 1).
- Empujar las selecciones lo más abajo posible del árbol (reglas 2, 7, 8, 12): cuanto antes se filtre, menos tuplas viajan hacia arriba.
- Sustituir los productos seguidos de selección por combinaciones (regla 10).
- Reordenar las combinaciones para ejecutar primero las más restrictivas (reglas 5 y 6).
- Empujar las proyecciones hacia abajo para descartar atributos innecesarios (reglas 3, 4, 9).
💻 Ejemplo: “Nombre y apellidos de los empleados de departamentos situados en Valencia que trabajan en proyectos con presupuesto superior a 100 000 €”.
Expresión canónica (la traducción directa, como haría un SELECT con varias tablas en el FROM):
π[E.nombre, E.apellidos](
σ[D.localidad = 'Valencia' ∧ P.presupuesto > 100000 ∧ E.dept_id = D.dept_id
∧ E.emp_id = A.emp_id ∧ A.proyecto_id = P.proyecto_id](
ρ[E](EMPLEADOS) × ρ[D](DEPARTAMENTOS) × ρ[A](ASIGNACIONES) × ρ[P](PROYECTOS)))
Árbol canónico:
flowchart TB
c1["π[E.nombre, E.apellidos]"] --> c2["σ[localidad = 'Valencia' ∧ presupuesto > 100000 ∧ 3 condiciones de combinación]"]
c2 --> c3["×"]
c3 --> c4["×"]
c3 --> c5["P: PROYECTOS (5)"]
c4 --> c6["×"]
c4 --> c7["A: ASIGNACIONES (14)"]
c6 --> c8["E: EMPLEADOS (24)"]
c6 --> c9["D: DEPARTAMENTOS (7)"]
El producto de las cuatro relaciones genera 24 × 7 × 14 × 5 = 11 760 tuplas intermedias, de las que la selección se queda con 3.
Árbol optimizado (selecciones empujadas a las hojas y productos convertidos en combinaciones):
flowchart TB
o1["π[E.nombre, E.apellidos]"] --> o2["⋈[E.emp_id = A.emp_id] (3 tuplas)"]
o2 --> o3["⋈[E.dept_id = D.dept_id] (6 tuplas)"]
o2 --> o4["⋈[A.proyecto_id = P.proyecto_id] (3 tuplas)"]
o3 --> o5["E: EMPLEADOS (24)"]
o3 --> o6["σ[localidad = 'Valencia'] (1 tupla)"]
o6 --> o7["D: DEPARTAMENTOS (7)"]
o4 --> o8["A: ASIGNACIONES (14)"]
o4 --> o9["σ[presupuesto > 100000] (1 tupla)"]
o9 --> o10["P: PROYECTOS (5)"]
Ahora el resultado intermedio más grande tiene 6 tuplas. Ambos árboles devuelven lo mismo:
| nombre | apellidos |
|---|---|
| Pablo | Moreno Herrera |
| Raquel | Molina Prieto |
| Hugo | Rubio Marín |
💡 Consejo: En SQL no tienes que hacer esta optimización a mano: el optimizador de Oracle la hace por ti. Pero entenderla te permite leer un plan de ejecución (
EXPLAIN PLAN) y saber por qué una consulta es lenta.
6. Cálculo relacional
6.1. Cálculo relacional de tuplas (CRT)
📘 Concepto: En el cálculo relacional de tuplas una consulta tiene la forma **{ t P(t) }: “el conjunto de tuplas t que cumplen el predicado P”. Las variables recorren **tuplas de relaciones.
Elementos de las fórmulas:
- Átomos:
R(t)(t es una tupla de R),t.A op s.B,t.A op constante, con op ∈ {=, ≠, <, ≤, >, ≥}. - Conectores: ∧, ∨, ¬, → (implica).
- Cuantificadores: ∃ (existe) y ∀ (para todo). Una variable cuantificada está ligada; la que aparece a la izquierda de la barra es libre.
| Consulta | Cálculo de tuplas |
|---|---|
| Empleados con salario mayor que 3000 | { t \| EMPLEADOS(t) ∧ t.salario > 3000 } |
| Nombre y salario de esos empleados | { t.nombre, t.salario \| EMPLEADOS(t) ∧ t.salario > 3000 } |
| Empleados de departamentos de Valencia con el nombre del departamento | { e.nombre, d.nombre \| EMPLEADOS(e) ∧ DEPARTAMENTOS(d) ∧ e.dept_id = d.dept_id ∧ d.localidad = 'Valencia' } |
| Departamentos con algún proyecto (∃) | { d \| DEPARTAMENTOS(d) ∧ ∃p (PROYECTOS(p) ∧ p.dept_id = d.dept_id) } |
| Empleados sin ninguna asignación (¬∃) | { e \| EMPLEADOS(e) ∧ ¬∃a (ASIGNACIONES(a) ∧ a.emp_id = e.emp_id) } |
La división se expresa de forma muy natural con ∀: “empleados que trabajan en todos los proyectos del departamento 30”:
{ e.emp_id | EMPLEADOS(e) ∧ ∀p ( (PROYECTOS(p) ∧ p.dept_id = 30)
→ ∃a (ASIGNACIONES(a) ∧ a.emp_id = e.emp_id ∧ a.proyecto_id = p.proyecto_id) ) }
Como SQL no tiene ∀, se usa la equivalencia ∀x P(x) ≡ ¬∃x ¬P(x): “no existe ningún proyecto del departamento 30 en el que no exista una asignación del empleado”. Es el famoso doble NOT EXISTS de SQL (sección 7).
⚠️ Importante: Una expresión como
{ t | ¬EMPLEADOS(t) }(“todas las tuplas que no son empleados”) describe un conjunto infinito. Se llaman expresiones inseguras y no se admiten. Una expresión es segura si todos los valores de su resultado proceden de las relaciones de la base de datos (de su dominio activo).
6.2. Cálculo relacional de dominios (CRD)
📘 Concepto: En el cálculo relacional de dominios las variables recorren valores de los dominios (no tuplas). Una consulta tiene la forma **{ ⟨x1, …, xn⟩ P(x1, …, xn) }**, y el átomo R(x1, ..., xm)significa que la tupla formada por esos valores pertenece a R (por posición).
Usando DEPARTAMENTOS(dept_id, nombre, localidad, presupuesto) y PROYECTOS(proyecto_id, nombre, dept_id, fecha_inicio, fecha_fin, presupuesto):
| Consulta | Cálculo de dominios |
|---|---|
| Nombre de los departamentos de Madrid | { ⟨n⟩ \| ∃d ∃p DEPARTAMENTOS(d, n, 'Madrid', p) } |
| Nombre de cada proyecto y localidad de su departamento | { ⟨np, l⟩ \| ∃i ∃d ∃fi ∃ff ∃pp ∃nd ∃pd (PROYECTOS(i, np, d, fi, ff, pp) ∧ DEPARTAMENTOS(d, nd, l, pd)) } |
| Departamentos sin proyectos | { ⟨d, n⟩ \| ∃l ∃p (DEPARTAMENTOS(d, n, l, p) ∧ ¬∃i ∃np ∃fi ∃ff ∃pp PROYECTOS(i, np, d, fi, ff, pp)) } |
El primer resultado es {DIRECCIÓN, VENTAS}; el segundo empareja cada proyecto con la localidad de su departamento (Migración a la nube - Valencia, …); el tercero devuelve (20, VENTAS), (40, CONTABILIDAD) y (70, INVESTIGACIÓN). Fíjate en que la igualdad se expresa repitiendo la misma variable (d en las dos relaciones), que es la idea que usaba QBE.
6.3. Teorema de equivalencia
📘 Concepto: Teorema de Codd: el álgebra relacional, el cálculo relacional de tuplas seguro y el cálculo relacional de dominios seguro tienen la misma potencia expresiva. Cualquier consulta escrita en uno de ellos se puede escribir en los otros dos.
7. Equivalencia con SQL
| Álgebra | SQL (Oracle) | Observaciones |
|---|---|---|
σ[c](R) | SELECT * FROM r WHERE c | La condición sigue la lógica trivaluada |
π[A, B](R) | SELECT DISTINCT a, b FROM r | Sin DISTINCT, SQL conserva duplicados |
ρ[S](R) | FROM r s (alias de tabla), AS para columnas | Oracle no admite AS en los alias de tabla |
R ∪ S | UNION | UNION ALL conserva duplicados |
R − S | MINUS | Estándar: EXCEPT (Oracle lo admite desde 21c) |
R ∩ S | INTERSECT | |
R × S | FROM r CROSS JOIN s o FROM r, s | |
R ⋈[θ] S | FROM r JOIN s ON θ | O condición en WHERE (sintaxis tradicional) |
R ⋈ S | FROM r NATURAL JOIN s | Mismo riesgo con atributos homónimos |
R ⋉[θ] S | WHERE EXISTS (SELECT ... FROM s WHERE θ) o IN | |
R ▷[θ] S | WHERE NOT EXISTS (...) | Cuidado con NOT IN y los nulos |
R ÷ S | Doble NOT EXISTS, o GROUP BY ... HAVING COUNT(...) = (SELECT COUNT(*) ...) | SQL no tiene operador de división |
R ⟕[θ] S, ⟖, ⟗ | LEFT, RIGHT, FULL OUTER JOIN ... ON θ | Oracle también tiene la notación antigua (+) |
γ[G; F](R) | SELECT g, f FROM r GROUP BY g | σ sobre el resultado de γ = HAVING |
T ← expresión | WITH t AS (...) (CTE) o una vista |
💻 Ejemplo: La división del apartado 3.5 en SQL, con doble
NOT EXISTS(“empleados para los que no existe ningún proyecto del departamento 30 en el que no estén asignados”):
SELECT e.emp_id, e.nombre
FROM empleados e
WHERE NOT EXISTS (
SELECT 1
FROM proyectos p
WHERE p.dept_id = 30
AND NOT EXISTS (
SELECT 1
FROM asignaciones a
WHERE a.emp_id = e.emp_id
AND a.proyecto_id = p.proyecto_id
)
);
Y con agrupación (cuenta los proyectos del 30 en los que está cada empleado y compara con el total):
SELECT a.emp_id
FROM asignaciones a
JOIN proyectos p ON p.proyecto_id = a.proyecto_id
WHERE p.dept_id = 30
GROUP BY a.emp_id
HAVING COUNT(*) = (SELECT COUNT(*) FROM proyectos WHERE dept_id = 30);
Las dos devuelven el empleado 120. Ojo: la versión con doble NOT EXISTS devolvería todos los empleados si el departamento no tuviera proyectos (división por conjunto vacío), mientras que la de GROUP BY no devolvería ninguno.
8. Colección de consultas resueltas sobre EMPRESA
Intenta resolver cada consulta antes de desplegar la solución. Los resultados son los que se obtienen con los datos de ../recursos/empresa_datos.sql. Recuerda: el álgebra trabaja con conjuntos, así que el orden de las tuplas del resultado es irrelevante y nunca hay duplicados.
Consulta 1: Empleados con salario superior a 3000 €
Nombre, apellidos y salario.
Ver solución
π[nombre, apellidos, salario](σ[salario > 3000](EMPLEADOS))
| nombre | apellidos | salario |
|---|---|---|
| Ana | García López | 6500 |
| Luis | Martínez Ruiz | 4800 |
| Marta | Sánchez Gil | 5000 |
| Pedro | Gómez Díaz | 4200 |
| Laura | Fernández Moreno | 4300 |
| Javier | López Navarro | 4000 |
| Pablo | Moreno Herrera | 3500 |
7 tuplas. SQL: SELECT nombre, apellidos, salario FROM empleados WHERE salario > 3000; (sin DISTINCT el resultado es el mismo porque la combinación de atributos no se repite).
Consulta 2: Oficios distintos que existen en la empresa
Ver solución
π[oficio](EMPLEADOS)
| oficio |
|---|
| ADMINISTRATIVO |
| ANALISTA |
| BECARIO |
| CONTABLE |
| DIRECTOR |
| DIRECTOR GENERAL |
| DISEÑADOR |
| OPERARIO |
| PROGRAMADOR |
| TÉCNICO SISTEMAS |
| VENDEDOR |
11 tuplas (de 24 empleados). SQL: SELECT DISTINCT oficio FROM empleados;. Sin DISTINCT, SQL devolvería 24 filas.
Consulta 3: Vendedores con comisión superior a 400 €
Identificador, nombre, salario y comisión.
Ver solución
π[emp_id, nombre, salario, comision](σ[oficio = 'VENDEDOR' ∧ comision > 400](EMPLEADOS))
| emp_id | nombre | salario | comision |
|---|---|---|---|
| 110 | Carmen | 2200 | 500 |
| 111 | David | 2100 | 800 |
2 tuplas. SQL: SELECT emp_id, nombre, salario, comision FROM empleados WHERE oficio = 'VENDEDOR' AND comision > 400;
Consulta 4: Productos no descatalogados con precio entre 100 y 300 €
Ver solución
π[producto_id, nombre, precio](σ[precio ≥ 100 ∧ precio ≤ 300 ∧ descatalogado = 'N'](PRODUCTOS))
| producto_id | nombre | precio |
|---|---|---|
| 9 | Monitor 24 FHD | 139.00 |
| 11 | Smartphone Gama Media | 299.00 |
| 15 | Silla Ergonómica | 249.00 |
| 18 | Impresora Láser | 189.00 |
| 20 | Tablet 10 | 279.00 |
5 tuplas. SQL: ... WHERE precio BETWEEN 100 AND 300 AND descatalogado = 'N';
Consulta 5: Empleados de los departamentos situados en Valencia
Nombre y apellidos del empleado, nombre y localidad del departamento.
Ver solución
π[EMPLEADOS.nombre, apellidos, DEPARTAMENTOS.nombre, localidad](
EMPLEADOS ⋈[EMPLEADOS.dept_id = DEPARTAMENTOS.dept_id] σ[localidad = 'Valencia'](DEPARTAMENTOS))
| EMPLEADOS.nombre | apellidos | DEPARTAMENTOS.nombre | localidad |
|---|---|---|---|
| Marta | Sánchez Gil | INFORMÁTICA | Valencia |
| Pablo | Moreno Herrera | INFORMÁTICA | Valencia |
| Raquel | Molina Prieto | INFORMÁTICA | Valencia |
| Iván | Castro Méndez | INFORMÁTICA | Valencia |
| Sara | Ortiz Delgado | INFORMÁTICA | Valencia |
| Hugo | Rubio Marín | INFORMÁTICA | Valencia |
6 tuplas. No se puede usar la combinación natural (EMPLEADOS y DEPARTAMENTOS comparten nombre).
SELECT e.nombre, e.apellidos, d.nombre AS departamento, d.localidad
FROM empleados e
JOIN departamentos d ON e.dept_id = d.dept_id
WHERE d.localidad = 'Valencia';
Consulta 6: Clientes de las provincias de Madrid o Sevilla con el nombre de su comercial
Ver solución
π[CLIENTES.nombre, EMPLEADOS.nombre](
σ[provincia = 'Madrid' ∨ provincia = 'Sevilla'](CLIENTES)
⋈[CLIENTES.comercial_id = EMPLEADOS.emp_id] EMPLEADOS)
| CLIENTES.nombre | EMPLEADOS.nombre |
|---|---|
| María José Herrera | David |
| Academia Nova | David |
| Clínica Dental Sonrisa | Elena |
| Antonio Ruiz Pérez | Elena |
4 tuplas. Los cuatro clientes de esas provincias tienen comercial; si alguno no lo tuviera, la combinación interna lo perdería (haría falta ⟕).
Consulta 7: Identificadores de los empleados que son jefes de alguien
Ver solución
π[jefe_id](σ[jefe_id IS NOT NULL](EMPLEADOS))
Resultado (7 tuplas): 100, 101, 102, 103, 104, 105 y 120.
La selección evita que el nulo de Ana aparezca en el resultado. SQL: SELECT DISTINCT jefe_id FROM empleados WHERE jefe_id IS NOT NULL;
Consulta 8: Empleados que no son jefes de nadie
Ver solución
π[emp_id](EMPLEADOS) − ρ[J(emp_id)](π[jefe_id](σ[jefe_id IS NOT NULL](EMPLEADOS)))
El renombrado hace que el segundo operando tenga el mismo nombre de atributo (en rigor, basta con que sean compatibles). Resultado (17 tuplas): 110, 111, 112, 113, 114, 121, 122, 123, 124, 130, 131, 140, 141, 150, 151, 152 y 160.
SELECT emp_id FROM empleados
MINUS
SELECT jefe_id FROM empleados WHERE jefe_id IS NOT NULL;
⚠️ Importante: Si en SQL lo escribes como
WHERE emp_id NOT IN (SELECT jefe_id FROM empleados)sin filtrar los nulos, el resultado es vacío, porque la subconsulta contiene elNULLde Ana.
Consulta 9: Departamentos sin empleados
Identificador, nombre y localidad.
Ver solución
T1 ← π[dept_id](DEPARTAMENTOS) − π[dept_id](σ[dept_id IS NOT NULL](EMPLEADOS))
RESULTADO ← π[dept_id, nombre, localidad](DEPARTAMENTOS ⋈ T1)
| dept_id | nombre | localidad |
|---|---|---|
| 70 | INVESTIGACIÓN | Bilbao |
1 tupla. La diferencia solo puede hacerse sobre relaciones compatibles, por eso se calcula sobre dept_id y luego se recuperan los demás atributos con una combinación natural (segura: T1 solo tiene dept_id). Equivale a la antisemicombinación DEPARTAMENTOS ▷[DEPARTAMENTOS.dept_id = EMPLEADOS.dept_id] EMPLEADOS.
Consulta 10: Clientes que no han hecho ningún pedido
Ver solución
T1 ← π[cliente_id](CLIENTES) − π[cliente_id](PEDIDOS)
RESULTADO ← π[cliente_id, nombre](CLIENTES ⋈ T1)
| cliente_id | nombre |
|---|---|
| 1013 | Carlos Ibáñez Rey |
| 1014 | Farmacia Central |
2 tuplas.
SELECT c.cliente_id, c.nombre
FROM clientes c
WHERE NOT EXISTS (SELECT 1 FROM pedidos p WHERE p.cliente_id = c.cliente_id);
Consulta 11: Productos que nunca se han vendido
Ver solución
T1 ← π[producto_id](PRODUCTOS) − π[producto_id](LINEAS_PEDIDO)
RESULTADO ← π[producto_id, nombre](PRODUCTOS ⋈ T1)
| producto_id | nombre |
|---|---|
| 14 | Altavoz Inteligente |
1 tupla: el producto descatalogado. Todos los demás aparecen en al menos una línea de pedido.
Consulta 12: Empleados asignados a la vez a los proyectos 1 y 2
Ver solución
T1 ← π[emp_id](σ[proyecto_id = 1](ASIGNACIONES)) ∩ π[emp_id](σ[proyecto_id = 2](ASIGNACIONES))
RESULTADO ← π[emp_id, nombre](EMPLEADOS ⋈ T1)
| emp_id | nombre |
|---|---|
| 120 | Pablo |
1 tupla. SQL: dos SELECT unidos con INTERSECT.
Consulta 13: Empleados asignados al proyecto 1 o al proyecto 2
Ver solución
π[emp_id](σ[proyecto_id = 1](ASIGNACIONES)) ∪ π[emp_id](σ[proyecto_id = 2](ASIGNACIONES))
Resultado (6 tuplas): 120, 121, 122, 123, 124 y 140. Aquí también valdría una sola selección con ∨: π[emp_id](σ[proyecto_id = 1 ∨ proyecto_id = 2](ASIGNACIONES)), porque las dos condiciones se refieren a la misma relación.
Consulta 14: Empleados asignados al proyecto 1 pero no al 2
Ver solución
π[emp_id](σ[proyecto_id = 1](ASIGNACIONES)) − π[emp_id](σ[proyecto_id = 2](ASIGNACIONES))
Resultado (2 tuplas): 121 (Raquel) y 124 (Hugo). Error típico: π[emp_id](σ[proyecto_id = 1 ∧ proyecto_id ≠ 2](ASIGNACIONES)) devuelve 120, 121 y 124, porque la condición se evalúa tupla a tupla y la tupla (120, 1) la cumple.
SELECT emp_id FROM asignaciones WHERE proyecto_id = 1
MINUS
SELECT emp_id FROM asignaciones WHERE proyecto_id = 2;
Consulta 15: Nombre de cada empleado del departamento 30 y el nombre de su jefe
Ver solución
π[E.nombre, J.nombre](σ[E.dept_id = 30](ρ[E](EMPLEADOS)) ⋈[E.jefe_id = J.emp_id] ρ[J](EMPLEADOS))
| E.nombre | J.nombre |
|---|---|
| Marta | Ana |
| Pablo | Marta |
| Raquel | Pablo |
| Iván | Pablo |
| Sara | Pablo |
| Hugo | Marta |
6 tuplas. El renombrado crea dos “copias” de EMPLEADOS con papeles distintos (empleado y jefe).
SELECT e.nombre AS empleado, j.nombre AS jefe
FROM empleados e
JOIN empleados j ON e.jefe_id = j.emp_id
WHERE e.dept_id = 30;
Consulta 16: Empleados que ganan más que su jefe
Ver solución
π[E.emp_id, E.nombre](ρ[E](EMPLEADOS) ⋈[E.jefe_id = J.emp_id ∧ E.salario > J.salario] ρ[J](EMPLEADOS))
Resultado: vacío (0 tuplas). En EMPRESA todos los jefes ganan más que sus subordinados. Un resultado vacío es una respuesta perfectamente válida: no indica que la consulta esté mal.
Consulta 17: Empleados cuyo jefe trabaja en un departamento distinto
Ver solución
π[E.emp_id, E.nombre, E.dept_id, J.nombre, J.dept_id](
ρ[E](EMPLEADOS) ⋈[E.jefe_id = J.emp_id ∧ E.dept_id ≠ J.dept_id] ρ[J](EMPLEADOS))
| E.emp_id | E.nombre | E.dept_id | J.nombre | J.dept_id |
|---|---|---|---|---|
| 101 | Luis | 20 | Ana | 10 |
| 102 | Marta | 30 | Ana | 10 |
| 103 | Pedro | 40 | Ana | 10 |
| 104 | Laura | 50 | Ana | 10 |
| 105 | Javier | 60 | Ana | 10 |
5 tuplas. Tomás (160) tiene jefe (Marta, del 30) pero su dept_id es nulo: NULL ≠ 30 es desconocido y la tupla se descarta. Si quisieras incluirlo, la condición sería E.dept_id ≠ J.dept_id ∨ E.dept_id IS NULL.
Consulta 18: Empleados que trabajan en algún proyecto de un departamento distinto al suyo
Ver solución
T1 ← EMPLEADOS ⋈ ASIGNACIONES
RESULTADO ← π[emp_id, T1.nombre, T1.proyecto_id](
T1 ⋈[T1.proyecto_id = PROYECTOS.proyecto_id ∧ T1.dept_id ≠ PROYECTOS.dept_id] PROYECTOS)
| emp_id | nombre | proyecto_id |
|---|---|---|
| 110 | Carmen | 3 |
| 140 | Nuria | 2 |
2 tuplas: Carmen (VENTAS) en la campaña de MARKETING, y Nuria (MARKETING) en la tienda online de INFORMÁTICA. La primera combinación natural es segura (EMPLEADOS y ASIGNACIONES solo comparten emp_id), pero la segunda no podría ser natural: T1 y PROYECTOS comparten proyecto_id, nombre y dept_id, y la natural exigiría justo lo contrario de lo que se pide (mismo departamento).
Consulta 19: Empleados que ganan más que Pablo Moreno (120)
Ver solución
π[EMPLEADOS.nombre, EMPLEADOS.salario](
EMPLEADOS ⋈[EMPLEADOS.salario > P.salario] σ[emp_id = 120](ρ[P](EMPLEADOS)))
| nombre | salario |
|---|---|
| Ana | 6500 |
| Luis | 4800 |
| Marta | 5000 |
| Pedro | 4200 |
| Laura | 4300 |
| Javier | 4000 |
6 tuplas. En SQL se resolvería con una subconsulta escalar: WHERE salario > (SELECT salario FROM empleados WHERE emp_id = 120).
Consulta 20: Empleado o empleados con el salario máximo, sin usar agregados
Ver solución
Idea: el salario máximo es el de los empleados que no ganan menos que ningún otro. Primero se calculan los que ganan menos que alguien y luego se restan.
T1 ← π[E1.emp_id](ρ[E1](EMPLEADOS) ⋈[E1.salario < E2.salario] ρ[E2](EMPLEADOS))
RESULTADO ← π[emp_id, nombre, salario](EMPLEADOS ⋈ (π[emp_id](EMPLEADOS) − T1))
| emp_id | nombre | salario |
|---|---|---|
| 100 | Ana | 6500 |
1 tupla. T1 contiene a los 23 empleados que ganan menos que alguien. Es un patrón clásico de examen; con el álgebra extendida sería γ[; MAX(salario)](EMPLEADOS) y una combinación.
Consulta 21: Departamentos que tienen algún proyecto
Ver solución
DEPARTAMENTOS ⋉[DEPARTAMENTOS.dept_id = PROYECTOS.dept_id] PROYECTOS
| dept_id | nombre | localidad | presupuesto |
|---|---|---|---|
| 10 | DIRECCIÓN | Madrid | 500000 |
| 30 | INFORMÁTICA | Valencia | 420000 |
| 50 | MARKETING | Barcelona | 200000 |
| 60 | LOGÍSTICA | Zaragoza | 180000 |
4 tuplas.
SELECT d.*
FROM departamentos d
WHERE EXISTS (SELECT 1 FROM proyectos p WHERE p.dept_id = d.dept_id);
Consulta 22: Empleados no asignados a ningún proyecto
Ver solución
π[emp_id, nombre, oficio](EMPLEADOS ▷[EMPLEADOS.emp_id = ASIGNACIONES.emp_id] ASIGNACIONES)
| emp_id | nombre | oficio |
|---|---|---|
| 100 | Ana | DIRECTOR GENERAL |
| 101 | Luis | DIRECTOR |
| 102 | Marta | DIRECTOR |
| 103 | Pedro | DIRECTOR |
| 111 | David | VENDEDOR |
| 112 | Elena | VENDEDOR |
| 113 | Sergio | VENDEDOR |
| 114 | Lucía | ADMINISTRATIVO |
| 130 | Isabel | CONTABLE |
| 131 | Jorge | ADMINISTRATIVO |
| 151 | Manuel | OPERARIO |
| 160 | Tomás | BECARIO |
12 tuplas (24 empleados − 12 con asignaciones). SQL: WHERE NOT EXISTS (SELECT 1 FROM asignaciones a WHERE a.emp_id = e.emp_id).
Consulta 23: Pedidos de clientes de la ciudad de Madrid gestionados por empleados del departamento 20
Número de pedido, nombre del cliente y nombre del empleado.
Ver solución
C ← σ[ciudad = 'Madrid'](CLIENTES)
E ← σ[dept_id = 20](EMPLEADOS)
RESULTADO ← π[pedido_id, C.nombre, E.nombre](
(PEDIDOS ⋈[PEDIDOS.cliente_id = C.cliente_id] C) ⋈[PEDIDOS.empleado_id = E.emp_id] E)
| pedido_id | C.nombre | E.nombre |
|---|---|---|
| 5003 | María José Herrera | David |
| 5004 | Academia Nova | David |
| 5012 | Academia Nova | David |
| 5015 | María José Herrera | David |
4 tuplas. Las selecciones se aplican antes de combinar (heurística de optimización).
Consulta 24: Productos de las subcategorías de “Periféricos”
Nombre del producto y nombre de su categoría.
Ver solución
PADRE ← π[categoria_id](σ[nombre = 'Periféricos'](CATEGORIAS)) {4}
HIJAS ← π[C.categoria_id, C.nombre](ρ[C](CATEGORIAS) ⋈[C.categoria_padre_id = PADRE.categoria_id] PADRE)
RESULTADO ← π[PRODUCTOS.nombre, HIJAS.nombre](PRODUCTOS ⋈[PRODUCTOS.categoria_id = HIJAS.categoria_id] HIJAS)
| PRODUCTOS.nombre | HIJAS.nombre |
|---|---|
| Ratón inalámbrico | Ratones y teclados |
| Teclado mecánico | Ratones y teclados |
| Pack teclado y ratón | Ratones y teclados |
| Monitor 24 FHD | Monitores |
| Monitor 27 4K | Monitores |
5 tuplas. Los productos asignados directamente a “Periféricos” (Webcam HD e Impresora Láser) no aparecen: no están en una subcategoría. El álgebra clásica no puede recorrer una jerarquía de profundidad arbitraria (necesitaría recursividad); SQL lo hace con CONNECT BY o con CTE recursivas (módulos 32 y 33).
Consulta 25: Empleados que trabajan en todos los proyectos del departamento 30
Ver solución
R ← π[emp_id, proyecto_id](ASIGNACIONES)
S ← π[proyecto_id](σ[dept_id = 30](PROYECTOS))
RESULTADO ← π[emp_id, nombre](EMPLEADOS ⋈ (R ÷ S))
| emp_id | nombre |
|---|---|
| 120 | Pablo |
1 tupla. El desarrollo paso a paso está en el apartado 3.5 y las dos versiones SQL en la sección 7.
Consulta 26: Clientes que han comprado todos los productos de la categoría “Monitores”
Ver solución
R ← π[cliente_id, producto_id](PEDIDOS ⋈ LINEAS_PEDIDO)
S ← π[producto_id](PRODUCTOS ⋈ π[categoria_id](σ[nombre = 'Monitores'](CATEGORIAS)))
RESULTADO ← π[cliente_id, nombre](CLIENTES ⋈ (R ÷ S))
S = {9, 10}. Las tuplas de R con productos de S son: (1001, 9), (1001, 10), (1002, 9), (1002, 10), (1004, 10), (1007, 9), (1010, 9) y (1012, 10). Solo dos clientes tienen ambos:
| cliente_id | nombre |
|---|---|
| 1001 | Talleres Martín S.L. |
| 1002 | Construcciones Levante S.A. |
2 tuplas. PEDIDOS ⋈ LINEAS_PEDIDO es segura (solo comparten pedido_id), igual que PRODUCTOS ⋈ π[categoria_id](...); en cambio PRODUCTOS ⋈ CATEGORIAS sin proyectar compararía también nombre. Observa que el 1001 compró el monitor 9 en el pedido 5001 y el 10 en el 5021: la división no exige que sea en el mismo pedido.
Consulta 27: Número de empleados y salario medio por departamento, solo de los departamentos con más de 3 empleados
Ver solución
T1 ← γ[dept_id; COUNT(*) → num_emp, AVG(salario) → media](σ[dept_id IS NOT NULL](EMPLEADOS))
RESULTADO ← σ[num_emp > 3](T1)
| dept_id | num_emp | media |
|---|---|---|
| 20 | 6 | 2500 |
| 30 | 6 | 3133,33 |
| 60 | 4 | 2237,5 |
3 tuplas. La selección después de γ corresponde a HAVING:
SELECT dept_id, COUNT(*) AS num_emp, ROUND(AVG(salario), 2) AS media
FROM empleados
WHERE dept_id IS NOT NULL
GROUP BY dept_id
HAVING COUNT(*) > 3;
Consulta 28: Horas totales y número de empleados de cada proyecto, incluidos los proyectos sin asignaciones
Ver solución
T1 ← PROYECTOS ⟕[PROYECTOS.proyecto_id = ASIGNACIONES.proyecto_id] ASIGNACIONES
RESULTADO ← γ[PROYECTOS.proyecto_id, nombre; COUNT(emp_id) → num_emp, SUM(horas) → total_horas](T1)
| proyecto_id | nombre | num_emp | total_horas |
|---|---|---|---|
| 1 | Migración a la nube | 3 | 1430 |
| 2 | Nueva tienda online | 4 | 1350 |
| 3 | Campaña Navidad 2025 | 4 | 480 |
| 4 | Optimización de almacén | 3 | 300 |
| 5 | Plan estratégico 2027 | 0 | NULL |
5 tuplas. Con una combinación interna, el proyecto 5 desaparecería. COUNT(emp_id) cuenta valores no nulos (0 para el proyecto 5), mientras que COUNT(*) contaría la tupla rellenada con nulos y daría 1: un error muy frecuente.
Consulta 29: Importe de cada pedido del cliente 1001
El importe de una línea es cantidad × precio_unitario × (1 − descuento / 100).
Ver solución
T1 ← π[pedido_id](σ[cliente_id = 1001](PEDIDOS))
T2 ← π[pedido_id, cantidad * precio_unitario * (1 − descuento / 100) → importe_linea](LINEAS_PEDIDO ⋈ T1)
RESULTADO ← γ[pedido_id; SUM(importe_linea) → importe](T2)
| pedido_id | importe |
|---|---|
| 5001 | 1802.90 |
| 5006 | 439.20 |
| 5021 | 2462.05 |
3 tuplas. Por ejemplo, 5021 = 1 × 1899 × 0,95 + 2 × 329 = 1804,05 + 658 = 2462,05.
⚠️ Importante: Cuidado con la proyección T2: si dos líneas del mismo pedido tuvieran el mismo importe, la proyección (que elimina duplicados) dejaría solo una y la suma saldría mal. Lo correcto es conservar
num_lineaen T2:π[pedido_id, num_linea, ... → importe_linea]. Es un buen ejemplo de por qué SQL trabaja con multiconjuntos.
9. Ejercicios
Ejercicio 1: Grado y cardinalidad
Sin calcular las tuplas, indica el grado y la cardinalidad de: a) DEPARTAMENTOS × PROYECTOS b) σ[dept_id = 30](EMPLEADOS) c) π[dept_id](PROYECTOS) d) EMPLEADOS ⟗[EMPLEADOS.dept_id = DEPARTAMENTOS.dept_id] DEPARTAMENTOS e) π[emp_id](ASIGNACIONES) f) EMPLEADOS ⋈ DEPARTAMENTOS
Ver solución
| Expresión | Grado | Cardinalidad |
|---|---|---|
| a) | 4 + 6 = 10 | 7 × 5 = 35 |
| b) | 11 | 6 |
| c) | 1 | 4 (30, 50, 60 y 10; el 30 se repite) |
| d) | 11 + 4 = 15 | 25 |
| e) | 1 | 12 |
| f) | 11 + 4 − 2 = 13 | 0 (comparan dept_id y nombre) |
Ejercicio 2: Operadores derivados con primitivos
Escribe usando solo σ, π, ∪, −, × y ρ: a) R ∩ S b) R ⋈[R.a = S.b] S c) R ⋉[R.a = S.b] S, con R(a, c) y S(b, d) d) R ÷ S, con R(x, y) y S(y)
Ver solución
a) R ∩ S = R − (R − S)
b) R ⋈[R.a = S.b] S = σ[R.a = S.b](R × S)
c) R ⋉[R.a = S.b] S = π[a, c](σ[R.a = S.b](R × S))
d) R ÷ S = π[x](R) − π[x]((π[x](R) × S) − R)
En d), la diferencia interna es válida porque π[x](R) × S tiene los atributos (x, y), los mismos que R.
Ejercicio 3: División a mano
Universidad. Dadas MATRICULAS(alumno, asignatura) = {(Ana, BD), (Ana, PROG), (Ana, SIS), (Luis, BD), (Luis, SIS), (Eva, PROG), (Eva, BD)} y OBLIGATORIAS(asignatura) = {(BD), (SIS)}, calcula paso a paso MATRICULAS ÷ OBLIGATORIAS.
Ver solución
T1 ← π[alumno](MATRICULAS) = {Ana, Luis, Eva}
T2 ← T1 × OBLIGATORIAS = {(Ana,BD), (Ana,SIS), (Luis,BD), (Luis,SIS), (Eva,BD), (Eva,SIS)}
T3 ← T2 − MATRICULAS = {(Eva,SIS)}
T4 ← π[alumno](T3) = {Eva}
RESULTADO ← T1 − T4 = {Ana, Luis}
Ana y Luis están matriculados en todas las obligatorias. Que Ana tenga además PROG no importa: la división pide “al menos todas las del divisor”.
Ejercicio 4: Optimizar una expresión
Dibuja (o escribe) la versión optimizada de esta expresión, que obtiene el nombre de los clientes de Valencia (ciudad) que han comprado algún monitor (categoría 6):
π[CLIENTES.nombre](σ[ciudad = 'Valencia' ∧ categoria_id = 6 ∧ CLIENTES.cliente_id = PEDIDOS.cliente_id
∧ PEDIDOS.pedido_id = LINEAS_PEDIDO.pedido_id ∧ LINEAS_PEDIDO.producto_id = PRODUCTOS.producto_id](
CLIENTES × PEDIDOS × LINEAS_PEDIDO × PRODUCTOS))
¿Cuántas tuplas tiene el producto cartesiano? ¿Cuál es el resultado?
Ver solución
El producto tiene 14 × 24 × 51 × 20 = 342 720 tuplas. Versión optimizada (selecciones abajo, productos convertidos en combinaciones, proyecciones tempranas):
C ← π[cliente_id, nombre](σ[ciudad = 'Valencia'](CLIENTES)) (2 tuplas: 1001, 1013)
M ← π[producto_id](σ[categoria_id = 6](PRODUCTOS)) (2 tuplas: 9, 10)
L ← π[pedido_id](LINEAS_PEDIDO ⋈ M) (pedidos con monitores)
RESULTADO ← π[nombre](C ⋈ π[cliente_id, pedido_id](PEDIDOS) ⋈ L)
flowchart TB
r1["π[nombre]"] --> r2["⋈ cliente_id"]
r2 --> r3["π[cliente_id, nombre]"]
r3 --> r4["σ[ciudad = 'Valencia']"]
r4 --> r5["CLIENTES"]
r2 --> r6["⋈ pedido_id"]
r6 --> r7["π[cliente_id, pedido_id]"]
r7 --> r8["PEDIDOS"]
r6 --> r9["π[pedido_id]"]
r9 --> r10["⋈ producto_id"]
r10 --> r11["LINEAS_PEDIDO"]
r10 --> r12["π[producto_id]"]
r12 --> r13["σ[categoria_id = 6]"]
r13 --> r14["PRODUCTOS"]
Resultado: Talleres Martín S.L. (1 tupla), que compró monitores en los pedidos 5001 y 5021. El otro cliente de Valencia (1013) no tiene pedidos. Las combinaciones naturales son seguras porque antes se ha proyectado sobre los atributos de combinación.
Ejercicio 5: Traducir entre lenguajes
Expresa en cálculo de tuplas y en SQL: π[nombre](CLIENTES ▷[CLIENTES.cliente_id = PEDIDOS.cliente_id] PEDIDOS). ¿Qué devuelve?
Ver solución
Cálculo de tuplas:
{ c.nombre | CLIENTES(c) ∧ ¬∃p (PEDIDOS(p) ∧ p.cliente_id = c.cliente_id) }
SQL:
SELECT c.nombre
FROM clientes c
WHERE NOT EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = c.cliente_id
);
Devuelve los clientes sin pedidos: Carlos Ibáñez Rey y Farmacia Central (2 tuplas).
Ejercicio 6: Encuentra el error
Cada expresión pretende resolver la consulta indicada pero es incorrecta. Explica por qué y corrígela.
a) “Nombre de cada proyecto y nombre de su departamento”: π[nombre](PROYECTOS ⋈ DEPARTAMENTOS). b) “Empleados sin comisión”: π[emp_id](EMPLEADOS) − π[emp_id](σ[comision ≥ 0](EMPLEADOS)). ¿Es incorrecta? c) “Identificadores de empleados y de clientes”: π[emp_id, nombre](EMPLEADOS) ∪ π[cliente_id](CLIENTES). d) “Empleados que trabajan en el proyecto 1 y en el 3”: π[emp_id](σ[proyecto_id = 1 ∧ proyecto_id = 3](ASIGNACIONES)).
Ver solución
a) La combinación natural compara dept_id, nombre y presupuesto (atributos homónimos) y el resultado es vacío; además π[nombre] es ambiguo. Corrección: π[PROYECTOS.nombre, DEPARTAMENTOS.nombre](PROYECTOS ⋈[PROYECTOS.dept_id = DEPARTAMENTOS.dept_id] DEPARTAMENTOS).
b) Es correcta, aunque rebuscada: el sustraendo contiene los 4 empleados con comisión no nula (incluido Sergio, con 0), y el resultado son los 20 con comisión nula. Más directa: π[emp_id](σ[comision IS NULL](EMPLEADOS)). Ojo: si “sin comisión” incluyera también a los que tienen 0, serían 21.
c) No son compatibles: grado 2 frente a grado 1. Corrección: π[emp_id](EMPLEADOS) ∪ π[cliente_id](CLIENTES) (grado 1, ambos numéricos). Aun así, mezclar identificadores de dominios distintos es semánticamente dudoso.
d) Ninguna tupla tiene a la vez proyecto_id = 1 y proyecto_id = 3: el resultado siempre es vacío. Corrección: intersección π[emp_id](σ[proyecto_id = 1](ASIGNACIONES)) ∩ π[emp_id](σ[proyecto_id = 3](ASIGNACIONES)), que en EMPRESA es vacía (ningún empleado está en ambos), pero por una razón legítima.
Ejercicios propuestos
Resuelve en álgebra relacional (y, si quieres, en SQL). Entre paréntesis tienes el número de tuplas que debe devolver tu solución con los datos del curso, para que puedas comprobarla.
✏️ Ejercicio: 1. Nombre y precio de los productos sin stock. (2 tuplas)
✏️ Ejercicio: 2. Clientes de la provincia de Valencia. (2 tuplas)
✏️ Ejercicio: 3. Empleados dados de alta a partir del 1 de enero de 2020 con salario inferior a 2000 €. (5 tuplas)
✏️ Ejercicio: 4. Nombre de los departamentos que tienen algún proyecto sin fecha de fin. (3 tuplas)
✏️ Ejercicio: 5. Nombre de los clientes que tienen algún pedido gestionado por Carmen Ruiz (110). (3 tuplas)
✏️ Ejercicio: 6. Productos que nunca se han vendido a clientes cuya ciudad es Madrid. (13 tuplas)
✏️ Ejercicio: 7. Empleados que no han gestionado ningún pedido. (20 tuplas)
✏️ Ejercicio: 8. Parejas de empleados distintos que trabajan en el mismo departamento y tienen el mismo oficio, sin repetir cada pareja en los dos órdenes. (10 tuplas)
✏️ Ejercicio: 9. Clientes que han comprado algún portátil (categoría 2) y también algún monitor (categoría 6). (4 tuplas)
✏️ Ejercicio: 10. Clientes que han comprado todos los productos de la categoría “Telefonía”. (1 tupla)
✏️ Ejercicio: 11. Número de pedidos de cada estado. (4 tuplas)
✏️ Ejercicio: 12. Proyectos con más de 1000 horas asignadas en total. (2 tuplas)
✏️ Ejercicio: 13. Empleados que ganan más que todos los empleados del departamento 20, sin usar agregados. (2 tuplas)
✏️ Ejercicio: 14. Todos los clientes con sus pedidos, incluidos los clientes que no tienen ninguno. (26 tuplas)
✏️ Ejercicio: 15. Categorías que no tienen ningún producto asignado directamente. (1 tupla)
Resumen
| Operador | Notación | Resultado | SQL |
|---|---|---|---|
| Selección | σ[c](R) | Tuplas de R que cumplen c (V) | WHERE |
| Proyección | π[L](R) | Atributos L de R, sin duplicados | SELECT DISTINCT |
| Renombrado | ρ[S](R) | R con otro nombre | Alias |
| Unión | R ∪ S | Tuplas de R o de S | UNION |
| Diferencia | R − S | Tuplas de R que no están en S | MINUS / EXCEPT |
| Producto | R × S | Todas las combinaciones | CROSS JOIN |
| Intersección | R ∩ S = R − (R − S) | Tuplas comunes | INTERSECT |
| Combinación theta | R ⋈[θ] S = σ[θ](R × S) | Parejas que cumplen θ | JOIN ... ON |
| Combinación natural | R ⋈ S | Igualdad en todos los atributos homónimos | NATURAL JOIN |
| Semicombinación | R ⋉[θ] S | Tuplas de R con pareja en S | EXISTS / IN |
| Antisemicombinación | R ▷[θ] S | Tuplas de R sin pareja en S | NOT EXISTS |
| División | R ÷ S | Valores relacionados con todos los de S | Doble NOT EXISTS |
| Externas | ⟕ ⟖ ⟗ | Combinación que conserva las tuplas sin pareja | LEFT/RIGHT/FULL OUTER JOIN |
| Agrupación | γ[G; F](R) | Una tupla por grupo con agregados | GROUP BY (+ HAVING) |
| Asignación | T ← e | Nombre para un resultado intermedio | WITH |
| Compatibilidad de unión | — | Mismo grado y dominios compatibles | — |
| Optimización | — | Empujar σ y π hacia las hojas; σ + × → ⋈ | Optimizador del SGBD |
| Cálculo de tuplas | { t \| P(t) } | Declarativo; variables de tupla | Base de SELECT |
| Cálculo de dominios | { ⟨x⟩ \| P(x) } | Declarativo; variables de dominio | Base de QBE |