KriaturasBases de datos para quien empieza

Parte IV · Pulir y proteger

Capítulo 11 — Reglas que se cumplen solas (integridad)

Cierras la Parte III con un modelo de Kriaturas bien diseñado y bien ordenado. Cada dato en su sitio, una sola vez. Has hecho los deberes.

Pero el capítulo anterior terminó con una advertencia. Que tú coloques los datos con cuidado no impide que otro los meta mal. Un compañero de equipo despistado, un formulario sin revisar, un bug del juego que escribe en la base de datos cuando no debe. Cualquiera de ellos puede colar una carta con coste negativo, dos jugadores con el mismo correo o una partida cuyo ganador no jugó.

Hasta ahora, varias reglas del juego dependían de tu disciplina. "El coste nunca es negativo." "Cada amistad se guarda una sola vez, con jugador_a < jugador_b." "El ganador tiene que ser uno de los dos contendientes." Son reglas que tú sabes, pero que la base de datos no vigila. Confías en que nadie las rompa.

En este capítulo vas a dejar de confiar. Vas a enseñarle esas reglas a la propia base de datos para que las haga cumplir sola, pase lo que pase en la aplicación. Eso se llama integridad, y es la red de seguridad de tus datos. Con esto abres la Parte IV: pulir y proteger.


Las cuatro leyes que toda base de datos respeta

Antes de escribir una sola línea de SQL, conviene conocer las cuatro reglas de fondo. Son las reglas de integridad del modelo relacional: las leyes que cualquier base de datos relacional respeta para que sus datos tengan sentido. Algunas ya las has usado sin nombrarlas. Aquí las juntamos todas.

1. Integridad de unicidad (la clave primaria no se repite). Dos filas no pueden tener la misma clave primaria. Dos cartas no pueden compartir el id 1. Si pudieran, volverías al caos del capítulo 2: pedirías "la carta 1" y la base de datos no sabría cuál darte. La clave primaria identifica, y para identificar tiene que ser única.

2. Integridad de entidad (la clave primaria no es nula). Los datos que forman la clave primaria no pueden quedar vacíos. Una carta sin id no se puede identificar; es como un jugador sin nombre en una lista de espera. Si la clave es el dato que señala a cada fila, no puede faltar.

3. Integridad referencial (las claves foráneas apuntan a algo real). Esta ya la conoces del capítulo 4. Una clave foránea (foreign key) debe apuntar a una fila que exista, o quedarse vacía. Un mazo no puede pertenecer al jugador 99 si ese jugador no está en la tabla. La base de datos no permite mazos huérfanos. Aquí solo le ponemos nombre formal a lo que ya hacías.

4. Integridad de dominio (cada valor es del tipo correcto). Cada columna tiene un dominio: el conjunto de valores que admite. El coste admite números; la rareza admite "común", "rara" o "épica", y nada más. Un valor fuera de su dominio no entra. Esta es la que más vas a trabajar en este capítulo, porque es la que tú defines a medida.

Fíjate en el patrón. Las dos primeras hablan de la clave primaria. La tercera, de las claves foráneas. La cuarta, de los valores de cada columna. Entre las cuatro, blindan los tres sitios donde un dato puede ir mal. Ahora vamos a escribirlas en SQL.

Cómo se escriben las reglas: las restricciones

Una restricción (constraint) es una regla que añades a una columna o a una tabla, y que la base de datos hace cumplir en cada escritura. Si una orden la incumple, la base de datos la rechaza y no guarda nada. Vamos una por una, con su ejemplo de Kriaturas.

NOT NULL — esta columna no puede quedar vacía

Algunos datos son obligatorios. Una carta tiene que tener nombre; no tiene sentido una carta sin nombre. Para exigirlo, marcas la columna con NOT NULL:

SQL
nombre VARCHAR(40) NOT NULL

A partir de ahí, cualquier intento de guardar una carta sin nombre se rechaza. (Una columna sin esta marca admite quedarse vacía; ese "vacío" se llama NULL, y significa "aquí no hay valor". NOT NULL lo prohíbe.)

UNIQUE — esta columna no admite repetidos

A veces un dato no es la clave primaria, pero igual no puede repetirse. El correo de un jugador, por ejemplo: dos cuentas no deberían compartir email. Lo declaras con UNIQUE:

SQL
email VARCHAR(100) UNIQUE

Ahora, si alguien intenta registrar un segundo jugador con un correo que ya existe, la base de datos lo frena. A una columna UNIQUE se la llama a veces clave alternativa: identifica también de forma única, aunque no sea la clave oficial.

PRIMARY KEY — ni repetida ni vacía

Esta ya la usas desde el capítulo 2. Ahora puedes verla con otros ojos: PRIMARY KEY es, en el fondo, UNIQUE y NOT NULL a la vez. No se repite (unicidad) y no se queda vacía (entidad). Las dos primeras reglas de integridad, juntas en una sola palabra. Por eso una clave primaria siempre identifica sin fallo.

CHECK — esta columna debe cumplir una condición

Aquí está la restricción más versátil. Con CHECK le pones a una columna una condición que todo valor debe cumplir. ¿El coste de una carta no puede ser negativo? Lo dices así:

SQL
coste INTEGER CHECK (coste >= 0)

A partir de ahí, intentar guardar una carta con coste = -5 se rechaza. La regla del juego "el coste nunca es negativo" deja de depender de tu memoria: la vigila la base de datos.

CHECK también sirve para limitar un valor a una lista cerrada. La rareza solo puede ser una de tres:

SQL
rareza VARCHAR(15) CHECK (rareza IN ('común', 'rara', 'épica'))

Cualquier rareza distinta —"legendaria", "comun" sin tilde, un dedazo— se rechaza. Así defines a mano el dominio de la columna: la cuarta regla de integridad, escrita por ti.

REFERENCES — esta columna apunta a otra tabla

Es la clave foránea del capítulo 4. Declara que una columna apunta a la clave primaria de otra tabla, y activa la integridad referencial:

SQL
jugador_id INTEGER REFERENCES jugador(id)

La base de datos no dejará que mazo.jugador_id valga 99 si no existe el jugador 99. Es la misma regla de siempre, ahora en el mismo saco que las demás.

Ponles nombre a tus reglas. Puedes bautizar una restricción con CONSTRAINT nombre …. Por ejemplo, CONSTRAINT coste_no_negativo CHECK (coste >= 0). No cambia lo que hace, pero cuando la regla salte, el mensaje de error dirá coste_no_negativo en vez de un código críptico, y sabrás al instante qué pasó. En proyectos serios, nombrar las restricciones se agradece muchísimo.

Reescribiendo la tabla `carta` con todas sus reglas

Junta todo lo anterior. Recuerda que desde el capítulo 7 la carta es la superclase de la jerarquía: además de los datos básicos lleva una columna tipo que dice si es criatura, entrenamiento u objeto. Pero hasta ahora la hemos creado sin una sola regla de integridad, confiada:

SQL
CREATE TABLE carta (
    id     INTEGER      PRIMARY KEY,
    nombre VARCHAR(40),
    rareza VARCHAR(15),
    coste  INTEGER,
    tipo   VARCHAR(15)
);

Y así queda ahora, con la integridad puesta:

SQL
CREATE TABLE carta (
    id     INTEGER      PRIMARY KEY,
    nombre VARCHAR(40)  NOT NULL,
    rareza VARCHAR(15)  CHECK (rareza IN ('común', 'rara', 'épica')),
    coste  INTEGER      NOT NULL CHECK (coste >= 0),
    tipo   VARCHAR(15)  CHECK (tipo IN ('criatura', 'entrenamiento', 'objeto'))
);

Léela como una lista de promesas que la base de datos te hace cumplir: toda carta tiene id único, tiene nombre (NOT NULL), su rareza es una de las tres permitidas, su coste es un número que nunca baja de cero y su tipo es una de las tres familias de la jerarquía. Cinco reglas del juego, convertidas en cinco reglas de datos. Ya no dependen de que nadie se acuerde de ellas.

La misma idea vale para jugador. Su correo no puede repetirse y su alias es obligatorio:

SQL
CREATE TABLE jugador (
    id         INTEGER      PRIMARY KEY,
    alias      VARCHAR(20)  NOT NULL,
    email      VARCHAR(100) NOT NULL UNIQUE,
    fecha_alta DATE
);

Fíjate en email: lleva NOT NULL y UNIQUE. Obligatorio y sin repetidos. Una columna puede acumular varias restricciones; se aplican todas a la vez.

Recogiendo los hilos sueltos: `partida` y `amistad`

Quedaron dos reglas pendientes en capítulos anteriores que dependían de tu disciplina. Es el momento de dejarlas en manos de la base de datos.

El ganador tiene que haber jugado (hilo del capítulo 8)

En la tabla partida guardabas jugador1, jugador2 y ganador, los tres apuntando a jugador. Avisamos entonces de un peligro: nada impedía poner como ganador a un jugador que no jugó esa partida. La integridad referencial garantiza que el ganador existe, pero no que sea uno de los dos contendientes.

Eso es exactamente trabajo para un CHECK. La regla "el ganador es jugador1 o jugador2" se escribe comparando columnas de la misma fila:

SQL
CREATE TABLE partida (
    id        INTEGER  PRIMARY KEY,
    jugador1  INTEGER  NOT NULL REFERENCES jugador(id),
    jugador2  INTEGER  NOT NULL REFERENCES jugador(id),
    ganador   INTEGER  REFERENCES jugador(id),
    fecha     DATE     NOT NULL,
    duracion  INTEGER  CHECK (duracion > 0),
    CONSTRAINT ganador_valido CHECK (ganador = jugador1 OR ganador = jugador2)
);

Tres cosas que mirar. El ganador no lleva NOT NULL a propósito: una partida puede quedar en empate o sin terminar, y entonces ganador se queda vacío. La duracion lleva un CHECK propio (una partida no dura cero minutos ni menos). Y la estrella es la última línea: ganador_valido, una restricción de tabla (no de una sola columna, porque compara tres) que obliga al ganador a ser uno de los dos que jugaron. La regla que antes vigilabas tú, ahora la vigila la base de datos.

Cada amistad, una sola vez (hilo del capítulo 9)

En el capítulo 9 acordaste una convención para la tabla amistad: guardar cada pareja con jugador_a < jugador_b, para no registrar la misma amistad dos veces (A-B y B-A). Era "pensar el dato antes de guardarlo". Pero era una convención de papel: nada impedía saltársela.

Ahora la blindas con un CHECK:

SQL
CREATE TABLE amistad (
    jugador_a INTEGER NOT NULL REFERENCES jugador(id),
    jugador_b INTEGER NOT NULL REFERENCES jugador(id),
    fecha     DATE,
    PRIMARY KEY (jugador_a, jugador_b),
    CONSTRAINT orden_amistad CHECK (jugador_a < jugador_b)
);

La restricción orden_amistad rechaza cualquier intento de guardar la pareja al revés (o de hacer a alguien amigo de sí mismo, porque 1 < 1 es falso). La clave primaria compuesta (jugador_a, jugador_b) ya evitaba duplicar la misma pareja exacta; el CHECK evita que entre disfrazada con el orden cambiado. Entre las dos, la simetría de la amistad queda garantizada por la propia base de datos. Tu convención dejó de ser una promesa y pasó a ser una ley.

Resumen

En este capítulo dejaste de confiar en que nadie rompa las reglas y se las enseñaste a la base de datos. Conociste las cuatro reglas de integridad del modelo relacional —unicidad, entidad, referencial y dominio— y aprendiste a escribirlas como restricciones en SQL: NOT NULL para lo obligatorio, UNIQUE para lo que no se repite, PRIMARY KEY que combina ambas, CHECK para condiciones a medida y REFERENCES para las claves foráneas.

Con ellas reforzaste carta, jugador, partida y amistad, y de paso recogiste dos hilos que arrastrabas: el ganador válido del capítulo 8 (un CHECK que lo obliga a ser uno de los dos contendientes) y la simetría de la amistad del capítulo 9 (un CHECK que imp