La Primera Forma Normal explicada con ejemplos claros

D
DanisCh
(Actualizado: ) • 11 min de lectura
La Primera Forma Normal explicada con ejemplos claros
Conceptos de Base de Datos

Cuando alguien empieza a diseñar bases de datos sin conocer las reglas de normalización, el resultado suele parecerse a una hoja de cálculo: columnas que se repiten, celdas con varios valores separados por comas, grupos de datos mezclados en la misma tabla. Todo parece funcionar hasta que intentas consultar, actualizar o escalar esos datos.

La Primera Forma Normal (abreviada 1FN o 1NF en inglés) es el punto de partida de cualquier diseño relacional correcto. Es la regla más básica de la normalización y, sin embargo, es la que más frecuentemente se viola cuando se diseña sin experiencia.

En este artículo aprenderás exactamente qué es la 1FN, cuáles son sus reglas, cómo reconocer cuándo una tabla la viola y cómo corregirla, todo con ejemplos concretos y visuales.

Qué es la Primera Forma Normal

Una tabla está en Primera Forma Normal cuando cumple cuatro condiciones:

  • Atomicidad: cada celda contiene un único valor indivisible. No puede haber listas, conjuntos ni múltiples valores separados por comas en una sola celda.
  • Homogeneidad: todos los valores de una columna son del mismo tipo de dato.
  • Unicidad de columnas: cada columna tiene un nombre único. No puede haber dos columnas con el mismo nombre ni grupos de columnas repetidos como telefono1, telefono2, telefono3.
  • Clave primaria: cada fila es única e identificable de forma inequívoca por una clave primaria.

La idea central es que la base de datos relacional no es una hoja de cálculo. Una celda almacena un valor y un solo valor. Si necesitas guardar varios teléfonos de un cliente, no los metes todos en una celda ni creas columnas repetidas: creas filas adicionales o una tabla separada.

Violación 1: valores múltiples en una celda

Es la violación más común y la más fácil de cometer. Ocurre cuando guardas varios valores en una sola celda, ya sea separados por comas, por punto y coma, por barras o cualquier otro separador.

Tabla que viola la 1FN:

id_clientenombretelefonoscategorias_interes
1Ana García612345678, 911234567Electrónica, Libros, Ropa
2Luis Pérez698765432Deportes
3María López677889900, 934567890, 655443322Libros, Jardín

Esta tabla tiene problemas graves:

  • Consultar un teléfono específico es costoso. Para saber si el número 911234567 pertenece a algún cliente tendrías que buscar dentro de strings con LIKE o funciones de texto, lo cual es lento e impreciso.
  • Contar los teléfonos por cliente es imposible de forma directa. No puedes hacer COUNT(telefonos) porque hay varios valores en una celda.
  • Añadir un teléfono significa modificar una celda. Lo correcto en una base de datos relacional es insertar una fila, no editar texto dentro de una celda.
  • Ordenar o filtrar por categoría es inmanejable. ¿Cómo obtienes todos los clientes interesados en "Libros"? Necesitarías LIKE '%Libros%', que es frágil y no usa índices.

Cómo corregirlo: una fila por valor

La solución para valores múltiples es crear una tabla separada donde cada valor ocupa su propia fila.

Tabla clientes (corregida):

id_cliente (PK)nombre
1Ana García
2Luis Pérez
3María López

Tabla telefonos_clientes (nueva):

id (PK)id_cliente (FK)telefonotipo
11612345678móvil
21911234567fijo
32698765432móvil
43677889900móvil
53934567890fijo
63655443322trabajo

Tabla categorias_interes (nueva):

id_cliente (FK)categoria
1Electrónica
1Libros
1Ropa
2Deportes
3Libros
3Jardín

Ahora buscar todos los clientes interesados en Libros es una consulta directa y eficiente:

SELECT c.nombre
FROM clientes c
JOIN categorias_interes ci ON ci.id_cliente = c.id
WHERE ci.categoria = 'Libros';

Violación 2: columnas repetidas

Esta violación ocurre cuando se crean varias columnas para almacenar el mismo tipo de información, numerándolas para distinguirlas: telefono1, telefono2, telefono3.

Tabla que viola la 1FN:

id_empleadonombrehabilidad1habilidad2habilidad3habilidad4
1Carlos RuizPythonJavaScriptSQLNULL
2Sofía MoraJavaDockerNULLNULL
3Pedro DíazPythonMachine LearningTensorFlowPandas

Los problemas son predecibles y graves:

  • ¿Cuántas habilidades puede tener un empleado? La estructura actual dice que cuatro como máximo. Si alguien tiene cinco, hay que alterar la tabla y agregar una columna más, lo cual rompe código y consultas existentes.
  • Los NULL desperdician espacio y complican las consultas.
  • Buscar todos los empleados que saben Python requiere buscar en cuatro columnas distintas: WHERE habilidad1 = 'Python' OR habilidad2 = 'Python' OR habilidad3 = 'Python' OR habilidad4 = 'Python'. Frágil, repetitivo y no escalable.
  • El orden de las columnas no tiene sentido semántico. ¿Por qué Python está en habilidad1 para Carlos y también en habilidad1 para Pedro? ¿Qué significa esa posición?

Cómo corregirlo: tabla separada con relación

Tabla empleados (corregida):

id_empleado (PK)nombre
1Carlos Ruiz
2Sofía Mora
3Pedro Díaz

Tabla empleado_habilidades (nueva):

id_empleado (FK)habilidadnivel
1Pythonavanzado
1JavaScriptintermedio
1SQLavanzado
2Javaavanzado
2Dockerintermedio
3Pythonavanzado
3Machine Learningavanzado
3TensorFlowintermedio
3Pandasavanzado

Ahora la consulta para encontrar empleados con Python es simple, rápida y usa índices:

SELECT e.nombre, eh.nivel
FROM empleados e
JOIN empleado_habilidades eh ON eh.id_empleado = e.id
WHERE eh.habilidad = 'Python';

Y si un empleado aprende una nueva habilidad, simplemente insertas una fila. No hay que alterar la estructura de la tabla.

Violación 3: valores no atómicos compuestos

Otra forma de violar la 1FN es guardar un valor que en realidad está compuesto por partes distintas y que se necesitará consultar por separado.

Tabla que viola la 1FN:

id_pedidoclientedireccion_entrega
1Ana GarcíaCalle Mayor 15, 3ºB, 28001, Madrid, España
2Luis PérezAv. Diagonal 432, 08037, Barcelona, España

El campo direccion_entrega mezcla calle, número, piso, código postal, ciudad y país en una sola celda. El problema aparece cuando necesitas:

  • Filtrar todos los pedidos enviados a Madrid.
  • Agrupar pedidos por código postal para optimizar rutas de reparto.
  • Validar que el código postal tiene el formato correcto.
  • Mostrar solo la ciudad en un informe.

Todo eso requiere parsear el texto, lo cual es costoso, frágil y proclive a errores si el formato de la dirección no es siempre igual.

Cómo corregirlo: separar en columnas atómicas

id_pedidoclientecallenumeropisocodigo_postalciudadpais
1Ana GarcíaCalle Mayor153ºB28001MadridEspaña
2Luis PérezAv. Diagonal432NULL08037BarcelonaEspaña

Ahora cada parte de la dirección es consultable, filtrable e indexable de forma independiente.

Violación 4: ausencia de clave primaria

Sin una clave primaria, no hay forma de identificar una fila de forma única. Eso significa que no puedes actualizar un registro específico con certeza de que estás modificando el correcto, y no puedes crear relaciones entre tablas de forma confiable.

Tabla que viola la 1FN (sin clave primaria):

nombreemailciudad
Ana Garcíaana@mail.comMadrid
Luis Pérezluis@mail.comBarcelona
Ana Garcíaana@mail.comSevilla

¿Son la segunda y tercera fila la misma persona que se mudó, o dos personas distintas llamadas igual con el mismo email? Sin clave primaria, es imposible saberlo con certeza.

Cómo corregirlo

La solución más sencilla es agregar una columna de identificador único autoincremental:

CREATE TABLE clientes (
  id      INT          AUTO_INCREMENT PRIMARY KEY,
  nombre  VARCHAR(100) NOT NULL,
  email   VARCHAR(100) UNIQUE NOT NULL,
  ciudad  VARCHAR(50)
);
id (PK)nombreemailciudad
1Ana Garcíaana@mail.comMadrid
2Luis Pérezluis@mail.comBarcelona

En este caso, la restricción UNIQUE en email también garantiza que no puede haber dos registros con el mismo correo, lo cual resuelve el duplicado anterior.

Ejemplo completo: hoja de cálculo a base de datos en 1FN

Veamos un caso realista. Una empresa lleva el registro de sus pedidos en una hoja de Excel así:

FechaClienteContactoProductos compradosCantidadesTotal
2024-06-01TechCorp S.L.Ana López, 612000111Laptop Pro, Mouse, Teclado2, 5, 53089.93
2024-06-03DataSystemsJuan Mora, 698111222Monitor 27", Hub USB3, 101399.87

Esta hoja viola la 1FN de múltiples formas:

  • Contacto mezcla nombre y teléfono en una celda.
  • Productos comprados tiene múltiples valores en una celda.
  • Cantidades tiene múltiples valores que deben emparejarse con los productos por posición (frágil e imposible de consultar).
  • No hay clave primaria.

Aplicando la 1FN, el diseño correcto en SQL sería:

-- Empresas clientes
CREATE TABLE empresas (
  id        INT          AUTO_INCREMENT PRIMARY KEY,
  nombre    VARCHAR(150) NOT NULL
);

-- Contactos de cada empresa
CREATE TABLE contactos (
  id          INT          AUTO_INCREMENT PRIMARY KEY,
  id_empresa  INT          NOT NULL,
  nombre      VARCHAR(100) NOT NULL,
  telefono    VARCHAR(20),
  FOREIGN KEY (id_empresa) REFERENCES empresas(id)
);

-- Catálogo de productos
CREATE TABLE productos (
  id      INT            AUTO_INCREMENT PRIMARY KEY,
  nombre  VARCHAR(100)   NOT NULL,
  precio  DECIMAL(10, 2) NOT NULL
);

-- Cabecera del pedido
CREATE TABLE pedidos (
  id          INT  AUTO_INCREMENT PRIMARY KEY,
  id_empresa  INT  NOT NULL,
  fecha       DATE NOT NULL,
  FOREIGN KEY (id_empresa) REFERENCES empresas(id)
);

-- Líneas del pedido: una fila por producto, valor atómico
CREATE TABLE lineas_pedido (
  id_pedido   INT            NOT NULL,
  id_producto INT            NOT NULL,
  cantidad    INT            NOT NULL,
  precio_unit DECIMAL(10, 2) NOT NULL,
  PRIMARY KEY (id_pedido, id_producto),
  FOREIGN KEY (id_pedido)   REFERENCES pedidos(id),
  FOREIGN KEY (id_producto) REFERENCES productos(id)
);

Con este diseño los datos del ejemplo quedan así:

empresas:

idnombre
1TechCorp S.L.
2DataSystems

contactos:

idid_empresanombretelefono
11Ana López612000111
22Juan Mora698111222

pedidos:

idid_empresafecha
112024-06-01
222024-06-03

lineas_pedido:

id_pedidoid_productocantidadprecio_unit
1121299.99
12545.50
13589.99
243349.99
251029.90

Ahora cualquier consulta es directa, eficiente y expresiva:

-- Calcular el total del pedido 1
SELECT SUM(cantidad * precio_unit) AS total
FROM lineas_pedido
WHERE id_pedido = 1;

-- Productos más vendidos
SELECT p.nombre, SUM(lp.cantidad) AS unidades_vendidas
FROM lineas_pedido lp
JOIN productos p ON p.id = lp.id_producto
GROUP BY p.nombre
ORDER BY unidades_vendidas DESC;

-- Todos los pedidos de TechCorp
SELECT pe.fecha, SUM(lp.cantidad * lp.precio_unit) AS total
FROM pedidos pe
JOIN lineas_pedido lp ON lp.id_pedido = pe.id
JOIN empresas e ON e.id = pe.id_empresa
WHERE e.nombre = 'TechCorp S.L.'
GROUP BY pe.id, pe.fecha;

Un caso especial: ¿cuándo es aceptable un valor compuesto?

La 1FN exige atomicidad, pero la definición de "atómico" depende del contexto y de cómo se van a usar los datos. Si guardas una dirección en una columna direccion y nunca necesitarás filtrar por ciudad, código postal o calle de forma independiente, podría ser aceptable guardarla completa.

La pregunta clave es: ¿alguna vez necesitaré consultar, filtrar, ordenar o actualizar partes de este valor por separado? Si la respuesta es sí, debes atomizar. Si la respuesta es no, puedes mantenerlo compuesto.

Sin embargo, en la duda, siempre es mejor separar. Es mucho más fácil concatenar valores en una consulta que parsear y dividir texto almacenado incorrectamente.

-- Si separas, siempre puedes concatenar cuando lo necesites
SELECT CONCAT(calle, ' ', numero, ', ', codigo_postal, ' ', ciudad) AS direccion_completa
FROM pedidos;

-- Pero si guardas junto, no puedes separar de forma confiable
SELECT ciudad FROM pedidos;  -- imposible si está todo en un string

Lista de verificación: ¿tu tabla cumple la 1FN?

Antes de dar por válida una tabla, hazte estas preguntas:

  • ¿Alguna celda puede contener una lista de valores separados por comas, punto y coma u otro delimitador?
  • ¿Hay columnas que se llaman igual pero con número al final: campo1, campo2, campo3?
  • ¿Alguna columna mezcla dos tipos de información distintos, como nombre y teléfono juntos?
  • ¿Puedo identificar cada fila de forma única con una clave primaria?
  • ¿Alguna columna podría tener valores de tipos distintos según la fila, como texto en unas filas y números en otras?

Si la respuesta a cualquiera de las primeras tres es sí, o la cuarta es no, la tabla viola la Primera Forma Normal y necesita ser rediseñada.

Conclusión

La Primera Forma Normal es el fundamento sobre el que se construye todo diseño relacional correcto. Sus reglas son simples en concepto: valores atómicos, sin columnas repetidas, clave primaria en cada tabla. Pero su impacto en la calidad, mantenibilidad y rendimiento de la base de datos es enorme.

Cada vez que sientas la tentación de guardar varios valores en una celda separados por comas, o de crear columnas como item1, item2, item3, recuerda que estás sacrificando la capacidad de consultar y mantener esos datos de forma eficiente. La solución siempre es la misma: una fila por valor, una columna por tipo de dato.

La 1FN es el primer paso. Una vez que tu diseño la cumple, el siguiente objetivo es aplicar la Segunda y Tercera Forma Normal para eliminar redundancias más sutiles. Y para entender el contexto completo del diseño relacional, te recomendamos leer nuestro artículo sobre cómo funciona el modelo entidad-relación y los diagramas ER, que es el paso previo a la normalización.

Etiquetas: normalizacion

¿Te ha gustado esta entrada?

Compártela con tus compañeros para que también sigan aprendiendo.

Comunidad y Comentarios

0 COMENTARIOS

No hay comentarios todavía. Sé el primero en compartir tu opinión.

Escribe tu opinión
Respondiendo a