Atlasingeniería

Bases de datosModelo relacionalTema 2

Tablas, claves e integridad referencial

Una clave no es un número autoincremental: es la promesa de que cada fila identifica algo distinto. Y la integridad referencial es la única garantía que sobrevive a cualquier bug de la aplicación.

Para este tema conviene tener claro:Modelo entidad-relación

Todo el modelo relacional descansa en una idea simple: una tabla es un conjunto de filas y ninguna fila se repite. Las claves son el mecanismo que lo garantiza, y la integridad referencial es lo que impide que una fila apunte a algo que no existe.

Clave candidata, primaria y alternativa

Una clave candidata es un conjunto mínimo de columnas que identifica cada fila de manera única. Mínimo importa: si con el CUIT alcanza, entonces CUIT más nombre no es clave candidata, es una clave con equipaje.

De las candidatas se elige una como clave primaria; las demás siguen siendo únicas y conviene declararlas como tales. Eso no es decorativo: cada restricción declarada es una verdad que el motor va a hacer cumplir y que el optimizador puede usar para elegir mejores planes.

Cinco empleados. Hay dos Ana y dos personas en la misma oficina.

1 / 6
Probá cada conjunto contra los datos: si dos filas comparten el valor, no es clave. Y si sacándole una columna sigue siendo única, tampoco es candidata, porque candidata quiere decir mínima.

Antes de seguir, predecí

Una tabla usa el correo electrónico como clave primaria. La persona cambia de correo. ¿Qué pasa?

Natural o sustituta: las dos escuelas

Hay dos escuelas. La clave natural usa un atributo del dominio: el CUIT, el ISBN. La sustituta es un identificador inventado sin significado: un entero autoincremental o un UUID.

El problema de las naturales es que el dominio cambia: lo que parecía único e inmutable se duplica, se corrige o se reasigna. Por eso el default sensato es una clave sustituta como primaria y una restricción de unicidad sobre el atributo natural. Se gana estabilidad sin resignar la garantía.

La clave foránea le pasa la regla al motor

Una clave foránea declara que los valores de una columna tienen que existir en otra tabla. Con eso el motor impide crear una factura de un cliente inexistente y borrar un cliente que tiene facturas.

Es la diferencia entre una regla que se cumple siempre y una que se cumple mientras todo el código la respete. Los datos sobreviven a la aplicación: van a ser tocados por scripts, migraciones, importaciones y consultas manuales a las tres de la mañana. La restricción vale ahí, donde ningún validador de la capa de negocio llega.

Qué pasa al borrar lo referenciado

Al declarar la foránea se elige qué pasa si se borra o se actualiza lo referenciado. RESTRICT impide la operación, CASCADE la propaga, SET NULL deja el vínculo vacío.

El default prudente es RESTRICT: que falle y que alguien decida. CASCADE es cómodo para composiciones reales —los ítems de una factura no existen sin ella— y peligroso en el resto, porque un borrado inocente puede llevarse media base sin dejar rastro.

El nulo merece un párrafo propio

El nulo merece un párrafo propio porque no es un valor: es la ausencia de valor, y se comporta distinto. Cualquier comparación con nulo da desconocido, así que columna = NULL nunca es verdadero y hay que usar IS NULL.

Eso se propaga a los filtros, a las agregaciones —que ignoran nulos— y a las restricciones de unicidad, donde en general varios nulos no se consideran repetidos. La consecuencia práctica: declarar NOT NULL en todo lo que el negocio exija, y no usar el nulo como código de estado.

Las restricciones que no son claves

Más allá de las claves, el motor acepta restricciones de dominio: CHECK para rangos y condiciones, tipos apropiados, DEFAULT para valores implícitos. Cada una es una validación que no se puede olvidar.

El argumento en contra —“esas reglas son lógica de negocio y van en el backend”— confunde dos cosas. La lógica de negocio va en el backend; las invariantes de los datos van donde los datos están. Un total que nunca puede ser negativo es un CHECK, y además una validación con mensaje lindo en la aplicación.

Qué hace cumplir el motor y qué no

RestricciónQué garantizaLo que no cubre
PRIMARY KEYunicidad y no nuloque el valor tenga sentido
UNIQUEunicidaden la mayoría de los motores, varios NULL sí se permiten
FOREIGN KEYque la fila referenciada existano crea el índice del lado hijo
CHECKuna condición sobre la filano puede mirar otras filas
NOT NULLque haya un valorque no sea una cadena vacía
La segunda fila sorprende a mucha gente: una columna UNIQUE que admite nulos acepta muchas filas con nulo, porque dos nulos no se consideran iguales.
Más a fondo · nivel seniorEntero autoincremental o UUID

El entero secuencial es chico, ordena por antigüedad y hace que las inserciones caigan siempre al final del índice, que es lo más barato para un árbol B. Sus contras: revela cuántas filas hay —«mi pedido es el 1043»— y obliga a coordinar cuando hay varios nodos escribiendo.

El UUID versión 4 es aleatorio, se genera sin coordinación y no revela nada. Su contra es justamente la aleatoriedad: cada inserción cae en una página distinta del índice, lo fragmenta y multiplica la escritura en disco.

La respuesta moderna es el UUID versión 7, que empieza con una marca de tiempo: se genera sin coordinación como el 4 y se inserta ordenado como el secuencial. Si hoy hay que elegir uno para un sistema nuevo, es el que conviene por defecto.

Cierre

Clave primaria para identificar, restricciones únicas para las demás candidatas, foráneas para que ningún vínculo apunte al vacío, y NOT NULL y CHECK para el resto de las invariantes. Son garantías que sobreviven a cualquier cambio de código, y por eso se declaran en la base.

Autoevaluación

¿Lo entendiste?

Si el CUIT alcanza para identificar un cliente, ¿CUIT más nombre es clave candidata?
¿Cuál es el problema de usar una clave natural como primaria?
Declarar una restricción de unicidad que ya se cumple «porque la aplicación lo garantiza», ¿sirve de algo?
¿Qué impide una clave foránea?