Rel Project: Una base de datos relacional (en Java) como deberian ser? Parte III: Restricciones

Continuando con las restricciones

En la parte II vimos que era posible efectuar un insert así (en donde el Usuario 'Rodrigo' no existe en la relvar/tabla de usuarios):

INSERT RELACIONES_USUARIOS RELATION { TUPLE {NombreUsuarioOrigen 'Juan', NombreUsuarioDestino 'Rodrigo', TipoRelacion 'Amigo'} }

Y la base de datos no nos lo impediría... ¿como podemos evitar que este tipo de "registros huérfanos" entren en nuestra base de datos?

Es necesario establecer una “restricción” en nuestra base de datos, así: :

CONSTRAINT USUARIO_ORIGEN_VALIDO USUARIOS{Nombre} RENAME(Nombre AS NombreUsuarioOrigen)>=RELACIONES_USUARIOS{NombreUsuarioOrigen};
CONSTRAINT USUARIO_DESTINO_VALIDO USUARIOS{Nombre} RENAME(Nombre AS NombreUsuarioDestino)>=RELACIONES_USUARIOS{NombreUsuarioDestino};

En Rel la expresion A >= B sirve para preguntar "A es superconjunto de B (o es B subconjunto de A)?" de modo que estamos preguntado si el conjunto de los "NombreUsuarioOrigen" en la relvar USUARIOS contiene al conjunto de los "NombreUsuarioOrigen" en la relvar RELACIONES_USUARIOS, si eso resultara falso (algun NombreUsuarioOrigen no estuviera en USUARIOS, la restricción no se cumple, y es imposible efectuar la modificación en la base de datos).

Por ejemplo, esta vez, al intentar hacer esto:

INSERT RELACIONES_USUARIOS RELATION {  TUPLE {NombreUsuarioOrigen 'Juan', NombreUsuarioDestino 'Rodrigo', TipoRelacion 'Amigo'} };

Obtenemos este mensaje de error:

ERROR: Update will cause constraint USUARIO_DESTINO_VALIDO to fail.

Ahora agreguemos otra relvar, “STATUS_USUARIOS”, en donde se guardaran los mensajes de status de los usuarios:

VAR STATUSES BASE RELATION
{

NombreUsuario CHAR,

Texto CHAR,

}

KEY { NombreUsuario , Texto } ;

Esta relvar nos permite representar que cada usuario puede tener todos los mensajes de status que quiera, siempre que estos no se repitan… si queremos que un usuario pueda volver a escribir el mismo status mas adelante, tendríamos que poner:

VAR STATUSES   BASE RELATION
{

IdStatus INTEGER,

NombreUsuario CHAR,

Texto CHAR,

}

KEY { IdStatus } ;

Ahora ya no estamos restringidos para duplicar mensajes para un mismo usuario y además, es fácil saber cual es el mensaje “actual” para un usuario determinado, “aquel que tenga el IdStatus mas grande”. Sin embargo, todavía nos hace falta cubrir la parte de la “opinión” de otros usuarios con respecto al los status de este usuario, lo cual nos lleva a darnos cuenta que el Status es una entidad independiente y los usuarios pueden jugar diferentes “roles” con respecto a ella:

VAR STATUSES BASE RELATION
{

IdStatus INTEGER,

Texto CHAR

}  KEY{IdStatus};

VAR STATUSES_USUARIOS  BASE RELATION
{

IdStatus INTEGER,

NombreUsuario CHAR,

Rol CHAR

}  KEY{IdStatus,NombreUsuario,Rol};

CONSTRAINT STATUSES_USUARIOS_VALIDOS  USUARIOS{Nombre} RENAME(Nombre AS NombreUsuario)>=STATUSES_USUARIOS  {NombreUsuario};

CONSTRAINT STATUSES_VALIDOS_USUARIOS   STATUSES {IdStatus}>=STATUSES_USUARIOS{IdStatus};

En “Rol” podemos tener que el usuario sea el “Autor” del Status, o que le “Guste” el status, o “Menciona” en cuyo caso indicamos que la persona esta siendo mencionada en el status. Debido a que una persona puede ser mencionada en un Status, y además a esta persona puede (o no) Gustarle dicho Status, es necesario que la llave primaria incluya también al Rol. (Si el rol no se incluyera, entonces los pares “IdStatus” y “NombreUsuario” serian únicos y una persona no podría jugar mas de 1 rol con respecto a un Status)

Sin embargo es importante hacer notar que solo 1 persona puede ser “Autor” de un Status. Como Rel es verdaderamente relacional, podemos expresar esta restricción así:

Primero, necesitamos detectar el numero de registros (tuplas) en  STATUSES_USUARIOS con el mismo Rol:

SUMMARIZE STATUSES_USUARIOS BY {IdStatus,Rol} ADD (COUNT() as Autores)

Luego, recordemos que solo nos interesa que el Rol “Autor” no se duplique:

SUMMARIZE STATUSES_USUARIOS where Rol = 'Autor' BY {IdStatus,Rol} ADD (COUNT() as Autores) > 1

Con esa consulta, tenemos todos los casos en que podría haber mas de 1 Autor, y finalmente definimos el constraint, en el que indicamos que queremos que la consulta anterior siempre este vacía, usando el operador IS_EMPTY. (En otras palabras queremos que nunca pueda encontrarse 2 autores)

CONSTRAINT AUTOR_STATUSES_USUARIOS  IS_EMPTY(SUMMARIZE STATUSES_USUARIOS where Rol = 'Autor' BY {IdStatus,Rol} ADD (COUNT() as Autores)  where Autores > 1);

En Oracle 11 la técnica con la que podríamos obtener el efecto mas parecido debería ser un check, pero no podemos usarlos por que en Oracle los check no pueden hacer consultas sobre toda la tabla, o invocar UDFs, asi que la única opción que nos queda es usar triggers (que tampoco son nada comodos de usar para este tipo de trabajos gracias a los errores "Mutating Table", y que al final tambien se quedan cortos por que en Oracle hay triggers a nivel registro y a nivel tabla, pero no a nivel transacción) . En MSSQL 2005 (no he podido encontrar información con respecto a la version 2008, pero no creo que la situación haya cambiado), podemos llamara a una UDF para hacer un chequeo así… y funcionara para ciertos casos… pero en otros casos fallara debido a que MSSQL no ejecuta el check como una “operación sobre el conjunto de los datos” si no que la ejecuta línea por línea (básicamente usar un UDF en un check en MSSQL es un “hack”, algo que se puede “por accidente”, pero que no se diseño correctamente). La que probablemente se acerca mas es FireBird con sus Database triggers que pueden dispararse "ON TRANSACTION COMMIT".

Por otro lado, de acuerdo con el Estándar de SQL de 1992, SQL debería contar con una instrucción “CREATE ASSERTION” que debería ser equivalente a los CONSTRAINT multipropósito de Rel… pero hasta la fecha no se de ninguna base de datos que lo implemente (Ni Oracle, ni MSSQL, ni PostgreSQL, ni MySQL, Ni Db2, Ni Firebird).

Probemos que efectivamente funciona, para ello necesitaremos unos Statuses asociados a Usuarios:

INSERT STATUSES     RELATION {
        TUPLE {IdStatus 1, Texto 'Estoy aburrido'},

        TUPLE {IdStatus 2,Texto 'Tengo Hambre'},

        TUPLE {IdStatus 3,Texto 'Cogito Ergo Sum'},

        TUPLE {IdStatus 4,Texto 'Voy a ir al concierto de XXXX'},

        TUPLE {IdStatus 5,Texto 'Lei el libro de las mil y una....'},

        TUPLE {IdStatus 6,Texto 'Estoy aburrido'}

};

INSERT STATUSES_USUARIOS     RELATION {
        TUPLE {IdStatus 1, NombreUsuario 'Clodomiro', Rol 'Autor'}

};

Se producirá un error por que “Clodomiro” no esta en la lista de usuarios.

Ahora probemos con:

INSERT STATUSES_USUARIOS     RELATION {
        TUPLE {IdStatus 7, NombreUsuario 'Juan', Rol 'Autor'}

};

Igual error por que IdStatus 7 no esta en la lista.

Ahora metamos un par de  tuplas correcta:

INSERT STATUSES_USUARIOS     RELATION {
        TUPLE {IdStatus 1, NombreUsuario 'Juan', Rol 'Autor'}

};

INSERT STATUSES_USUARIOS     RELATION {

        TUPLE {IdStatus 1, NombreUsuario 'Pedro', Rol 'Gusta'}

};

Ahora, que pasa si tratamos de indicar que alguien mas es autor de ese mismo status:

INSERT STATUSES_USUARIOS     RELATION {
        TUPLE {IdStatus 1, NombreUsuario 'Jose', Rol 'Autor'}

};

y Rel no reportara que

ERROR: Update will cause constraint AUTOR_STATUSES_USUARIOS to fail.

Ahora bien, si examinamos nuestro modelo mas de cerca, podremos darnos cuenta de que tiene un problema: Podemos insertar Statuses que no estén conectados con ningún usuario (en este caso aquellos con IdStatus 2, 3,4,5,6… En SQL, evitar que eso suceda es algo difícil ¿Como puede ayudarnos Tutorial D/Rel en esos casos?. Eso lo veremos en la Parte IV (que tratara del comportamiento Transaccional y las implicaciones que tiene sobre el momento en que se evalúan las restricciones).

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de rodrigo salado anaya

Rel

Esta padre este tema felicidades. Espero con gusto las siguientes partes, también me gusta mucho el tema de 'Intentional programming'.