La integridad referencial declarativa y las relaciones entre tablas

Introducción

La integridad referencial declarativa (DRI) y las relaciones entre tablas vienen a garantizar algo importantísimo en los sistemas relacionales, en este caso la consistencia de los datos. No es de recibo que exista en una base de datos un albarán al cliente 50, y que el cliente 50 no exista!!, y eso es lo que nos garantiza la DRI, que esas cosas no sucedan. Ahora bien, lo que la integridad referencial no garantiza es que existan registros relacionados,

por ejemplo, en los trozos de código que vienen justo a continuación vamos a ver como puede existir una cabecera que no tenga líneas, aunque la DRI garantiza que si existe una línea su cabecera si existe. Esto puede llevarnos a situaciones a veces difíciles de entender, sobre todo al principio. Si se construye una sentencia select que devuelve los registros de la cabecera, aparecen X registros, si la sentencia se completa con las unión con las líneas aparecen Y, pero Y puede ser hasta menor que X, sería ‘normal’ que fuese mayor, pero ¿menor?, veamos el ejemplo.

/* Siempre nos basamos en una bd que exista en el sistema y que se llame pruebas */

use pruebas go create table cabecera( id int identity (1,1) primary key not null,

                                fecha datetime default getdate())

go create table lineas (id int not null, orden int not null,

                            Descripcion varchar(100) not null,
                            constraint pk_lineas primary key (id,orden),
                            constraint fk_lineas foreign key (id) references cabecera)

go

/* además metemos 100 cabeceras */

declare @i int

set @i=0

while @i<100

begin

insert into cabecera default values

select @i=@i+1

end

go

/* y dos lineas *//

insert into lineas values (1,1,’Esto es una prueba’)

insert into lineas values (1,2,’Esto es una prueba linea 2′)

go

select * from cabecera

— 100 filas

Esto es normal, el sistema nos devuelve las 100 filas de la cabecera,

go

select * from cabecera inner join lineas on cabecera.id=lineas.id

— 2 filas !!!!

Sin embargo ahora devuelve solo dos, por que una sola cabecera tiene líneas y tiene dos líneas, por tanto devuelve aquellas que están en las dos tablas, coincidiendo su id, esto da como resultado, dos líneas solamente.

Pero podemos hacer que devuelva todas las cabeceras, y aquellas que no tengan líneas, pues que nos devuelva valores null, esto son los llamados ‘joins externos’ y sirven para resolver la problemática precisamente de cabeceras no relacionadas con líneas.

go

select * from cabecera left join lineas on cabecera.id=lineas.id

— 101 filas

El resultado serán las 101 filas, siendo todos los valores de la tabla líneas null, en aquellas que no tienen registros relacionados. Aparece una fila más que cabeceras por que una cabecera tiene dos líneas, por tanto esa cabecera aparecerá dos veces.

go

select * from cabecera left join lineas on cabecera.id=lineas.id where lineas.id is null

— 99 filas = 101 −2 filas.

También podemos mediante sentencias como la que tenéis arriba encontrar todas las cabeceras que no tiene líneas, sabemos que si no hay líneas, el campo que relaciona líneas con cabecera será null, no existirá, así que obligamos a que esto sea así con el predicado ‘Where lineas.id is null’

Otras cuestiones, también puede ser que las relaciones externas sean opcionales, en nuestro ejemplo, vamos a añadir una zona, que resultará opcional, se podrá indicar para esta cabecera o no. Si no se indica será un valor null en la tabla de cabecera, pero si se indica, tiene que existir, a continuación el script para crear esta relación.

alter table cabecera add idZona int

go

create table Zonas (idzona int identity(1,1) not null primary key,

                    nombre varchar(100) not null )

go

alter table cabecera add constraint fk_CabZonas foreign key (idZona) references zonas(idzona)

go

Insertamos un registro en zonas

insert into zonas values (‘Sur Este?’)

select @@identity

Insertada la zona 1

Sin embargo existen ya 100 cabeceras con zona =null y eso no supone ningún problema, vamos excepto que queramos sacar el nombre de la cabecera.

select * from cabecera c inner join zonas on c.idzona=zonas.idzona

También devolverá cero registros, por lo que tendremos que usar de nuevo left join.

Como comentarios añadir que sql-server también admite una notación distinta de inner join, left join y right join, a base de poner el la condición where el símbolo * al lado del igual de una de las condiciones, pero que además de ser menos clara en mi opinión, no es nomenclatura ANSI, por lo que Sql-server podría no soportarla en futuras versiones.