Normalizar una BD es aplicar una serie de reglas para evitar inconsistencias en las consultas de datos, anomalías de en operaciones de inserción, actualización y borrado, evitar redundancia y proteger integridad de los datos.
Una BD normalizada nos da la suficiente flexibilidad para agregar catálogos, relaciones y tipos de datos sin modificar severamente la estructura.
La normalización nos lleva a través de 7 formas normales, de las cuales solo veremos 3.
En general, una vez que tu BD está en 3a forma normal (3NF), ya está lista para producción.
Algunas de estas reglas ya las hemos visto como buenas prácticas, así que esto no debe ser complicado.
- Anomalías de
insert
: al insertar un registro con columnasestado
ymunicipio
, combinarlos de formas no consistentes con la realidad (i.e. municipio deAtizapán
, estado deZacatecas
) - Anomalías de
update
: al querer actualizar 1 y solo 1 registro, accidentalmente actualizar varios debido a llaves inconsistentes. - Anomalías de
delete
: al querer borrar 1 y solo 1 registro, accidentalmente chutarnos varios debido a llaves inconsistentes.
Hay diferentes formas de expresar las reglas, pero en favor de ser concretos y no confusos:
- Cada celda de la tabla debe contener 1 y solo 1 valor
- Cada registro debe ser único
- Eliminar nombres de atributos, o grupos de atributos repetidos
Supongamos que queremos agregar la tabla servicio
al esquema hospital
. La tabla servicio es mediante la cual se construirá la factura de nuestro paciente al momento de saldar su cuenta.
Si, ya se que la salud y la seguridad deben ser responsabilidad del estado, pero síganme solo para efectos de ejemplo.
Esta tabla es un asco. No sigue ninguna de las mejores prácticas que hemos acordado. Llevemosla a la 1NF.
Aplicando regla por regla:
- Cada celda de la tabla debe contener 1 y solo 1 valor:
Separamos los valores cada uno en su propio renglón, pero con eso repetimos el nombre del paciente.
- Cada registro debe ser único
Pudimos definir una llave primaria compuesta para identificar cada renglón como único.
- Eliminar nombres de atributos, o grupos de atributos repetidos
En lugar de tener varias columnas de costo para un renglón de servicio con múltiples datos, separamos esas columnas para que a cada servicio le toque su precio.
- Cumplir con las reglas de la 1NF
- Todos los atributos o columnas de una tabla deben pertenecer a la entidad que representa.
- Excepto relaciones N a M, la PK no debe ser compuesta y debe ser de 1 solo atributo.
Veamos como se transforma la tabla servicio
aplicando todas las reglas de la 2NF. Ya cumplimos la 1NF, así que vamos desde la regla 2.
- Todos los atributos o columnas de una tabla deben pertenecer a la entidad que representa.
Un atributo X pertenece a una entidad Y si para saber X podemos depender de la PK de Y. Todo atributo que no pertenezca a la entidad, debe quedar fuera de la tabla. De este modo se descubren otras entidades, sus relaciones, y algunos catálogos.
Como servicio
es una entidad separada de paciente
, hacemos tablas independientes para cada una. Adicionalmente, como el departamento responsable de proveer el servicio no pertenece al servicio, entonces igual lo extraemos a la tabla departamento_hospital
.
Al hacer esto, hemos eliminado la necesidad de tener nombres de pacientes, de servicios y de departamentos repetidos, lo cual nos permite cumplir con la siguiente regla de la 2NF.
- Excepto relaciones N a M, la PK no debe ser compuesta y debe ser de 1 solo atributo.
Pero las best practices para llaves primarias que hemos visto anteriormente nos recomiendan que:
- no sea un atributo primario de la entidad que la tabla representa
- sea un entero secuencial
Entonces nos quedarían de esta forma:
- Cumplir con las reglas de la 2NF
- No hay dependencias transitivas (no hay atributo A que para llegar a él se requiera del atributo B al que a su vez se llega por la PK)
Después de tener la tabla servicio
en 2NF, nos quedamos con el id
, el nombre
y el costo
del servicio.
Pero realmente el costo
depende del id
?
Si suponemos que el costo del servicio está dado por un tabulador
, nuestra tabla de servicio queda del siguiente modo:
Con esto tenemos una dependencia transitiva: el costo
está dado por el tabulador
, y el tabulador
del servicio lo da el id
servicio.
Para cumplir con la 3NF, debemos sacar a otra tabla el atributo que inicia la cadenita de dependencias transitivas: el costo
.
Y ya si nos ponemos exquisitos y aplicamos las best practices:
- Requerir la existencia de una llave primaria automáticamente nos pone en 1NF (la llave)
- Requerir que todos los atributos dependan de toda la llave primaria nos pone en 2NF (toda la llave)
- Requerir que todos los atributos dependan solamente de la llave primaria nos pone en automático en 3NF (nada más que la llave)
Como dijimos anteriormente, al estar diseñando BDs, es importante tomar en cuenta que muchas de las decisiones son subjetivas y sujetas a su criterio. En general, una BD que evite anomalías en operaciones de borrado, actualización e inserción, y que siga las prácticas que hemos visto en clase, quedará en automático en 3NF.
La mayoría de las aplicaciones de la vida real serán suficientes con que tengamos nuestra BD en 3NF. Las NF posteriores garantizan aún mayor cobertura VS anomalías, mayor consistencia en algunos casos frontera, etc.
Del mismo modo, "normalización" se dió cuando el storage era caro, pero ahora en 2021 la razón más importante es estructura, y optimización de escritura.
- Comencemos con la relación N a M de
paciente -> doctor
:
- Luego agreguemos la relación N a M de
paciente_doctor -> medicamento
:
-
Aquí entramos en varios problemas:
- Corremos el riesgo de arrastrar la llave compuesta
id_doctor + id_paciente + id_medicamento
a otras relaciones, y podemos incurrir en repetición de datos - Semánticamente, la relación
paciente -> doctor
es una que materializa el verbo tratar, el cual es diferente del verbo recetar, por lo que debemos manifestarlo con 2 relaciones N a M totalmente diferentes - Esto no nos introduce relaciones circulares porque los viajes que podemos hacer son de N a 1, es decir, los viajes siempre van desde una observación o instancia única de una entidad hacia otra observación o instancia única de otra entidad, por lo que cuando vamos de una instancia única (el lado de la relación con cardinalidad 1) a instancias múltiples (el lado de la relación con cardinalidad N) ahí se detiene nuestro viaje
- Corremos el riesgo de arrastrar la llave compuesta
-
Para poder componer esta relación, vamos a definir por un lado, la relación de médico tratante con la tabla intermedia
paciente -> paciente_doctor <- doctor
por un lado, y por otro lado la relación ternariapaciente -> doctor -> medicamento
:
- 👀OJO👀: con esta alternativa ya no es recomendable TENDER MÁS RELACIONES entre las tablas intermedias (sea de la relación N a M o de la relación N a M a P) y otras tablas, porque estaríamos copiando llaves compuestas más allá de las tablas de soporte. Si absolutamente debemos hacer esto para satisfacer la narrativa del contexto, debemos hacer lo siguiente:
Esto es, reemplazar la llave primaria de la tabla de soporte con un entero secuencial y sin relación a la entidad, mantener las llaves foráneas, y entonces sí establecer las relaciones subsecuentes CON LA NUEVA LLAVE PRIMARIA en lugar de hacerlo con las llaves compuestas.