Atlasingeniería

Bases de datosModelo relacionalTema 1

Modelo entidad-relación

Antes de escribir una tabla conviene dibujar de qué habla el sistema. El modelo entidad-relación existe para discutir el dominio con quien lo conoce, no para documentar la base ya hecha.

La mayoría de los problemas caros de una base de datos no son de rendimiento: son de modelado, y se decidieron el primer día. Una entidad que debió ser dos, o una relación cuya cardinalidad se leyó mal, se paga en cada consulta y en cada migración posterior.

Entidades, atributos y relaciones

El modelo tiene tres piezas. Las entidades son las cosas de las que el sistema guarda información: cliente, factura, producto. Los atributos son sus datos. Las relaciones son los vínculos entre entidades: un cliente emite facturas, una factura tiene ítems.

La prueba práctica para separar entidad de atributo es preguntarse si la cosa tiene vida propia —identidad, atributos propios, historia— o si sólo describe a otra. Una dirección suele ser un atributo; si hay que guardar varias por cliente y saber cuál está vigente, ya es una entidad.

Antes de seguir, predecí

Una relación de muchos a muchos entre alumnos y materias, ¿cómo se guarda en tablas?

La cardinalidad es la decisión más cara

La cardinalidad dice cuántas instancias de cada lado participan: uno a uno, uno a muchos, muchos a muchos. Es la decisión que más consecuencias tiene y la que más se discute mal.

Conviene enunciarla en voz alta y en las dos direcciones: “una factura pertenece a un solo cliente; un cliente puede tener muchas facturas”. Y siempre preguntar por el futuro, no por hoy: “¿puede un producto tener más de un proveedor alguna vez?”. Esa pregunta cuesta un minuto ahora y una migración con datos en producción después.

Tres entidades, sin relaciones todavía. Cada una tiene identidad propia: se puede hablar de «el cliente 7» sin mencionar nada más.

1 / 5
La relación muchos a muchos entre factura y producto no se puede dibujar como una flecha: necesita una entidad propia. Y esa entidad intermedia casi siempre termina teniendo atributos —cantidad, precio del día— que no pertenecen ni a una ni a la otra.

Obligatoria u opcional

Además de cuántos, importa si la participación es obligatoria u opcional. ¿Puede existir una factura sin ítems? ¿Un empleado sin departamento asignado?

Eso se traduce directo en si la columna admite nulos, y define qué estados intermedios son válidos. Un modelo que no distingue obligatorio de opcional termina en una base llena de nulos que significan cosas distintas: “no se cargó todavía”, “no aplica” y “se perdió el dato” conviviendo en la misma columna.

El pasaje a tablas es casi mecánico

El pasaje a tablas es casi mecánico. Cada entidad es una tabla con su clave primaria. Una relación uno a muchos se resuelve poniendo la clave foránea del lado “muchos”: la factura guarda el identificador del cliente.

Una relación muchos a muchos necesita una tabla propia, con las dos claves foráneas. Y ahí aparece algo útil: esa tabla intermedia casi siempre termina teniendo atributos —cantidad, precio pactado, fecha— y eso confirma que era una entidad disfrazada de relación.

Los errores que se repiten

Los errores típicos se repiten. Modelar un estado como muchas columnas booleanas en vez de un atributo con dominio acotado. Guardar listas en un campo de texto separadas por comas, que rompe toda consulta posterior. Reusar una misma tabla para dos conceptos parecidos porque “comparten casi todas las columnas”.

Y el más caro: no modelar el tiempo. Si el precio de un producto cambia, ¿la factura vieja tiene que seguir mostrando el precio de entonces? Casi siempre sí, y eso significa copiar el valor al momento de emitir, no apuntar al producto.

El diagrama es una herramienta de conversación

El diagrama no es documentación: es una herramienta de conversación. Su público es quien conoce el negocio y no sabe SQL, y su valor está en que esa persona pueda mirarlo y decir “eso está mal, un cliente puede tener dos cuentas”.

Por eso conviene mantenerlo en el vocabulario del dominio y sin detalles de implementación. Una vez que la base existe, el esquema es la verdad; el diagrama sirve para las decisiones nuevas.

Del diagrama a las tablas

CREATE TABLE customers (
  id      BIGSERIAL PRIMARY KEY,
  cuit    VARCHAR(13) NOT NULL UNIQUE,   -- clave natural, como restricción
  name    TEXT        NOT NULL
);

CREATE TABLE invoices (
  id          BIGSERIAL PRIMARY KEY,
  customer_id BIGINT      NOT NULL REFERENCES customers(id),  -- el lado «muchos»
  issued_at   TIMESTAMPTZ NOT NULL,
  UNIQUE (customer_id, issued_at)        -- una regla de negocio, hecha cumplir por el motor
);

-- La relación muchos a muchos, con sus propios atributos
CREATE TABLE invoice_items (
  invoice_id  BIGINT         NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
  product_id  BIGINT         NOT NULL REFERENCES products(id),
  quantity    INTEGER        NOT NULL CHECK (quantity > 0),
  unit_price  NUMERIC(12,2)  NOT NULL,   -- el precio del día, congelado
  PRIMARY KEY (invoice_id, product_id)
);

CREATE INDEX ON invoices (customer_id);  -- la foránea no se indexa sola

Tres traducciones a la vista. El uno a muchos es una columna con REFERENCES en el lado «muchos». El muchos a muchos es una tabla propia, con clave primaria compuesta. Y el unit_price en el ítem —no en el producto— es la decisión de modelado que más se discute y la más importante: el precio de una venta es un dato histórico y tiene que quedar congelado, aunque el del producto cambie mañana.

El ON DELETE CASCADE del ítem contra su ausencia en product_id también es una decisión: borrar una factura borra sus ítems, pero un producto usado en una factura no se puede borrar, y eso está bien.

Las decisiones que cuestan caro después

DecisiónLo cómodo hoyLo que convienePor qué
Clave primariala natural: el CUITuna sustituta, y la natural como UNIQUElas claves naturales cambian y se corrigen
Cardinalidad dudosauno a muchospreguntar por el futuropasar de 1:N a N:M es una migración
BorradoDELETEuna marca de bajalo borrado no se puede auditar
Un campo con varios valoresuna lista separada por comasuna tablano se puede indexar ni consultar
Atributos que varían por tipocolumnas nulas para todostablas por tipo o JSON validadouna tabla con 40 columnas nulas no dice nada
La segunda fila es la que más cuesta: cambiar una cardinalidad implica mover datos, y se descubre cuando ya hay millones de filas.

Cierre

Entidades, atributos y relaciones con su cardinalidad y su obligatoriedad. El pasaje a tablas es mecánico; lo difícil es antes: separar entidad de atributo, leer bien la cardinalidad, decidir qué es opcional y modelar el tiempo donde el negocio lo necesita.

Autoevaluación

¿Lo entendiste?

¿Cómo se decide si algo es entidad o atributo?
¿Cómo conviene enunciar una cardinalidad?
¿Por qué los problemas de modelado son los más caros?
Además de cuántos, ¿qué otra cosa define una relación?