Normalización de Bases de Datos: 1NF, 2NF, 3NF, BCNF y Anomalías
La normalización de bases de datos es un proceso sistemático para organizar datos en bases de datos relacionales. El objetivo es reducir la redundancia y evitar anomalías durante las manipulaciones de datos.
¿Qué es la normalización?
La normalización descompone estructuras de datos complejas en tablas más pequeñas y lógicamente relacionadas. Al cumplir con las formas normales se garantiza la integridad y consistencia de los datos.
Objetivos de la normalización
- Evitar redundancia: almacenar datos una sola vez
- Eliminar anomalías: evitar anomalías de actualización, inserción y eliminación
- Integridad de datos: garantizar datos consistentes
- Mantenibilidad: realizar cambios y extensiones de forma sencilla
Dependencias funcionales
Conceptos fundamentales
Una dependencia funcional describe la relación entre atributos en una relación.
-- Ejemplo: relación de estudiantes
CREATE TABLE Studenten (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT,
Fachbereich VARCHAR(50)
);
-- Dependencias funcionales:
-- MatrikelNr → Name (cada matrícula tiene exactamente un nombre)
-- MatrikelNr → Semester (cada matrícula tiene exactamente un semestre)
-- MatrikelNr → Fachbereich (cada matrícula tiene exactamente un departamento)
Tipos de dependencias funcionales
-- Dependencia funcional completa
CREATE TABLE Noten (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr)
);
-- (MatrikelNr, VorlesungNr) → Note (completamente dependiente)
-- MatrikelNr ↛ Note (no dependiente)
-- VorlesungNr ↛ Note (no dependiente)
-- Dependencia funcional transitiva
CREATE TABLE Dozenten (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50),
FachbereichLeiter VARCHAR(50)
);
-- DozentenNr → Fachbereich
-- Fachbereich → FachbereichLeiter
-- DozentenNr → FachbereichLeiter (transitiva)
Anomalías en datos no normalizados
Ejemplo de anomalías
-- Tabla no normalizada con problemas
CREATE TABLE Probleme_Tabelle (
MatrikelNr INT,
StudentName VARCHAR(50),
VorlesungNr INT,
VorlesungName VARCHAR(50),
DozentNr INT,
DozentName VARCHAR(50),
Note DECIMAL(3,1),
Semester INT
);
-- Datos:
-- 101, 'Max Mustermann', 301, 'Datenbanken', 501, 'Prof. Schmidt', 1.3, 5
-- 101, 'Max Mustermann', 302, 'Algorithmen', 502, 'Prof. Mueller', 2.0, 5
-- 102, 'Erika Mustermann', 301, 'Datenbanken', 501, 'Prof. Schmidt', 1.7, 3
Tipos de anomalías
1. Anomalía de actualización
-- Problema: cambiar el nombre del docente
UPDATE Probleme_Tabelle
SET DozentName = 'Prof. Dr. Schmidt'
WHERE DozentNr = 501;
-- ¡Debe hacerse para todas las ocurrencias!
2. Anomalía de inserción
-- Problema: insertar nueva clase sin estudiantes
INSERT INTO Probleme_Tabelle (MatrikelNr, VorlesungNr, VorlesungName, DozentNr, DozentName)
VALUES (NULL, 303, 'Software Engineering', 503, 'Prof. Weber');
-- Problema: MatrikelNr no puede ser NULL (clave primaria)
3. Anomalía de eliminación
-- Problema: eliminar el último estudiante de una clase
DELETE FROM Probleme_Tabelle
WHERE MatrikelNr = 101 AND VorlesungNr = 302;
-- Se pierde información sobre la clase 302 y el docente 502
Primera forma normal (1NF)
Definición y reglas
Una relación está en 1NF si:
- Todos los atributos son atómicos (sin grupos repetidos)
- Cada fila es identificable de forma única (clave primaria)
- Todos los valores de atributos provienen del mismo dominio
Implementación en 1NF
-- Antes: no está en 1NF (grupos repetidos)
CREATE TABLE Studenten_Vorher (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Vorlesungen VARCHAR(200), -- "301,302,303"
Noten VARCHAR(100) -- "1.3,2.0,1.7"
);
-- Después: en 1NF (atributos atómicos)
CREATE TABLE Studenten_1NF (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT
);
CREATE TABLE Belegungen_1NF (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr),
FOREIGN KEY (MatrikelNr) REFERENCES Studenten_1NF(MatrikelNr)
);
-- Ejemplo de valores atómicos
INSERT INTO Studenten_1NF VALUES (101, 'Max Mustermann', 5);
INSERT INTO Belegungen_1NF VALUES (101, 301, 1.3);
INSERT INTO Belegungen_1NF VALUES (101, 302, 2.0);
1NF con JSON/XML (enfoques modernos)
-- Ejemplo de PostgreSQL con JSON
CREATE TABLE Studenten_Modern (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Belegungen JSONB
);
INSERT INTO Studenten_Modern VALUES (
101,
'Max Mustermann',
'[
{"vorlesungNr": 301, "note": 1.3},
{"vorlesungNr": 302, "note": 2.0}
]'
);
-- Consulta con funciones JSON
SELECT
MatrikelNr,
Name,
vorlesung->>'vorlesungNr' AS Vorlesung,
vorlesung->>'note' AS Note
FROM Studenten_Modern,
jsonb_array_elements(Belegungen) AS vorlesung
WHERE MatrikelNr = 101;
Segunda forma normal (2NF)
Definición y reglas
Una relación está en 2NF si:
- Está en 1NF
- Todos los atributos que no son clave dependen completamente de la clave primaria
Problema: dependencias parciales
-- Problema: no está en 2NF (dependencias parciales)
CREATE TABLE Belegungen_Problem (
MatrikelNr INT,
VorlesungNr INT,
StudentName VARCHAR(50), -- Depende solo de MatrikelNr
VorlesungName VARCHAR(50), -- Depende solo de VorlesungNr
DozentNr INT, -- Depende solo de VorlesungNr
Note DECIMAL(3,1), -- Depende de (MatrikelNr, VorlesungNr)
PRIMARY KEY (MatrikelNr, VorlesungNr)
);
-- Dependencias parciales:
-- MatrikelNr → StudentName (parcial)
-- VorlesungNr → VorlesungName, DozentNr (parcial)
-- (MatrikelNr, VorlesungNr) → Note (completa)
Implementación en 2NF
-- Descomposición en 2NF
CREATE TABLE Studenten_2NF (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT
);
CREATE TABLE Vorlesungen_2NF (
VorlesungNr INT PRIMARY KEY,
Name VARCHAR(50),
DozentNr INT
);
CREATE TABLE Belegungen_2NF (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr),
FOREIGN KEY (MatrikelNr) REFERENCES Studenten_2NF(MatrikelNr),
FOREIGN KEY (VorlesungNr) REFERENCES Vorlesungen_2NF(VorlesungNr)
);
-- Datos de ejemplo
INSERT INTO Studenten_2NF VALUES (101, 'Max Mustermann', 5);
INSERT INTO Vorlesungen_2NF VALUES (301, 'Datenbanken', 501);
INSERT INTO Belegungen_2NF VALUES (101, 301, 1.3);
Verificación de 2NF
-- Prueba de dependencias parciales
-- Para cada atributo que no es clave, verificar:
-- ¿Depende de la clave completa?
-- Ejemplo: StudentName
-- ¿Depende de MatrikelNr? Sí
-- ¿Depende de VorlesungNr? No
-- → Dependencia parcial → No está en 2NF
-- Ejemplo: Note
-- ¿Depende de MatrikelNr? No
-- ¿Depende de VorlesungNr? No
-- ¿Depende de (MatrikelNr, VorlesungNr)? Sí
-- → Dependencia completa → Está en 2NF
Tercera forma normal (3NF)
Definición y reglas
Una relación está en 3NF si:
- Está en 2NF
- No existen dependencias transitivas de atributos que no son clave
Problema: Dependencias transitivas
-- Problema: No está en 3NF (dependencias transitivas)
CREATE TABLE Dozenten_Problem (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50),
FachbereichLeiter VARCHAR(50)
);
-- Dependencias transitivas:
-- DozentenNr → Fachbereich
-- Fachbereich → FachbereichLeiter
-- DozentenNr → FachbereichLeiter (transitiva)
Implementación en 3NF
-- División en 3NF
CREATE TABLE Dozenten_3NF (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50)
);
CREATE TABLE Fachbereiche_3NF (
Fachbereich VARCHAR(50) PRIMARY KEY,
Leiter VARCHAR(50)
);
-- Datos de ejemplo
INSERT INTO Dozenten_3NF VALUES (501, 'Prof. Schmidt', 'Informatik');
INSERT INTO Fachbereiche_3NF VALUES ('Informatik', 'Prof. Dr. Meier');
Ejemplo complejo de 3NF
-- Esquema completo en 3NF para universidad
CREATE TABLE Studenten (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50) NOT NULL,
Geburtsdatum DATE,
Fachbereich VARCHAR(50)
);
CREATE TABLE Fachbereiche (
Fachbereich VARCHAR(50) PRIMARY KEY,
Dekan VARCHAR(50),
Gebaeude VARCHAR(20)
);
CREATE TABLE Dozenten (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50) NOT NULL,
Fachbereich VARCHAR(50),
FOREIGN KEY (Fachbereich) REFERENCES Fachbereiche(Fachbereich)
);
CREATE TABLE Vorlesungen (
VorlesungNr INT PRIMARY KEY,
Titel VARCHAR(100) NOT NULL,
DozentenNr INT,
Fachbereich VARCHAR(50),
FOREIGN KEY (DozentenNr) REFERENCES Dozenten(DozentenNr),
FOREIGN KEY (Fachbereich) REFERENCES Fachbereiche(Fachbereich)
);
CREATE TABLE Belegungen (
MatrikelNr INT,
VorlesungNr INT,
Semester INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr, Semester),
FOREIGN KEY (MatrikelNr) REFERENCES Studenten(MatrikelNr),
FOREIGN KEY (VorlesungNr) REFERENCES Vorlesungen(VorlesungNr)
);
Forma Normal de Boyce-Codd (BCNF)
Definición y reglas
Una relación está en BCNF si:
- Está en 3NF
- Para cada dependencia funcional X → Y se cumple que X es una superclave
Problema: Violación de BCNF
-- Problema: No está en BCNF
CREATE TABLE Lehrveranstaltungen (
DozentenNr INT,
Fachbereich VARCHAR(50),
VorlesungNr INT,
PRIMARY KEY (DozentenNr, Fachbereich)
);
-- Datos:
-- 501, 'Informatik', 301
-- 501, 'Mathematik', 401
-- 502, 'Informatik', 302
-- Dependencias funcionales:
-- (DozentenNr, Fachbereich) → VorlesungNr (clave primaria)
-- DozentenNr → Fachbereich (cada docente pertenece a exactamente un departamento)
-- Problema: DozentenNr → Fachbereich
-- DozentenNr no es una superclave → Violación de BCNF
Implementación en BCNF
-- División en BCNF
CREATE TABLE Dozenten_Fachbereich (
DozentenNr INT PRIMARY KEY,
Fachbereich VARCHAR(50)
);
CREATE TABLE Fachbereich_Vorlesungen (
Fachbereich VARCHAR(50),
DozentenNr INT,
VorlesungNr INT,
PRIMARY KEY (Fachbereich, DozentenNr)
);
-- Solución alternativa en BCNF
CREATE TABLE Dozenten (
DozentenNr INT PRIMARY KEY,
Fachbereich VARCHAR(50)
);
CREATE TABLE Vorlesungen_Dozenten (
VorlesungNr INT,
DozentenNr INT,
PRIMARY KEY (VorlesungNr, DozentenNr),
FOREIGN KEY (DozentenNr) REFERENCES Dozenten(DozentenNr)
);
BCNF vs 3NF
-- Ejemplo donde 3NF ≠ BCNF
CREATE TABLE Projektmitarbeiter (
ProjektNr INT,
MitarbeiterNr INT,
Rolle VARCHAR(50),
PRIMARY KEY (ProjektNr, MitarbeiterNr)
);
-- Supuesto: Cada empleado tiene exactamente un rol en cada proyecto
-- Pero: Un empleado puede tener el mismo rol en diferentes proyectos
-- Dependencias funcionales:
-- (ProjektNr, MitarbeiterNr) → Rolle (clave primaria)
-- MitarbeiterNr → Rolle (cada empleado tiene un rol fijo)
-- Violación de BCNF: MitarbeiterNr → Rolle, pero MitarbeiterNr no es una superclave
-- Solución BCNF:
CREATE TABLE Mitarbeiter_Rolle (
MitarbeiterNr INT PRIMARY KEY,
Rolle VARCHAR(50)
);
CREATE TABLE Projekt_Mitarbeiter (
ProjektNr INT,
MitarbeiterNr INT,
PRIMARY KEY (ProjektNr, MitarbeiterNr),
FOREIGN KEY (MitarbeiterNr) REFERENCES Mitarbeiter_Rolle(MitarbeiterNr)
);
Proceso de normalización
Guía paso a paso
-- Paso 0: Tabla inicial (sin normalizar)
CREATE TABLE Uni_Daten (
MatrikelNr INT,
StudentName VARCHAR(50),
VorlesungNr INT,
VorlesungName VARCHAR(50),
DozentNr INT,
DozentName VARCHAR(50),
Fachbereich VARCHAR(50),
Note DECIMAL(3,1),
Semester INT
);
-- Paso 1: 1NF - Valores atómicos
-- (ya son atómicos en este ejemplo)
-- Paso 2: 2NF - Eliminar dependencias parciales
CREATE TABLE Studenten_2NF (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT
);
CREATE TABLE Vorlesungen_2NF (
VorlesungNr INT PRIMARY KEY,
Name VARCHAR(50),
DozentNr INT,
Fachbereich VARCHAR(50)
);
CREATE TABLE Noten_2NF (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr)
);
-- Paso 3: 3NF - Eliminar dependencias transitivas
CREATE TABLE Dozenten_3NF (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50)
);
CREATE TABLE Fachbereiche_3NF (
Fachbereich VARCHAR(50) PRIMARY KEY
);
-- Estructura final en 3NF
CREATE TABLE Studenten (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT
);
CREATE TABLE Fachbereiche (
Fachbereich VARCHAR(50) PRIMARY KEY
);
CREATE TABLE Dozenten (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50),
FOREIGN KEY (Fachbereich) REFERENCES Fachbereiche(Fachbereich)
);
CREATE TABLE Vorlesungen (
VorlesungNr INT PRIMARY KEY,
Name VARCHAR(50),
DozentenNr INT,
Fachbereich VARCHAR(50),
FOREIGN KEY (DozentenNr) REFERENCES Dozenten(DozentenNr),
FOREIGN KEY (Fachbereich) REFERENCES Fachbereiche(Fachbereich)
);
CREATE TABLE Belegungen (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr),
FOREIGN KEY (MatrikelNr) REFERENCES Studenten(MatrikelNr),
FOREIGN KEY (VorlesungNr) REFERENCES Vorlesungen(VorlesungNr)
);
Algoritmos de normalización
Algoritmo de síntesis
-- Analizar dependencias funcionales
-- F = {MatrikelNr → Name, VorlesungNr → Titel, DozentNr → Name,
-- (MatrikelNr, VorlesungNr) → Note}
-- Paso 1: Encontrar cobertura mínima
-- F_min = {MatrikelNr → Name, VorlesungNr → Titel,
-- DozentNr → Name, (MatrikelNr, VorlesungNr) → Note}
-- Paso 2: Agrupar por lado izquierdo
-- Grupo 1: MatrikelNr → Name → Relación Studenten(MatrikelNr, Name)
-- Grupo 2: VorlesungNr → Titel → Relación Vorlesungen(VorlesungNr, Titel)
-- Grupo 3: DozentNr → Name → Relación Dozenten(DozentenNr, Name)
-- Grupo 4: (MatrikelNr, VorlesungNr) → Note → Relación Noten(MatrikelNr, VorlesungNr, Note)
-- Paso 3: Determinar claves candidatas y complementar
-- Clave candidata: (MatrikelNr, VorlesungNr) para Noten
-- Otras relaciones tienen sus propias claves primarias
Algoritmo de descomposición
-- Tabla inicial con anomalías
CREATE TABLE Probleme (
A INT,
B INT,
C INT,
D INT,
PRIMARY KEY (A, B)
);
-- Dependencias funcionales: A → C, B → D
-- Paso 1: Verificar 2NF
-- C depende solo de A (parcialmente) → dividir
CREATE TABLE R1 (A INT PRIMARY KEY, C INT);
CREATE TABLE R2 (A INT, B INT, D INT, PRIMARY KEY (A, B));
-- Paso 2: Verificar 3NF
-- En R2: B → D (transitiva sobre (A,B) → B) → dividir
CREATE TABLE R3 (B INT PRIMARY KEY, D INT);
CREATE TABLE R4 (A INT, B INT, PRIMARY KEY (A, B));
-- Estructura final en 3NF
-- R1(A, C), R3(B, D), R4(A, B)
Ejemplos prácticos
Base de datos de comercio electrónico
-- Schema normalizado de e-commerce
CREATE TABLE Clientes (
ClienteId INT PRIMARY KEY,
Nombre VARCHAR(50),
Email VARCHAR(100) UNIQUE,
Direccion VARCHAR(200),
Ciudad VARCHAR(50),
CodigoPostal VARCHAR(10)
);
CREATE TABLE Categorias (
CategoriaId INT PRIMARY KEY,
Nombre VARCHAR(50),
Descripcion TEXT
);
CREATE TABLE Productos (
ProductoId INT PRIMARY KEY,
Nombre VARCHAR(100),
Precio DECIMAL(10,2),
Descripcion TEXT,
CategoriaId INT,
Stock INT,
FOREIGN KEY (CategoriaId) REFERENCES Categorias(CategoriaId)
);
CREATE TABLE Pedidos (
PedidoId INT PRIMARY KEY,
ClienteId INT,
FechaPedido DATE,
Total DECIMAL(10,2),
Estado VARCHAR(20),
FOREIGN KEY (ClienteId) REFERENCES Clientes(ClienteId)
);
CREATE TABLE LineaPedidos (
PedidoId INT,
ProductoId INT,
Cantidad INT,
PrecioUnitario DECIMAL(10,2),
PRIMARY KEY (PedidoId, ProductoId),
FOREIGN KEY (PedidoId) REFERENCES Pedidos(PedidoId),
FOREIGN KEY (ProductoId) REFERENCES Productos(ProductoId)
);
Sistema de biblioteca
-- Schema normalizado de biblioteca
CREATE TABLE Autores (
AutorId INT PRIMARY KEY,
Nombre VARCHAR(50),
AñoNacimiento INT
);
CREATE TABLE Libros (
LibroId INT PRIMARY KEY,
Titulo VARCHAR(100),
ISBN VARCHAR(20) UNIQUE,
AñoPublicacion INT,
Editorial VARCHAR(50)
);
CREATE TABLE Libro_Autores (
LibroId INT,
AutorId INT,
PRIMARY KEY (LibroId, AutorId),
FOREIGN KEY (LibroId) REFERENCES Libros(LibroId),
FOREIGN KEY (AutorId) REFERENCES Autores(AutorId)
);
CREATE TABLE Lectores (
LectorId INT PRIMARY KEY,
Nombre VARCHAR(50),
Direccion VARCHAR(200),
Telefono VARCHAR(20)
);
CREATE TABLE Ejemplares (
EjemplarId INT PRIMARY KEY,
LibroId INT,
Estado VARCHAR(20),
FOREIGN KEY (LibroId) REFERENCES Libros(LibroId)
);
CREATE TABLE Prestamos (
PrestamoId INT PRIMARY KEY,
LectorId INT,
EjemplarId INT,
FechaPrestamo DATE,
FechaDevolucion DATE,
FOREIGN KEY (LectorId) REFERENCES Lectores(LectorId),
FOREIGN KEY (EjemplarId) REFERENCES Ejemplares(EjemplarId)
);
Desnormalización
Cuándo y por qué desnormalizar
-- Schema optimizado para performance (desnormalizado)
CREATE TABLE Estadisticas_Producto (
ProductoId INT PRIMARY KEY,
Nombre VARCHAR(100),
Precio DECIMAL(10,2),
NombreCategoria VARCHAR(50), -- Desnormalizado
DescripcionCategoria TEXT, -- Desnormalizado
VentasTotales INT, -- Desnormalizado (Agregado)
UltimoPedido DATE -- Desnormalizado (Agregado)
);
-- Ventajas:
-- Menos JOINs necesarios
-- Consultas más rápidas
-- Mejor rendimiento en lecturas
-- Desventajas:
-- Redundancia de datos
-- Anomalías de actualización
-- Mayor consumo de almacenamiento
Estrategias de desnormalización
-- 1. Agregados precalculados
CREATE TABLE Ventas_Mensuales (
Mes DATE,
ProductoId INT,
CantidadVendida INT,
Ingresos DECIMAL(12,2),
PRIMARY KEY (Mes, ProductoId)
);
-- 2. Atributos copiados
CREATE TABLE Pedidos_Resumido (
PedidoId INT PRIMARY KEY,
NombreCliente VARCHAR(50), -- Copiado de Clientes
EmailCliente VARCHAR(100), -- Copiado de Clientes
FechaPedido DATE,
Total DECIMAL(10,2)
);
-- 3. Datos jerárquicos
CREATE TABLE Empleados_Jerarquia (
EmpleadoId INT PRIMARY KEY,
Nombre VARCHAR(50),
SupervisorId INT,
Ruta VARCHAR(200), -- Ruta desnormalizada
Nivel INT -- Profundidad desnormalizada
);
Conceptos relevantes para exámenes
Definiciones importantes
- Dependencia funcional: X → Y significa que Y depende de X
- Dependencia completa: Y depende de todo X
- Dependencia parcial: Y depende de parte de X
- Dependencia transitiva: X → Y y Y → Z implica X → Z
Formas normales resumidas
| Forma normal | Problema principal | Solución |
|---|---|---|
| 1FN | Grupos repetidos | Valores atómicos |
| 2FN | Dependencias parciales | Separación por claves |
| 3FN | Dependencias transitivas | Eliminación de transitividad |
| BCNF | Determinantes no clave | Todo determinante es superclave |
Tareas típicas en exámenes
- Identifique dependencias funcionales
- Normalice las relaciones proporcionadas
- Explique anomalías y sus causas
- Compare diferentes formas normales
- Justifique decisiones sobre desnormalización
Resumen
La normalización es fundamental para diseños de base de datos de calidad:
- 1FN: Valores atómicos y filas únicas
- 2FN: Elimina dependencias parciales
- 3FN: Elimina dependencias transitivas
- BCNF: Forma normal más robusta para aplicaciones prácticas
El equilibrio adecuado entre normalización y rendimiento es crucial para sistemas de bases de datos exitosos.
Lecturas recomendadas: Bases de datos
Keine Bücher für Kategorie "datenbanken" gefunden.

