KriaturasBases de datos para quien empieza

Parte III · Diseñar bien

Capítulo 10 — Ordenar la casa (normalización sin dolor)

En el capítulo anterior cerraste algo grande: con la amistad y el intercambio, el modelo de Kriaturas quedó completo. Tienes jugadores, cartas, mazos, colecciones, partidas, amistades e intercambios. Las siete piezas de diseño, puestas. El plano entero de un juego de cartas.

Pero al final de aquel capítulo dejé caer una pregunta incómoda: que esté completo no significa que esté bien hecho. Y es la hora de mirarlo de frente.

En este capítulo te vas a poner el chaleco de inspector. Vas a recorrer las tablas que ya tienes buscando un enemigo concreto: datos repetidos. Esos que parecen inofensivos pero que, el día menos pensado, hacen que el juego empiece a mentir. Aprenderás a reconocerlos, a entender por qué dan problemas y a colocar cada dato en su sitio. Esa tarea de "ordenar la casa" tiene un nombre técnico, normalización, y un final feliz: cuando termines, vas a descubrir que buena parte de tu diseño ya iba bien encaminado sin saberlo.


El enemigo: la redundancia y las anomalías

Volvamos al término que ya asomó en el capítulo 1 y que ahora toca de lleno: redundancia. Es el mismo dato guardado en varios sitios sin necesidad. No es que repetir un dato esté "prohibido"; es que cuando lo haces sin querer, te metes en líos. Esos líos tienen nombre: anomalías. Son los problemas que aparecen al crear, borrar o cambiar datos en una tabla mal diseñada.

Para verlas, vamos a hacer trampa a propósito. Imagina que, en lugar de tu tabla mazo_carta limpia del capítulo 5, alguien hubiera diseñado así la lista de cartas que lleva cada mazo:

mazo_id | nombre_mazo     | carta_id | nombre_carta | coste | cantidad
--------+-----------------+----------+--------------+-------+---------
   1    | Mazo eléctrico  |    1     | Chispín      |   2   |    2
   1    | Mazo eléctrico  |    2     | Flamita      |   2   |    2
   4    | Mazo control    |    6     | Poción       |   1   |    1

A primera vista parece hasta cómoda: lo tienes todo a la vista, sin necesidad de mirar otras tablas. Pero esa comodidad se paga cara. Mira los tres desastres que esconde.

Anomalía de modificación

Si "Chispín" pasa a costar 3 en vez de 2, ¿en cuántas filas tienes que cambiarlo? En todas las que aparezca esa carta, en cualquier mazo del juego. Decenas, puede que cientos. Si te olvidas de una, esa fila dirá que Chispín cuesta 2 mientras el resto dice 3. La base de datos se contradice a sí misma. Eso es una anomalía de modificación: un cambio sencillo te obliga a tocar muchas filas, y basta fallar una para romper la coherencia.

Anomalía de inserción

Acabas de diseñar una carta nueva, "Trueno", y quieres registrarla. Pero en esta tabla cada fila necesita un mazo_id… La carta no existe en ningún mazo todavía. No puedes guardarla hasta que alguien la meta en un mazo. Absurdo, ¿no? El dato "existe la carta Trueno" no debería depender de que esté en una baraja. Eso es una anomalía de inserción: no puedes registrar una cosa sin tener otra a la fuerza.

Anomalía de borrado

El "Mazo control" se queda con una sola carta, la Poción, y un jugador lo borra. Al desaparecer esa fila, ¿qué más se va con ella? La única información que tenías de que la carta Poción cuesta 1 y se llama así. Estaba ahí, "de paso", colgada de un mazo. Borras el mazo y, sin querer, borras lo que sabías de una carta. Eso es una anomalía de borrado: pierdes información que no querías perder porque estaba enganchada a otra cosa.

La señal de alarma es siempre la misma: si tienes que actualizar el mismo dato en muchos sitios, tu diseño te está pidiendo a gritos que lo normalices. No es manía de perfeccionista. Es evitarte el bug.

La idea que lo explica todo: la dependencia funcional

Antes de las reglas, una sola idea. Y es muy fácil.

Hay datos que determinan otros. Si te digo el id de una carta, tú ya puedes decirme su nombre y su coste sin dudar. El id manda sobre el nombre y el coste. Decimos que el nombre y el coste dependen del id de la carta.

Eso es una dependencia funcional: cuando conocer un dato te da otro de forma segura. Se escribe con una flecha, así:

{carta_id}  →  {nombre, coste}

Que se lee: "el carta_id determina el nombre y el coste". Dale la vuelta para comprobar que tiene sentido al revés: ¿el nombre determina el id? No del todo, porque podría haber dos cartas que se llamen igual. Por eso el id es la clave: él manda sobre los demás, no al contrario.

No necesitas nada más. Olvídate de fórmulas y de teoría de conjuntos. Toda la normalización es, en el fondo, colocar cada dato junto a aquello de lo que depende de verdad. Las tres reglas que vienen ahora solo son tres formas de mirar esa misma idea.

Las tres formas normales

Las reglas para ordenar una base de datos se llaman formas normales, y están numeradas. Hay unas cuantas, pero en la práctica del día a día, llegar hasta la tercera te deja la casa más que ordenada. Vamos una por una, cada una con su regla en una frase y su ejemplo de Kriaturas.

1FN — un solo valor por celda

La primera forma normal (1FN) pide algo básico: en cada celda guardas un único valor, no una lista.

¿Cómo sería romperla? Así de fácil. Imagina guardar las cartas de un mazo metiendo todos los nombres juntos en una sola casilla:

mazo_id | nombre_mazo    | cartas
--------+----------------+-----------------------------
   1    | Mazo eléctrico | Chispín, Flamita, Chispín

Parece práctico, pero es una trampa. ¿Cómo cuentas cuántas cartas distintas hay? ¿Cómo buscas todos los mazos que llevan Flamita? Tendrías que partir el texto por las comas y rezar para que nadie haya escrito un nombre con una coma dentro. La casilla cartas es una lista disfrazada de dato.

La solución ya la conoces, aunque no supieras que tenía nombre: una fila por cada valor. Es exactamente lo que hiciste en el capítulo 5 con la tabla mazo_carta:

mazo_id | carta_id | cantidad
--------+----------+---------
   1    |    1     |    2
   1    |    2     |    2

Cada carta del mazo, su propia fila. Eso es estar en 1FN. Regla práctica: nada de listas dentro de una celda. Y fíjate en el detalle bonito: tu diseño del capítulo 5 ya cumplía la 1FN. Lo hiciste bien por intuición; ahora sabes por qué.

2FN — depender de la clave entera

La segunda forma normal (2FN) solo entra en juego cuando la clave primaria es compuesta, es decir, formada por varias columnas. ¿Te suena? Es justo el caso de coleccion y de mazo_carta, cuya clave es la pareja (jugador_id, carta_id) o (mazo_id, carta_id).

La regla dice: estando ya en 1FN, cada dato que no sea parte de la clave debe depender de la clave entera, no de un trocito de ella.

Veámoslo con la tabla mazo_carta echada a perder a propósito, metiéndole el nombre de la carta:

mazo_id | carta_id | nombre_carta | cantidad
--------+----------+--------------+---------
   1    |    1     | Chispín      |    2
   1    |    2     | Flamita      |    2

La clave es la pareja (mazo_id, carta_id). Pregúntate de qué depende cada columna que no es clave:

  • cantidad: ¿de qué depende? De la pareja entera. Cuántas copias de esta carta hay en este mazo necesita saber las dos cosas. Correcto.
  • nombre_carta: ¿de qué depende? Solo del carta_id. El nombre de la carta es el mismo esté en el mazo que esté. No le hace ninguna falta el mazo_id.

Ahí está el problema. nombre_carta depende solo de una parte de la clave, no de toda. Y eso rompe la 2FN. Por eso aparecía repetido en cada mazo que llevara Chispín: el clásico foco de redundancia.

La solución es la que ya tenías: el nombre de la carta vive en la tabla carta, donde su clave es carta_id a secas. En mazo_carta solo queda lo que depende de la pareja completa: la cantidad. Regla práctica: si un dato depende solo de parte de la clave, sácalo a la tabla donde esa parte sea la clave entera.

Cuando tu clave primaria es una sola columna (un id), la 2FN se cumple sola: no hay "partes" de la clave de las que algo pueda depender mal. Por eso esta regla solo te preocupa en las tablas intermedias, las de clave compuesta.

3FN — sin rodeos por en medio

La tercera forma normal (3FN) cierra el círculo. Estando ya en 2FN, ningún dato que no sea clave debe depender de otro dato que tampoco es clave. Dicho de otro modo: nada de dependencias "indirectas", que pasan por un intermediario.

Aquí Kriaturas nos da el ejemplo perfecto, y enlaza con algo que ya tienes entre manos: el rango del jugador. En el capítulo 8 quedó como atributo derivado (se calcula contando victorias). Pero imagina que, además, decides guardar en la tabla jugador la recompensa que da cada rango:

id | alias        | rango   | recompensa_rango
---+--------------+---------+------------------
 1 | DragoFuego99 | Oro     | 500 monedas
 2 | LunaVerde    | Plata   | 200 monedas
 3 | PixelPunk    | Bronce  | 100 monedas
 4 | ToxiRana     | Plata   | 200 monedas

¿Ves el rodeo? La recompensa_rango no depende del jugador. Depende del rango. Todos los jugadores de plata cobran 200 monedas, sean quienes sean. La cadena es:

{id_jugador}  →  {rango}  →  {recompensa_rango}

El dato pasa por un intermediario (el rango) antes de llegar a la recompensa. Y eso trae la redundancia de siempre: "Plata = 200" está repetido en Luna y en Toxi. El día que la recompensa de plata suba a 250, a correr a cambiar todas las filas de jugadores de plata. La anomalía de modificación, otra vez.

La solución es sacar esa relación a su propia tabla, una tabla de rangos:

rango   | recompensa
--------+-----------
Bronce  | 100 monedas
Plata   | 200 monedas
Oro     | 500 monedas

Ahora "Plata = 200" está escrito una sola vez. La tabla jugador guarda solo el rango; la recompensa de cada rango vive donde le corresponde. Regla práctica: si un dato depende de otro que no es la clave, móntale su propia tabla.

El resumen que cabe en una frase. Hay una forma de recordar las tres reglas de golpe. Cada dato de una tabla debe depender de la clave, toda la clave y nada más que la clave. "La clave" (que haya clave y valores atómicos, 1FN). "Toda la clave" (no parte de ella, 2FN). "Nada más que la clave" (no de otro dato que no sea clave, 3FN). Si te aprendes esa frase, te llevas el capítulo entero en el bolsillo.

La buena noticia: tu modelo ya iba bien

Aquí está la recompensa de haber diseñado con cuidado desde el principio. Repasa mentalmente las tablas de Kriaturas tal como las construiste:

  • jugador y carta (capítulo 2): cada una con su id como clave única, un valor por celda, y solo datos que dependen de ese id. Normalizadas.
  • mazo (capítulo 4): id, nombre, jugador_id. Cada dato depende del mazo. Normalizada.
  • coleccion y mazo_carta (capítulo 5): clave compuesta, un valor por celda, y el único dato no clave (cantidad) depende de la pareja entera. Normalizadas.
  • partida, amistad, intercambio (capítulos 8 y 9): cada hecho en su sitio, sin datos colgados de otras cosas. Normalizadas.

No es casualidad. Cuando decidiste, en el capítulo 5, que el nombre de la carta viviera en carta y no dentro de mazo_carta, estabas aplicando la 2FN sin llamarla por su nombre. Cuando en el capítulo 8 dejaste el rango como derivado en lugar de empotrarlo en la tabla, esquivaste un problema de 3FN. El método de "una tabla por entidad, un dato en su sitio" que arrastras desde el capítulo 2 es, básicamente, normalizar por instinto.

La normalización no es, entonces, una operación rara que se hace una vez al final. Es una forma de mirar el diseño que ahora ya tienes incorporada. Las formas normales solo te dan el vocabulario para explicar por qué un diseño está bien o mal, y para detectar el fallo cuando se cuela.

Resumen

En este capítulo te hiciste inspector de tu propio diseño. Aprendiste que la redundancia —el mismo dato repetido— provoca anomalías al insertar, borrar y modificar, y que la normalización las evita guardando cada dato una sola vez. La herramienta mental es la dependencia funcional: colocar cada dato junto a aquello de lo que depende de verdad.

Recorriste las tres formas normales: 1FN (un valor por celda, nada de listas), 2FN (depender de toda la clave compuesta) y 3FN (sin depender de otro dato no clave). Y todo cabe en una frase: cada dato depende de la clave, toda la clave y nada más que la clave. Lo mejor de todo: al revisar Kriaturas descubriste que tu modelo ya estaba normalizado, porque venías diseñando con buen criterio desde el capítulo 2.

Con esto cierras la Parte III: tu base de datos está bien diseñada y bien ordenada. Lo que viene ahora es blindarla. Porque una cosa es que tú coloques los datos en su sitio… y otra es impedir que alguien, o algún error del programa, meta datos imposibles: un ganador que no jugó la partida, un mazo sin dueño, un coste negativo. Hacer que la propia base de datos vigile esas reglas por ti es lo que viene en el capítulo 11: la integridad.