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.
Antes de seguir, predecí
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ón | Qué garantiza | Lo que no cubre |
|---|---|---|
| PRIMARY KEY | unicidad y no nulo | que el valor tenga sentido |
| UNIQUE | unicidad | en la mayoría de los motores, varios NULL sí se permiten |
| FOREIGN KEY | que la fila referenciada exista | no crea el índice del lado hijo |
| CHECK | una condición sobre la fila | no puede mirar otras filas |
| NOT NULL | que haya un valor | que no sea una cadena vacía |
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?
Práctica