Diseño de bases de datos para todos los públicos

Anuncio
Diseño de bases de datos
para todos los públicos
Fernando Cano Espinosa
Juan David González Cobas
Mayo de 2003
1. Conceptos previos
Antes de introducirnos en los problemas de diseño de bases de datos relacionales se hace necesario estudiar algunos conceptos básicos asociados fundamentalmente a las dependencias funcionales (DF).
Vamos a definir una DF α → β, siendo α y β conjunto de atributos (columnas de una tabla), como una restricción que nos dice que cada valor concreto de
los atributos α está asociado a un valor concreto de los atributos β. Es decir que
si dos filas de una relación comparten el mismo valor para α entonces deben
tener el mismo valor para β:
t1 [α] = t2 [α] =⇒ t1 [β] = t2 [β]
(1)
Para ilustrar el concepto de DF supongamos que en una tabla tenemos una
columna Profesor y otra Asignatura, entonces P rof esor → Asignatura nos dice
que para un determinado Profesor sólo puede existir una Asignatura, en lenguaje más natural: un profesor sólo imparte una asignatura. O como decíamos
antes, que si en dos filas aparece el nombre de un Profesor debe aparecer la
misma asignatura.
Con las DF podemos expresar cierto tipo de restricciones, no todas, que nos
van a permitir estudiar la bondad de las tablas que contiene nuestra base de
datos.
Definamos L como el conjunto de restricciones que deben cumplir nuestros
datos (columnas) expresadas mediante DFs. Casi siempre podremos deducir
nuevas restricciones partiendo de un conjunto L inicial. Por ejemplo, si un código postal determina la localidad (CP → LOC) y la localidad determina la
provincia (LOC → P ROV ) entonces podremos deducir por transitividad que
un código postal sólo pertenece a una provincia (CP → P ROV ). Existen algoritmos (Anexo II) que nos permiten calcular el conjunto total de DFs que
afectan a nuestro conjunto de datos. A dicho conjunto se le denomina cierre del
conjunto de dependencias y se expresa como L+ . Lógicamente en L+ existirán
1
Diseño de bases de datos
muchas DF redundantes (CP → P ROV ) y triviales (CP LOC → CP ), pero
también contamos con un algoritmo (Anexo III) que nos permite calcular un
conjunto minimal de DF. A estos conjuntos de DF donde ’no sobra nada’ se le
denomina Recubrimiento canónico o Recubrimiento minimal.
Por último vamos a definir el cierre de un conjunto de atributos, expresado
como α+ como el conjunto de todos los atributos que dependen funcionalmente de α. Evidentemente también existe un algoritmo sencillo para calcularlo
(Anexo I).
Todos los algoritmos anteriormente mencionados pueden consultarse en
cualquier libro clásico de fundamentos de bases de datos o de manera informal
en los anexos, pero para el desarrollo de este tema el realmente útil es el cierre
de un conjunto de atributos.
2. Un pequeño ejemplo
Vamos a plantearnos un sencillo ejemplo para ilustrar los problemas que
suelen presentarse a la hora de diseñar una base de datos relacional. Nuestro
objetivo será definir un conjunto de tablas que nos permita almacenar, modificar y recuperar toda nuestra información de forma sencilla, partiendo de
una serie de restricciones. Nuestro ejemplo consiste en una base de datos que
contendrá información sobre alumnos, asignaturas, notas y profesores. Las restricciones de las que partimos son las siguientes:
Un alumno sólo tiene asignado un profesor por asignatura
Un alumno obtiene una única nota por asignatura
Un profesor sólo puede dar clase de una asignatura
Podríamos plantearnos mantener todos los datos en una única tabla. Utilizaremos el siguiente ejemplo:
Alumno
Luis
Luis
Carlos
Marta
Pepe
Asignatura
Física
Filosofía
Física
Física
Filosofía
Profesor
Newton
Kant
Einstein
Newton
Kant
Nota
5
7
6
5
4
Llamaremos a este esquema R(T, L), para poner de manifiesto que estará compuesto por el conjunto de atributos T y el conjunto de restricciones L
expresadas como dependencias funcionales.
R(T, L) : T =
L=
{Alumno, Asignatura, Profesor, Nota}
{Alumno Asignatura → Profesor,
Alumno Asignatura → Nota,
Profesor → Asignatura}
Revisión 3 de mayo de 2007
Diseño de bases de datos
Del conjunto de restricciones podemos deducir que el par (Alumno, Asignatura) funciona como clave candidata para el esquema, al igual que (Alumno,
Profesor), ya que podemos deducir que
Alumno Profesor → Asignatura ∈
Alumno Profesor → Nota ∈
L+
L+
De momento vamos a definir la tabla con (Alumno, Asignatura) como clave
primaria, mediante la orden SQL:
create table R (
alumno
asignatura
profesor
nota
primary key
);
varchar(15) not null,
varchar(15) not null,
varchar(15) not null,
integer,
(alumno, asignatura)
3. Anomalías de actualización
Si observamos nuestra tabla, podemos ver que nuestra decisión de mantener en ella toda la información conduce a varios inconvenientes.
Repetición de información Dado que un profesor sólo imparte clases de una
única asignatura, se produce una redundancia de datos. En nuestro ejemplo, la mención de que Marta cursa Física es redundante, puesto que sabemos que su profesor, Newton, sólo da clase de Física. Lo mismo ocurre
con la asignatura de Filosofía que cursa Pepe.
En la actualidad, dadas las altas prestaciones de los ordenadores y el precio del byte de Ram y de disco, el problema fundamental de la repetición
de información no es el desperdicio de espacio sino otros problemas derivados del mantenimiento de información repetida, que enumeramos a
continuación.
Anomalías en la inserción y en la actualización Al insertar una fila nueva (en
SQL, INSERT) o actualizar una ya existente (en SQL, UPDATE), tendremos que comprobar que la información que introducimos es consistente. Por ejemplo, si quiero insertar un alumno Dalmacio cuyo profesor es
Newton, debo comprobar que cursa Física; de no hacerlo así, sería posible
violar la dependencia funcional Profesor→Asignatura, es decir, permitir
que Newton imparta más de una asignatura.
Anomalías en el borrado El problema que se nos presenta aquí no es un problema de inconsistencia, sino de pérdida de información. Si, por ejemplo,
borramos la única fila en la que aparece Einstein, desaparece la información de que éste es profesor de Física.
Revisión 3 de mayo de 2007
Diseño de bases de datos
Representación de Información Este problema está íntimamente ligado al anterior y se basa en que nuestro esquema no nos permite guardar algunos
hechos. Por ejemplo, aunque sepamos qué profesores dan cada asignatura, no puedo guardar dicha información hasta que no tenga alumnos
matriculados en ella. La opción de dejar valores nulos no es válida generalmente, ya que algunos campos que deberían quedar en blanco (no
tenemos información de ellos) están declarados como not null por formar parte de la clave primaria, por lo tanto el sistema no me permite
dejarlos ’en blanco’.
Imposición de las restricciones Imponer la restricción de que un alumno sólo tiene un profesor por asignatura resulta tan sencillo como el declarar
(Alumno, Asignatura) como clave primaria. Sin embargo, garantizar el
cumplimiento de las dependencias funcionales cuya parte izquierda no
es clave candidata no es así de sencillo.
Por ejemplo, comprobar la dependencia Profesor→Asignatura requeriría
establecer en SQL un check como este:
alter table R add constraint PRO_ASIG
check (not exists (
select profesor
from
R
group by profesor
having count(distinct asignatura) > 1));
Otra solución podría ser evitar que se inserten o actualicen tuplas que
contradigan la restricción mediante triggers. En cualquier caso, nos vemos obligados a agregar más código, lo que es posiblemente innecesario
y ralentiza la ejecución de nuestras modificaciones a la base de datos.
Ante este tipo de anomalías, una solución consiste en descomponer nuestro esquema original en varios subesquemas de forma que dichas anomalías
se eviten. Este proceso se conoce como normalización. No siempre es posible
encontrar una descomposición que elimine todas las posibles anomalías. En algunos casos, la normalización resuelve ciertos problemas, pero introduce otros.
Revisión 3 de mayo de 2007
Diseño de bases de datos
4. Descomposiciones
Vamos a comenzar definiendo formalmente lo que es una descomposición.
Sea R un esquema de relación. Un conjunto {R1 , R2 , . . . , Rn } de esquemas de
relación es una descomposición de R si
n
[
Ri = R
(2)
i=1
es decir, cada atributo de R aparece por lo menos en uno de los Ri . En la práctica, cuando descomponemos un esquema en un conjunto de subesquemas,
incluimos en éstos atributos comunes. El objetivo de esto es poder recuperar
posteriormente la información que se ha guardado en distintas tablas a través
de operaciones de join.
Vamos a ver cómo una descomposición puede resolver algunos de los problemas mencionados en la sección ??. La propuesta es dividir nuestro esquema R(T, L) en dos subesquemas R1 (T1 , L1 ) y R2 (T2 , L2 ).
R2
R1
Alumno
Luis
Luis
Carlos
Marta
Pepe
Asignatura
Física
Filosofía
Física
Física
Filosofía
Nota
5
7
6
5
4
Profesor
Newton
Kant
Einstein
Asignatura
Física
Filosofía
Física
Es evidente que esta descomposición nos resuelve algunos problemas: se
repite menos información; a un profesor sólo se le puede asignar una asignatura (profesor es la clave primaria de R2 ); y podemos guardar información de
la asignatura que imparte cada profesor aunque no tengamos alumnos matriculados.
Pero nos aparece un problema nuevo: ya no sabemos qué profesor tiene cada alumno en una determinada asignatura. Ante la pregunta de quién da clase
de Física a Luis, no podemos saber si es Newton o Einstein con la información
que las tablas R1 y R2 nos proporcionan. Nos pasa igual con Marta, aunque
no con los alumnos de Filosofía, ya que únicamente tenemos al profesor Kant
impartiéndola.
Este problema se conoce con el nombre de descomposición con pérdida de información, generalmente denominado como lossy-join. Cuando esta situación
no se da, y toda la información que existía previamente puede recuperarse
después de la descomposición, decimos que se cumple la propiedad losslessjoin o LJ (ciertamente, la “L” podría ser abreviatura tanto de “lossy” como de
“lossless”, pero la vida es así de dura a veces).
La aparición de la temible palabra join tiene que ver con la definición formal
de la propiedad. Cuando la descomposición es con pérdida, al hacer la reunión
Revisión 3 de mayo de 2007
Diseño de bases de datos
de los subesquemas no obtenemos el esquema original. En nuestro ejemplo,
para esta reunión la consulta apropiada sería:
select
from
r1.alumno, r1.asignatura,
r2.profesor, r1.nota
r1 inner join r2
on r1.asignatura = r2.asignatura
Y el resultado sería:
R1 .Alumno
Luis
Luis
Luis
Carlos
Carlos
Marta
Marta
Pepe
R1 .Asignatura
Física
Física
Filosofía
Física
Física
Física
Física
Filosofía
R2 .Profesor
Newton
Einstein
Kant
Newton
Einstein
Newton
Einstein
Kant
R1 .Nota
5
5
7
6
6
5
5
4
Y evidentemente nos aparecen tres filas más (en sombreado) que en la tabla
R original. El problema radica en el hecho de que hay más de un profesor de
Física, con lo que al reunir (realizar el producto natural) R1 y R2 , a cada alumno
de Física se le empareja con todos los profesores de Física, dando lugar a filas
espurias que contienen información incorrecta.
Revisión 3 de mayo de 2007
Diseño de bases de datos
5. Descomposición con la propiedad LJ
(Lossless-Join)
Para formalizar lo anterior vamos a retomar el concepto formal de descomposición. Como anteriormente, sea R un esquema de una relación y consideremos una descomposición {R1 , R2 , . . . , Rn } (en el sentido de la definición ??).
Sea r una instancia de la relación (los valores actuales del conjunto de filas
que contiene la tabla) con esquema R; definimos las instancias ri de Ri como
ri = Ri (r) = πTi (r). De esta forma, {R1 , R2 , . . . , Rn } es la base de datos que
resulta de descomponer R en sus proyecciones sobre los conjuntos de atributos
{T1 , T2 , . . . , Tn }.
Pues bien, puede demostrarse que siempre se da la siguiente inclusión:
n
⊲⊳ ri ⊇ r
i=1
(3)
es decir, las tuplas de la relación original siempre se recuperan realizando una
reunión natural de las proyecciones ri . Desgraciadamente, también pueden
aparecer otras nuevas.
Se puede ver que en nuestro caso se cumple esta afirmación, ya que el conjunto de filas original es un subconjunto del obtenido tras de realizar el producto natural de r1 y r2 . A estas alturas está claro que nuestro objetivo es realizar una descomposición en la que ambos conjuntos sean iguales, es decir, que
cumpla la propiedad LJ.
Decimos que una descomposición {R1 , R2 , . . . , Rn } de un esquema R es sin
pérdidas (lossless) cuando se verifica la identidad
n
⊲⊳ ri = r
i=1
(4)
es decir, la relación original se recupera siempre realizando la reunión de las
relaciones proyección que resultan de la descomposición.
Para conseguir esto en nuestro ejemplo, sería necesario que al cruzar una
fila de la tabla R1 sólo obtuviéramos una fila de la tabla R2 que cumpliera la
condición
R1 .Asignatura = R2 .Asignatura
o dicho de otra forma, que cada asignatura de la tabla R2 sólo apareciera una
vez. Esto también se puede formalizar: el hecho se conoce como
Teorema 1. (Heath) Dado un esquema R(T, L), la descomposición R1 (R), R2 (R)
es una descomposición sin pérdida (propiedad LJ) si y sólo si alguna de las siguientes
dependencias funcionales se pueden deducir de L (pertenecen a L+ ):
R1 ∩ R2
R1 ∩ R2
→ R1 − R2
→ R2 − R1
O lo que es lo mismo:
Revisión 3 de mayo de 2007
R1 ∩ R2
→ R1
R1 ∩ R2
→ R2
Diseño de bases de datos
Estas dependencias funcionales nos viene a decir que los atributos comunes
a las dos tablas funcionan como clave (más exactamente, superclave) en alguna
de ellas.
Aprovechando este formalismo, vamos a dejar enunciado el siguiente teorema sobre descomposiciones con la propiedad LJ:
Teorema 2. Sea el esquema R(T, L) y sea ρ = {R1 , R2 , . . . , Rk } una descomposición
de R con la propiedad LJ respecto de L. Si τ = {S1 , S2 , . . . , Sk } es una descomposición
de Ri con la propiedad LJ respecto de Li (proyección de L+ sobre Ti ), entonces γ =
{R1 , R2 , . . . , Ri − 1, S1, S2, . . . , Sk, Ri + 1, . . . , Er} es una descomposición de R
respecto L con la propiedad LJ.
Siguiendo con nuestro ejemplo, vemos que Asignatura es el atributo común
a ambas tablas y no es clave en ninguna de ellas. Por eso, la descomposición
propuesta resultó ser con pérdidas.
Otra descomposición alternativa que sí cumpliría la propiedad LJ sería la
siguiente:
Alumno
Luis
Luis
Carlos
Marta
Pepe
R1
Profesor
Newton
Kant
Einstein
Newton
Kant
Nota
5
7
6
5
4
R2
Profesor Asignatura
Newton Física
Kant
Filosofía
Einstein Física
La descomposición es sin pérdida, ya que Profesor, el atributo común, es
clave en R2 .
En principio, nuestro problema parece resuelto, pero si miramos con atención aún pueden aparecer algunas complicaciones. Si alguien, por error, introduce una nueva fila en la tabla R1 con los valores (“Luis”, “Einstein”, 4) (puede
hacerlo ya que no se viola la unicidad de la clave primaria), nos encontraremos
con el dilema de si Luis tiene aprobada la asignatura de Física o no, además de
no saber si su profesor es Newton o Einstein:
Alumno
Luis
Luis
Carlos
Marta
Pepe
Luis
R1
Profesor
Newton
Kant
Einstein
Newton
Kant
Einstein
Nota
5
7
6
5
4
4
Profesor
Newton
Kant
Einstein
R2
Asignatura
Física
Filosofía
Física
Lo que sucede en este caso es que no estamos cumpliendo las restricciones
de que un alumno tiene un único profesor y una única nota en cada asignatura
(Alumno Asignatura → P rof esorN ota)
Revisión 3 de mayo de 2007
Diseño de bases de datos
No ocurre lo mismo con la dependencia Profesor→Asignatura, que se exige
al declarar Profesor como clave primaria en R2 .
Este nuevo problema se conoce como descomposición con pérdida de dependencias funcionales.
6. Descomposición sin pérdida de dependencias
Cuando imponemos un conjunto de restricciones mediante dependencias
funcionales debemos ser conscientes de las consecuencias que esto tiene a la
hora de implementar la base de datos. Hacer que se cumplan ciertas restricciones en una tabla es casi siempre costoso. Cuando se trata de DFs que se
traducen en definir claves candidatas (PRIMARY KEY o UNIQUE), la solución
es sencilla desde el punto de vista del programador de la base de datos. Para otras DFs que involucran a atributos de una misma tabla también tenemos
algunas alternativas (por ejemplo mediantes CHECKs). La cuestión tiene mayores inconvenientes cuando en las restricciones intervienen atributos que se
encuentran localizados en distintas tablas. Esto suele realizarse mediante asertos (CREATE ASSERTION) de la base de datos, aunque, si es posible, es mejor evitarlos. No tenemos que olvidar que normalmente hablamos de sistemas
multiusuario y las tablas pueden estar bloqueadas por otro usuario cuando
nosotros necesitamos acceder a ellas para comprobar ciertas restricciones. Lo
que se pretende, por tanto, es que las restricciones se exijan a nivel de tabla,
es decir, que ante inserciones o modificaciones de datos en una tabla no sea
necesario consultar otras. Visto esto formalicemos el problema.
Sea el esquema R(T, L), donde L son las dependencias estipuladas para la relación R. Sea {R1 , R2 , . . . , Rk } la descomposición de R con esquemas
Ri (Ti , Li ), construidos de modo que Li es la proyección de L+ sobre Ti , es decir
Li = (X → Y ) ∈ L+ | (X ∪ Y ) ⊆ Ti
(5)
Por tanto cada Li será el conjunto de restricciones que involucra atributos
únicamente de Ri , es decir, aquellas dependencias funcionales que se pueden
comprobar de forma independiente en Ri .
Hay que tener cuidado con la engañosa simplicidad de la definición anterior. Las dependencias que afectan a cada relación Ri pueden no resultar necesariamente de la proyección de las dependencias originales sobre los atributos Ti de Ri . Por ejemplo, en una relación R(A, B, C) con dependencias dadas
por el recubrimiento L = {A → B, B → C} la proyección sobre R1 (A, C) debe
satisfacer la dependencia A → C, aunque ésta no se obtiene proyectando el
recubrimiento expresado en la definición de R:
π(A,C) (L) = φ
pero
A → C ∈ L1 = π(A,C) (L+ )
de forma que es preciso deducir (un recubrimiento de) todas las dependencias
que pueden afectar solamente a los atributos A y C para conocer L1 .
Revisión 3 de mayo de 2007
Diseño de bases de datos
Consideremos el conjunto L′ = ∪Li , es decir, la reunión de todas las dependencias impuestas a cada esquema Ri . En general L′ ⊆ L, pero puede darse
el caso de que una dependencia del conjunto L pueda deducirse de L′ aun
no apareciendo explícitamente en L′ ; es decir, se encuentre en (L′ )+ . Para que
la descomposición siga imponiendo (a nivel de tabla) las dependencias originales, será entonces preciso que (L′ )+ = L+ . Si esto ocurre se dice que la
descomposición preserva las dependencias.
Traducido a lenguaje menos simbólico, la descomposición preserva dependencias si al imponer las dependencias relativas a cada tabla de la descomposición, todas las originales resultan impuestas también, al poder deducirse de
ellas.
Observemos qué es lo que sucede en nuestro ejemplo.
Esquema de partida:
Atributos:
Dependencias:
Descomposición:
Atributos:
R(T, L)
T = {Alumno, Asignatura, Profesor, Nota}
L = {Alumno Asignatura → Profesor,
Alumno Asignatura → Nota,
Profesor → Asignatura,
Alumno Profesor → Nota}
R1 (T1 , L1 ), R2 (T2 , L2 )
T1 = {Alumno, Profesor, Nota}
T2 = {Profesor, Asignatura}
Dependencias:
L1 = {Alumno Profesor → Nota}
L2 = {Profesor→Asignatura}
En suma:
L′ = {Alumno Profesor→Nota,
Profesor→Asignatura}
Como vemos la dependencia Alumno Asignatura→Profesor no aparece explícitamente en L′ ; igual ocurre con Alumno Asignatura→Nota. Pero, además,
tampoco se pueden deducir de dicho conjunto. Por este motivo, si no establecemos procedimientos adicionales a la hora de realizar actualizaciones en nuestras tablas, nos podemos encontrar con inconsistencias como las mencionadas
en la sección ??.
Existe un mecanismo para comprobar si esto ocurre: cuando dudemos de
si una restricción de la forma X → Y se exige o no, bastará con calcular el
cierre X + de X respecto al conjunto L′ , y si Y aparece en dicho cierre, la dependencia se estará imponiendo.
Aún más, hay un algoritmo que nos facilita este proceso sin la necesidad
de calcular cada Li (cálculo que, como vimos, puede resultar equívoco), y es el
que se presenta a continuación.
El algoritmo se basa en la R-operación sobre el conjunto de atributos Z respecto de L como la sustitución de Z por Z ∪ ((Z ∩ Ti )+ ∩ Ti ). Repitiendo esta
operación para cada uno de los conjuntos de atributos Ti hasta que el conjunto Z no cambie, obtenemos el algoritmo siguiente
Revisión 3 de mayo de 2007
Diseño de bases de datos
Z⇐X
while Z cambie and not Y ∈ Z do
for i = 1 to K do {siendo K el número de subesquemas}
Z = Z ∪ ((Z ∩ Ti )+ ∩ Ti )
end for
end while
if Y 6∈ Z then
Se pierde la dependencia X → Y
else
Se conserva la dependencia X → Y
end if
Aplicando este algoritmo se puede comprobar que nuestra descomposición
de R es una descomposición con pérdida de dependencias.
Revisión 3 de mayo de 2007
Diseño de bases de datos
7. Estudio de Normalización
Llegados a este punto, la pregunta es si existe alguna otra alternativa que
no presente ninguno de los anteriores problemas. La respuesta depende de cada base de datos en concreto, pero en nuestro caso la respuesta es negativa.
No siempre es así, de hecho nuestro ejemplo ha sido elegido para ilustrar estos problemas. En general, el cumplir la propiedad LJ debe ser una exigencia
básica que debe satisfacer cualquier descomposición, pero en el caso de descomposiciones con pérdidas habrá que analizar las ventajas e inconvenientes
de las posibles descomposiciones y de las implicaciones de su implementación
en un sistema real de gestión de bases de datos.
Hasta el momento hemos visto qué tipos de problemas nos puede presentar
una tabla y qué propiedades son deseables que cumpla una descomposición.
Ahora vamos a centrarnos en el estudio de la calidad de un esquema R(T, L),
lo que se conoce como estudio de normalización. Lo que se pretende es dar
una clasificación, de mayor a menor, de la posibilidad de encontrarnos con
problemas a la hora de implementar un esquema (tabla), teniendo en cuenta las
restricciones que se deben cumplir impuestas como dependencias funcionales.
Realmente es un estudio sobre la posibilidad de que nos aparezca redundancia
de información. Cuanto mayor es el grado de repetición de información más
problemas tendremos. Para esto se definen 4 formas normales: Primera Forma
Normal (1FN), 2FN, 3FN y Forma Normal de Boyce-Codd (FNBC), en este orden.
Cuanto más alta sea la forma normal que alcancemos, menor repetición de
información tendremos a priori.
Estas forma normales que vamos a ver están basadas en las Dependencias
Funcionales. Existen otras formas normales superiores (4FN y 5FN) basadas
en otro tipo de dependencias que no vamos a ver aquí. Lo que se pretende es
analizar qué formas normales alcanzan las tablas que conforman nuestra base
de datos.
Antes de entrar a fondo con la normalización es importante recalcar que se
trata de una recomendación general, pero que en bastantes ocasiones es muy
aconsejable no respetarla.
7.1. Primera Forma Normal (1FN)
Un esquema R(T, L) está en 1FN cuando todas sus columnas (atributos de
T ) son simples. Dicho de otra forma, cada columna de una tabla debe estar
definida sobre un tipo simple de datos (entero, real, cadena, etc.) y no un tipo
estructurado (registro, lista, matriz, etc.). Realmente, no es necesario ser tan
exigente; lo que se busca con esta forma normal es que cada atributo se trate
como un valor atómico.
Para ilustrarlo, vamos a suponer que tenemos un tipo de dato estructurado
fecha, con los campos día, mes y año. Si el tratamiento que hago sobre la fecha
es de forma conjunta podríamos decir que estamos cumpliendo la 1FN, pero si
vamos a trabajar con cada uno de lo campos de forma independiente, no.
Revisión 3 de mayo de 2007
Diseño de bases de datos
Por ejemplo, si el sueldo de un trabajador depende funcionalmente del valor fecha.mes (Operario Fecha.Mes → Sueldo) entonces sería más recomendable
tener tres columnas (día, mes y año) en lugar de la columna fecha. Como vemos,
la 1FN es una recomendación, ya que muchos gestores de bases de datos nos
permiten definir tipos de datos estructurados, tales como matrices.
7.2. Segunda forma normal (2FN)
Un esquema R(T, L) en 1FN está en 2FN cuando todos sus atributos no
principales (aquellos que no forman parte de ninguna clave candidata) tienen
dependencia funcional total (completa) respecto de cada una de las claves candidatas.
En algunos textos se habla únicamente de la clave primaria y no del resto
de las claves candidatas. Para ver que esto no es así vamos a utilizar nuestro
ejemplo con una pequeña modificación: vamos a incluir en T el atributo que
nos diga el departamento al que pertenece el profesor. Tomemos el siguiente
esquema:
Esquema:
R(T, L)
Atributos:
T = {Alumno, Asignatura, Profesor, Nota, Departamento }
Dependencias:L = {Alumno Asignatura → Profesor,
Alumno Asignatura → Nota,
Profesor → Asignatura,
Profesor → Departamento}
Tomemos como clave primaria (Alumno Asignatura), de manera que Departamento tiene una dependencia funcional completa con respecto a ella. No
ocurre lo mismo con la otra clave candidata (Alumno Profesor) ya que Departamento tiene una dependencia parcial de ella, porque sólo depende de Profesor.
Es cierto que en este esquema se repite redundantemente la información del
Departamento siempre que se repita un determinado profesor, y por tanto no
cumple la 2FN. Consecuentemente, un esquema en 1FN cuyas claves candidatas están formadas por un solo atributo siempre está en 2FN.
7.3. Tercera Forma Normal (3FN)
Un esquema R(T, L) en 2FN está en 3FN cuando ningún atributo no principal depende transitivamente de ninguna clave.
Entre X y Z existe una dependencia transitiva (X ։ Z) si se cumple que
Revisión 3 de mayo de 2007
X ∩Z
∃Y : X ∩ Y
=
=
φ
φ, Y ∩ Z = φ
X → Y, Y 9 X
y
Y →Z
Diseño de bases de datos
Se dice que Z es una información sobre X, pero de forma indirecta, ya que
realmente Z es una información sobre Y , e Y sobre X.
Aunque nuestro esquema no cumple la 2FN, este tipo de transitividad también se presenta:
Alumno Asignatura → Profesor
Profesor 9 Alumno Asignatura
Profesor → Departamento
Luego: Alumno Asignatura ։ Departamento
Esto sucede porque Departamento es una información propia del Profesor
y no de la asignatura de un alumno. Por eso cada vez que se repite el profesor
se repetirá la asignatura que imparte.
Otra forma equivalente, aunque algo más sencilla, de comprobar si un esquema está en 3FN es la siguiente: un esquema estará en 3FN si para toda dependencia funcional no trivial X → A ( es decir que A * X ) se cumple alguna
de las siguientes condiciones:
1. X es una superclave de R
2. A está contenida en una clave candidata de R
Aunque se habla de superclaves, si trabajamos con un conjunto de dependencias sin atributos extraños (por ejemplo un recubrimiento no redundante)
podemos hablar directamente de claves candidatas.
En nuestro esquema R vemos que la dependencia Profesor → Departamento no cumple ninguna de las dos condiciones.
7.4. Forma Normal de Boyce-Codd (FNBC)
Un esquema R(T, L) en 1FN está en BCFN cuando para toda dependencia
funcional no trivial X → Y (es decir, Y * X , Y 6= φ), se cumple que:
1. X es una clave o superclave de R.
Vemos que ahora somos más restrictivos y exigimos que se cumpla únicamente la condición 1 para estar en 3FN. Por lo tanto es evidente que todo
esquema que esté en FNBC también estará en 3FN.
Para ver la diferencia entre FNBC y 3FN vamos a retomar nuestro esquema
original:
R(T, L) : T =
L=
{Alumno, Asignatura, Profesor, Nota}
{Alumno Asignatura → Profesor,
Alumno Asignatura → Nota,
Profesor → Asignatura}
Claves Candidatas
Revisión 3 de mayo de 2007
=
{(Alumno Asignatura), (Alumno Profesor)}
Diseño de bases de datos
Ahora R sí que está en 3FN, ya que en las dos primeras dependencias su
parte izquierda es una clave candidata (condición 1) y en la dependencia Profesor → Asignatura, Asignatura es un atributo principal (condición 2). Pero
es por esta última dependencia por lo que R no alcanza la FNBC. De hecho,
en R tenemos repetición de información, como ya se ha mencionado en varias
ocasiones.
Hay un aspecto de la FNBC que la hace muy interesante a la hora de implementar la base de datos. Si todas la tablas están en FNBC, podemos hacer
cumplir las dependencias funcionales asociadas a cada tabla mediante la restricción PRIMARY KEY (para la clave primaria) y UNIQUE (para el resto de
claves candidatas). Es decir con estas dos palabras reservadas de SQL no necesitamos implementar ningún otro procedimiento adicional. Sin embargo en
3FN las dependencias del tipo Profesor → Asignatura suelen tener un coste
computacional elevado, como ya se mencionó anteriormente.
Aún así, tenemos que ser conscientes de que el declarar un conjunto de
atributos como clave candidata, ya sea mediante PRIMARY KEY o UNIQUE,
también lleva un alto coste asociado. Los sistemas de gestión de bases de datos
suelen construir automáticamente, cuando hacemos estas declaraciones, un índice con estos atributos (frecuentemente se utilizan árboles balanceados). Estos
índices le permiten al sistema agilizar las comprobaciones de valores únicos
ante inserciones o modificaciones de las columnas implicadas. Por otro lado
debemos tener cierta confianza y pensar que los sistemas suelen hacer estas
operaciones de forma muy optimizada y por tanto no parece mala la idea de
que sean ellos los que se encarguen de estas comprobaciones.
Resumiendo todo lo anteriormente visto, a la hora de realizar un diseño de
una base de datos debemos plantearnos lo siguiente. Si un esquema o tabla
no alcanza una forma normal satisfactoria, por ejemplo FNBC, que reduzca la
repetición de información, entonces podemos realizar una descomposición de
la misma. Esta descomposición debe cumplir: la propiedad LJ; que si es posible
conserve dependencias; y que las tablas obtenidas alcancen una forma normal
más adecuada. Esto no siempre es posible, y a veces lo que ganamos por un
lado lo perdemos por otro. En este sentido existe un algoritmo que describimos
a continuación y que nos asegura una descomposición con la propiedad LJ y
que todas las tablas obtenidas alcanzan la FNBC.
Algoritmo de descomposición en FNBC con la Propiedad LJ
Partimos de R(T, L) donde L es un recubrimiento no redundante. Si existe
una dependencia X →A tal que X no sea clave (por tanto no está en FNBC),
proyectamos R en R1 = (T1 , L1 ) y R2 = (T2 , L2 ), con
T1
T2
=
=
X ∪ {A}
T − {A}
y
Podemos ver una de las condiciones para que la descomposición cumpla la
propiedad LJ ( T1 ∩ T2 → T1 − T2 ) siempre se cumple, ya que (T1 ∩ T2 ) es X y
Revisión 3 de mayo de 2007
Diseño de bases de datos
(T1 − T2 ) es A, y por tanto, se trata de la propia dependencia X → A. Es decir,
los atributos comunes (X) funcionan como clave por lo menos en uno de los
subesquemas (T2 ).
L1 y L2 son las proyecciones de L+ (cierre del conjunto L) sobre T1 y T2
respectivamente. Aquí es donde radica la dificultad del algoritmo, ya que normalmente no tenemos calculado L+ . Por tanto lo que debemos hacer es encontrar todas las posibles dependencias que impliquen únicamente a atributos de
cada esquema y después obtener su recubrimiento no redundante.
Si alguno de los esquemas obtenidos no alcanzan la FNBC volveremos a
descomponerlo hasta que todos los esquemas resultantes cumplan la FNBC.
En la práctica cuando en los esquemas aparecen dependencias de la forma
X → A1 , X → A2 . . . X → An donde X no es clave candidata, se genera una
tabla T1 con los atributos {X, A1 , A2 . . . An } y otra T2 con {T − {A1 , A2 . . . An }}
Para ser realmente formales tenemos que añadir una pequeña sutileza: dado un esquema R que es descompuesto sucesivamente, cumpliendo la propiedad LJ en cada paso, hasta obtener un conjunto de subesquemas R1 , R2 . . . Rn
por aplicación del Teorema 2, dicha descomposición también cumple la propiedad LJ, es decir, si reunimos de nuevo R1 , R2 . . . Rn obtendremos de nuevo
R.
Para no perder de vista nuestro sustancioso ejemplo vamos a ver cómo se
aplica este algoritmo en un caso concreto. Partimos del siguiente esquema:
R(T, L) : T =
L=
Claves Candidatas
=
{Alumno, Asignatura, Profesor, Nota}
{Alumno Asignatura → Profesor,
Alumno Asignatura → Nota,
Profesor → Asignatura}
{(Alumno Asignatura), (Alumno Profesor)}
Vemos que la dependencia Profesor → Asignatura no está en FNBC, y la
utilizamos para descomponer, obteniendo:
R1 (T1 , L1 ) :
Clave Candidata
T1 = {Alumno, Profesor, Nota}
L1 = {Alumno Profesor → Nota}
=
{(Alumno Profesor)}
R2 (T2 , L2 ) :
T2 =
{Profesor, Asignatura}
Clave Candidata
L2 =
=
{Profesor → Asignatura, }
{(Profesor)}
El proceso es sencillo, pero incluso aquí vemos que el cálculo de L1 no es
tan inmediato, ya que ninguna de las dependencias de L se pueden exigir en
Revisión 3 de mayo de 2007
Diseño de bases de datos
R1 pues involucras atributos de R2 y hemos tenido que encontrar una dependencia que se deduce de L (es decir que pertenece a L+ ) y que involucra únicamente a a tributos de T1 , la verdad es que ya sabíamos que (Alumno, Profesor)
era clave candidata en R, luego debería seguir siéndolo en R1 . Es cierto que
en otros casos esto es menos evidente. Como planteamiento práctico una estrategia puede ser: dado el conjunto de atributos Ti buscamos las dependencias
funcionales que existen como si fuera el problema inicial, si ya lo hicimos con
un conjunto de atributos mayor (T ) raro será que no lo consigamos de nuevo.
Si hubiéramos partido del esquema que incluía el Departamento del Profesor, el atributo Departamento quedaría en el esquema R2 junto con Profesor y
Asignatura.
El resultado que hemos obtenido con todas las tablas en FNBC ya lo vimos
anteriormente para mostrar que dicha descomposición no conservaba dependencias. Una vez más el resultado no parece óptimo, la elección última dependerá de otros factores de implementación que no se contemplan en esta
teoría de diseño de bases de datos. Por ejemplo: si la consulta más frecuente es
obtener listados de alumnos asignaturas y notas la descomposición no parece
demasiado adecuada, si el número de actualizaciones de los campos Alumnos
Asignatura Profesor no es muy elevado puede que el esquema original sea la
mejor alternativa, si . . .
Revisión 3 de mayo de 2007
Diseño de bases de datos
Anexo I: Cierre de un conjunto de atributos
El siguiente algoritmo, partiendo de un conjunto de DF L, nos permite calcular el conjunto de atributos que depende funcionalmente de uno dado (α).
Dicho conjunto se expresa como α+ .
α+ ⇐ α
while α+ cambie do
for each β → γ in F do
if β ∈ α+ then
α+ ⇐ α+ ∪ γ
end if
end for
end while
Anexo II: Cálculo del cierre de un conjunto de dependencias
Ya hemos definido anteriormente L+ como el conjunto de todas las DF que
se pueden inferir de un conjunto L dado. Existen diversos algoritmos para obtenerlo, pero aquí sólo vamos a mostrar uno sencillo ya que en la práctica no
se suele utilizar. Dado un esquema R(T, L)
for each X ⊆ T do
Calcular X +
for each A ∈ X + do
incluir X → A en L
end for
end for
Revisión 3 de mayo de 2007
Diseño de bases de datos
Anexo II: Cálculo del recubrimiento canónico
Dados dos conjuntos de dependencias funcionales L y M , tal que L+ =
M , se dice que L recubre a M y M recubre a L, o que son mutuamente recubrimientos. Se dice que un recubrimiento no es redundante o es un recubrimiento
canónica cuando:
+
Todas las dependencias son de la forma X → A siendo X un conjunto de
atributos y A un único atributo.
No hay atributos extraños. B ∈ X es extraño en la dependencia X → A,
si al sustituir en L la dependencia X → A por (X − B) → A, el cierre no
varía. Es decir:
(L − {X → A}U {(X − B) → A})+ = L+
No hay DFs redundantes. X → A es redundante si al eliminarla del conjunto L se puede seguir infiriendo del resto. Es decir, que no aporta nada
nuevo a L:
(L − {X → A})+ = L+
El algoritmo que nos permite obtener un Recubrimiento canónico se basa
en los siguientes pasos:
1. Para toda dependencia X → Y de L, la sustituimos por X → Ai , siendo
A1 , ..., An el conjunto de atributos Y . Sea L1 el conjunto resultante.
2. Eliminamos atributos extraños. Para toda dependencia X → A ∈ L1 , si
B → X , y Z = X − B, calculamos Z + respecto de L1 .
Si A ∈ Z + , sustituimos X → A por la dependencia Z → A. Sea L2 el
conjunto resultante.
3. Eliminamos dependencias redundantes. Para toda dependencia X →
A ∈ L2 , calculamos X + respecto de L2 − {X → A}, si A ∈ X + , eliminamos X → A.
Sea L3 el conjunto canónico resultante.
Revisión 3 de mayo de 2007
Descargar