Ya sabes que la Primera Forma Normal exige que cada celda tenga un solo valor y que no haya columnas repetidas. Con eso resuelves los problemas más visibles de una tabla mal diseñada. Pero hay un tipo de redundancia más sutil que la 1FN no elimina, y que puede causar exactamente los mismos problemas: datos duplicados, actualizaciones inconsistentes y pérdida de información al borrar registros.
Ese problema lo resuelve la Segunda Forma Normal (2FN).
En este artículo aprenderás qué es la 2FN, cuándo se aplica, cómo detectar cuándo una tabla la viola y cómo corregirlo, todo con ejemplos concretos y visuales pensados para que quede completamente claro.
El concepto que necesitas entender antes: dependencia funcional
Para entender la 2FN necesitas entender qué es una dependencia funcional. Es más sencillo de lo que suena.
Decimos que una columna B depende funcionalmente de una columna A cuando: si conoces el valor de A, puedes determinar de forma única el valor de B. Lo escribimos así: A → B (A determina B).
Ejemplos cotidianos:
id_producto → nombre_producto: si sabes el ID del producto, sabes su nombre. Un ID solo puede corresponder a un nombre.id_cliente → email_cliente: si sabes el ID del cliente, sabes su email.codigo_postal → ciudad: si sabes el código postal, sabes la ciudad a la que pertenece.
Ahora imagina que la clave primaria de una tabla está formada por dos columnas juntas, por ejemplo (id_pedido, id_producto). Esa combinación identifica de forma única cada fila. Pero ¿qué pasa si una columna de esa tabla solo depende de una parte de la clave, no de la combinación completa? Eso se llama dependencia parcial, y es exactamente lo que la 2FN prohíbe.
Definición de la Segunda Forma Normal
Una tabla está en Segunda Forma Normal cuando cumple estas dos condiciones:
- Ya está en Primera Forma Normal.
- Todos los atributos que no forman parte de la clave primaria dependen de toda la clave primaria, no solo de una parte de ella.
Dicho de otra forma: si tienes una clave primaria compuesta por varias columnas, ningún otro dato de la tabla puede depender solo de una de esas columnas. Debe depender de todas juntas.
Hay un detalle importante: la 2FN solo es relevante cuando la clave primaria está compuesta por más de una columna. Si tu tabla tiene una clave primaria de una sola columna (como un ID autoincremental) y ya está en 1FN, automáticamente cumple la 2FN. No hay partes en una clave de un solo campo, así que no puede haber dependencias parciales.
Un ejemplo que lo hace todo más claro
Imagina una tabla que registra los productos incluidos en cada pedido. La clave primaria es la combinación (id_pedido, id_producto) porque un mismo producto puede aparecer en muchos pedidos, y un pedido puede tener muchos productos.
Tabla detalle_pedidos (viola la 2FN):
| id_pedido (PK) | id_producto (PK) | cantidad | nombre_producto | precio_producto | nombre_cliente | email_cliente |
|---|---|---|---|---|---|---|
| 1 | 101 | 2 | Laptop Pro | 1299.99 | Ana García | ana@mail.com |
| 1 | 102 | 1 | Mouse | 45.50 | Ana García | ana@mail.com |
| 2 | 101 | 1 | Laptop Pro | 1299.99 | Luis Pérez | luis@mail.com |
| 2 | 103 | 3 | Teclado | 89.99 | Luis Pérez | luis@mail.com |
| 3 | 102 | 2 | Mouse | 45.50 | Ana García | ana@mail.com |
A primera vista puede parecer razonable. Pero analicemos las dependencias funcionales de cada columna respecto a la clave primaria (id_pedido, id_producto):
(id_pedido, id_producto) → cantidad: la cantidad de ese producto en ese pedido específico depende de los dos. ✅ Dependencia completa.id_producto → nombre_producto: el nombre del producto solo depende del ID del producto, no del pedido. ❌ Dependencia parcial.id_producto → precio_producto: el precio del producto solo depende del ID del producto. ❌ Dependencia parcial.id_pedido → nombre_cliente: el nombre del cliente solo depende del pedido. ❌ Dependencia parcial.id_pedido → email_cliente: el email del cliente solo depende del pedido. ❌ Dependencia parcial.
De cinco columnas adicionales, cuatro tienen dependencias parciales. La tabla viola la 2FN de forma severa.
Los problemas concretos que causa esta violación
No es solo una cuestión teórica. Esas dependencias parciales generan problemas reales:
Anomalía de actualización
El producto con id_producto = 101 (Laptop Pro) aparece en dos filas con el mismo nombre y precio. Si ese producto cambia de nombre, tienes que actualizar todas las filas donde aparece. Si actualizas solo algunas, la base de datos tendrá datos contradictorios: el mismo producto con dos nombres diferentes.
-- ❌ Problema: si el nombre del producto cambia,
-- hay que actualizar todas las filas que lo mencionan
UPDATE detalle_pedidos
SET nombre_producto = 'Laptop Pro 2025'
WHERE id_producto = 101;
-- ¿Cuántas filas hay que actualizar? ¿Y si se te olvida alguna?Anomalía de inserción
¿Puedes registrar un nuevo producto en el catálogo antes de que aparezca en algún pedido? No. Esta tabla solo permite agregar productos cuando ya están dentro de un pedido. Si quieres crear el catálogo de productos independientemente, no puedes.
Anomalía de eliminación
Si el pedido número 3 se cancela y eliminas sus filas, también pierdes el registro de que Ana García existe como cliente. Si ese era el único pedido que tenía con ese email, esa información desaparece junto con el pedido.
-- ❌ Problema: eliminar el pedido 3 hace desaparecer datos del cliente
DELETE FROM detalle_pedidos WHERE id_pedido = 3;
-- Ana García desaparece si no tenía otros pedidosCómo aplicar la 2FN: separar según de qué depende cada columna
La solución es mecánica una vez identificadas las dependencias: cada grupo de columnas que depende de la misma clave va en su propia tabla. Veamos el proceso paso a paso.
Paso 1: identificar todos los grupos de dependencia
- Dependen de
(id_pedido, id_producto)juntos:cantidad - Dependen solo de
id_producto:nombre_producto,precio_producto - Dependen solo de
id_pedido:nombre_cliente,email_cliente
Paso 2: crear una tabla para cada grupo
Tabla productos (columnas que dependen de id_producto):
| id_producto (PK) | nombre_producto | precio_producto |
|---|---|---|
| 101 | Laptop Pro | 1299.99 |
| 102 | Mouse | 45.50 |
| 103 | Teclado | 89.99 |
Tabla pedidos (columnas que dependen de id_pedido):
| id_pedido (PK) | nombre_cliente | email_cliente |
|---|---|---|
| 1 | Ana García | ana@mail.com |
| 2 | Luis Pérez | luis@mail.com |
| 3 | Ana García | ana@mail.com |
Tabla detalle_pedidos (columna que depende de la clave compuesta completa):
| id_pedido (PK) | id_producto (PK) | cantidad |
|---|---|---|
| 1 | 101 | 2 |
| 1 | 102 | 1 |
| 2 | 101 | 1 |
| 2 | 103 | 3 |
| 3 | 102 | 2 |
Ahora cada tabla tiene exactamente lo que le corresponde. Los problemas anteriores desaparecen:
- Cambiar el nombre de un producto: una sola fila en la tabla
productos. - Agregar un producto al catálogo sin que esté en ningún pedido: perfectamente posible insertando en
productos. - Eliminar el pedido 3: no se pierde ningún dato del cliente porque el cliente tiene su propia tabla (o en una mejora posterior, una tabla
clientesseparada).
Segundo ejemplo: tabla de notas de estudiantes
Otro caso muy ilustrativo. Una tabla que registra las notas de los estudiantes en distintas asignaturas:
Tabla notas (viola la 2FN):
| id_estudiante (PK) | id_asignatura (PK) | nota | nombre_estudiante | carrera_estudiante | nombre_asignatura | creditos_asignatura | departamento |
|---|---|---|---|---|---|---|---|
| E001 | A10 | 8.5 | Laura Martín | Ingeniería | Bases de Datos | 6 | Informática |
| E001 | A11 | 7.0 | Laura Martín | Ingeniería | Redes | 4 | Informática |
| E002 | A10 | 9.0 | Marco Torres | Telemática | Bases de Datos | 6 | Informática |
| E002 | A12 | 6.5 | Marco Torres | Telemática | Matemáticas | 8 | Ciencias |
Análisis de dependencias con clave (id_estudiante, id_asignatura):
(id_estudiante, id_asignatura) → nota: la nota depende de ambos. Un estudiante puede tener distinta nota en cada asignatura. ✅ Completa.id_estudiante → nombre_estudiante: el nombre depende solo del estudiante. ❌ Parcial.id_estudiante → carrera_estudiante: la carrera depende solo del estudiante. ❌ Parcial.id_asignatura → nombre_asignatura: el nombre depende solo de la asignatura. ❌ Parcial.id_asignatura → creditos_asignatura: los créditos dependen solo de la asignatura. ❌ Parcial.id_asignatura → departamento: el departamento depende solo de la asignatura. ❌ Parcial.
De seis columnas, cinco tienen dependencias parciales. Apliquemos la 2FN:
Tabla estudiantes:
| id_estudiante (PK) | nombre | carrera |
|---|---|---|
| E001 | Laura Martín | Ingeniería |
| E002 | Marco Torres | Telemática |
Tabla asignaturas:
| id_asignatura (PK) | nombre | creditos | departamento |
|---|---|---|---|
| A10 | Bases de Datos | 6 | Informática |
| A11 | Redes | 4 | Informática |
| A12 | Matemáticas | 8 | Ciencias |
Tabla notas (solo lo que depende de ambas claves):
| id_estudiante (PK) | id_asignatura (PK) | nota |
|---|---|---|
| E001 | A10 | 8.5 |
| E001 | A11 | 7.0 |
| E002 | A10 | 9.0 |
| E002 | A12 | 6.5 |
Ahora los datos de los estudiantes y las asignaturas se guardan una sola vez. Consultar las notas con contexto sigue siendo sencillo con un JOIN:
SELECT
e.nombre AS estudiante,
a.nombre AS asignatura,
a.creditos,
n.nota
FROM notas n
JOIN estudiantes e ON e.id_estudiante = n.id_estudiante
JOIN asignaturas a ON a.id_asignatura = n.id_asignatura
ORDER BY e.nombre, a.nombre;Tercer ejemplo: reservas de hotel
Un caso práctico de un sistema de reservas. Cuando alguien reserva una habitación, se registra así:
Tabla reservas (viola la 2FN):
| id_reserva (PK) | id_habitacion (PK) | fecha_entrada | fecha_salida | tipo_habitacion | precio_noche | capacidad | nombre_huesped | email_huesped |
|---|---|---|---|---|---|---|---|---|
| R001 | 101 | 2024-07-10 | 2024-07-13 | Doble | 120.00 | 2 | Carmen Vega | carmen@mail.com |
| R002 | 205 | 2024-07-15 | 2024-07-17 | Suite | 280.00 | 3 | Roberto Gil | roberto@mail.com |
| R003 | 101 | 2024-08-01 | 2024-08-05 | Doble | 120.00 | 2 | Ana García | ana@mail.com |
Dependencias con clave (id_reserva, id_habitacion):
(id_reserva, id_habitacion) → fecha_entrada, fecha_salida: las fechas de una reserva en una habitación concreta dependen de ambas. ✅ Completa.id_habitacion → tipo_habitacion: el tipo de habitación no cambia por cada reserva. ❌ Parcial.id_habitacion → precio_noche: el precio por noche es una característica de la habitación. ❌ Parcial.id_habitacion → capacidad: la capacidad es fija en la habitación. ❌ Parcial.id_reserva → nombre_huesped: el huésped depende de la reserva, no de la habitación. ❌ Parcial.id_reserva → email_huesped: igual que el anterior. ❌ Parcial.
Aplicando la 2FN, separamos en tres tablas:
Tabla habitaciones:
| id_habitacion (PK) | tipo | precio_noche | capacidad |
|---|---|---|---|
| 101 | Doble | 120.00 | 2 |
| 205 | Suite | 280.00 | 3 |
Tabla huespedes:
| id_reserva (PK) | nombre | |
|---|---|---|
| R001 | Carmen Vega | carmen@mail.com |
| R002 | Roberto Gil | roberto@mail.com |
| R003 | Ana García | ana@mail.com |
Tabla reservas (solo lo que depende de ambas claves):
| id_reserva (PK) | id_habitacion (PK) | fecha_entrada | fecha_salida |
|---|---|---|---|
| R001 | 101 | 2024-07-10 | 2024-07-13 |
| R002 | 205 | 2024-07-15 | 2024-07-17 |
| R003 | 101 | 2024-08-01 | 2024-08-05 |
Ahora si el precio de la habitación 101 sube de 120 a 135 euros por noche, se actualiza en un solo lugar. Las reservas históricas que ya guardaron el precio que pagó el cliente en su momento no se ven afectadas.
El diagrama mental para detectar violaciones
Cuando tengas una tabla con clave primaria compuesta, hazte esta pregunta por cada columna que no forma parte de la clave:
¿Esta columna describe a la fila completa (combinación de ambas claves) o describe solo a uno de los elementos de la clave por separado?
- Si describe la fila completa → dependencia completa. ✅ Está bien donde está.
- Si describe solo uno de los elementos → dependencia parcial. ❌ Debe ir a una tabla separada.
En la tabla de notas:
- ¿La nota describe al estudiante en esa asignatura (fila completa)? Sí. ✅
- ¿El nombre del estudiante describe al estudiante en esa asignatura? No, describe al estudiante sin importar la asignatura. ❌
- ¿Los créditos de la asignatura describen al estudiante en esa asignatura? No, describen a la asignatura sin importar el estudiante. ❌
El proceso completo en SQL
Veamos cómo queda el ejemplo del pedido en SQL correcto desde el principio:
-- Tabla de clientes (entidad propia)
CREATE TABLE clientes (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL
);
-- Tabla de productos (entidad propia)
CREATE TABLE productos (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
precio DECIMAL(10, 2) NOT NULL,
stock INT DEFAULT 0
);
-- Tabla de pedidos (vincula un cliente con su pedido)
CREATE TABLE pedidos (
id INT AUTO_INCREMENT PRIMARY KEY,
id_cliente INT NOT NULL,
fecha DATE NOT NULL,
estado VARCHAR(20) DEFAULT 'pendiente',
FOREIGN KEY (id_cliente) REFERENCES clientes(id)
);
-- Tabla de detalle: solo lo que depende de (id_pedido, id_producto) juntos
CREATE TABLE detalle_pedidos (
id_pedido INT NOT NULL,
id_producto INT NOT NULL,
cantidad INT NOT NULL,
precio_unit DECIMAL(10, 2) NOT NULL, -- precio en el momento del pedido
PRIMARY KEY (id_pedido, id_producto),
FOREIGN KEY (id_pedido) REFERENCES pedidos(id),
FOREIGN KEY (id_producto) REFERENCES productos(id)
);Nota que precio_unit sí pertenece en detalle_pedidos aunque el precio también esté en productos. ¿Por qué? Porque el precio que pagó el cliente en ese pedido es un hecho histórico de esa transacción específica. Si mañana el producto sube de precio, los pedidos anteriores deben conservar el precio que se cobró en su momento. Ese precio depende de la combinación (id_pedido, id_producto), no solo del producto.
Resumen visual del proceso
| Columna | Depende de | Tipo de dependencia | ¿Dónde va? |
|---|---|---|---|
| cantidad | (id_pedido, id_producto) | ✅ Completa | detalle_pedidos |
| precio_unit | (id_pedido, id_producto) | ✅ Completa | detalle_pedidos |
| nombre_producto | id_producto | ❌ Parcial | productos |
| precio_producto | id_producto | ❌ Parcial | productos |
| nombre_cliente | id_pedido | ❌ Parcial | pedidos / clientes |
| email_cliente | id_pedido | ❌ Parcial | pedidos / clientes |
Lista de verificación para la 2FN
Antes de dar una tabla por válida respecto a la 2FN, hazte estas preguntas:
- ¿La clave primaria está formada por más de una columna? Si no, la tabla ya cumple la 2FN automáticamente (si está en 1FN).
- ¿Hay alguna columna que describe a uno de los elementos de la clave por sí solo, sin necesitar el otro?
- ¿Hay datos que se repiten en varias filas porque siempre van acompañados de la misma parte de la clave?
- ¿Si cambias un dato de un elemento (producto, estudiante, habitación) tienes que actualizarlo en múltiples filas?
Si alguna de las últimas tres preguntas tiene respuesta afirmativa, hay dependencias parciales que resolver.
Conclusión
La Segunda Forma Normal elimina un tipo de redundancia invisible a primera vista pero con consecuencias reales: las dependencias parciales. Cuando una tabla tiene clave primaria compuesta y algunas columnas solo dependen de parte de esa clave, los datos se repiten, las actualizaciones se vuelven peligrosas y la integridad del sistema se compromete.
El proceso para aplicarla es siempre el mismo: identificar de qué depende cada columna, agrupar las que comparten la misma dependencia y crear una tabla para cada grupo. El resultado es un esquema más limpio, con menos redundancia y mucho más fácil de mantener.
Recuerda que la 2FN es un paso intermedio. Una vez que tu diseño la cumple, el siguiente objetivo es aplicar la Tercera Forma Normal para eliminar las dependencias transitivas, el otro tipo de redundancia que aún puede existir aunque ya hayas aplicado la 1FN y la 2FN. Y si quieres afianzar la base antes de continuar, te recomendamos releer el artículo sobre la Primera Forma Normal con ejemplos claros.
No hay comentarios todavía. Sé el primero en compartir tu opinión.