Ir al contenido principal

Usa el esquema en tus consultas o rompe tu código sin darte cuenta

Hace ya algún tiempo contábamos en otra entrada de este blog la importancia que tiene nombrar correctamente a los objetos de una base de datos a la hora de realizar las consultas: siempre debemos usar el formato esquema.nombre.

Los motivos quedaban claros entonces: evitar problemas de rendimiento en la compilación de las consultas debidos al proceso de resolución de nombres y evitar errores si el esquema por defecto de un usuario es distinto a "dbo" o "NULL".

Si bien estos errores pueden ocasionar algún quebradero de cabeza, su resolución es bastante sencilla, tal y como se explica en el artículo anterior. Sin embargo, añadimos ahora un nuevo caso que podría ser más problemático, ya que podemos generar un error que pasase inadvertido, devolviendo registros incorrectos en nuestras consultas.

Cuando trabajamos con más de un esquema -cosa que deberíamos hacer no sólo por motivos de organización de nuestros objetos, sino también para aplicar permisos sobre ellos más ágilmente- es especialmente importante prestar atención en la definición de vistas y procedimientos almacenados.

En ellos, deberemos poner siempre el esquema y el nombre de los objetos a los que hagamos referencia, si no queremos exponer nuestro código a futuros problemas.

Abocados al cataclismo


Supongamos que en nuestra base de datos usamos esquemas, en concreto "dbo" y "web". En el esquema dbo tenemos dos tablas "Cliente" y "Pedido", en el esquema web añadimos ahora la vista "PedidosCliente" e insertamos algunos registros en las tablas a modo de prueba. El script completo para crear los objetos y rellenar las tablas es el siguiente:

Una vez tenemos los objetos creados, ejecutamos una simple consulta sobre la vista:

Que nos devolverá tres resultados: Dos pedidos para Juan y uno para María.

Ahora bien, imaginemos que solamente para los clientes web necesitamos guardar más información, por ejemplo su password. Como tenemos clientes tradicionales y web, decidimos añadir una segunda tabla Cliente, esta vez en el esquema web, que hará referencia a la tabla dbo.Cliente por el Codigo y extenderá la información de sus registros con la columna Password, para aquellos que vengan a través de la web. Así:


Empieza la batalla de los esquemas


A estas alturas ya podemos empezar a tener dudas sobre a qué tabla referencia nuestra vista web.PedidosCliente. Recordemos que habíamos referenciado a las tablas Pedido y Cliente sin usar el esquema en la definición de la vista. Así pues, ¿la vista seguirá usando dbo.Clientes como hasta ahora, o bien pasará a referenciar la nueva tabla web.Clientes?

Una primera comprobación que podemos hacer es, en el SQL Server Management Studio, hacer clic derecho sobre la vista y en el menú contextual seleccionar "Ver Dependencias".

SQL Server nos dice que la vista sigue dependiendo de dbo.Cliente
Parece que sigue usando la tabla dbo.Cliente. Para estar doblemente seguros, podemos consultar también la definición de la vista  -igualmente con clic derecho sobre ella- y ver cómo está definida. Efectivamente, SQL Server parece estar seguro, es sobre dbo.Cliente:

SELECT        P.Numero, P.FechaPedido, C.Nombre
FROM            dbo.Pedido AS P INNER JOIN
                         dbo.Cliente AS C ON P.CodigoCliente = C.Codigo

SQL Server Management Studio no dice la verdad


A estas alturas podemos asumir que nuestra vista seguirá funcionando como si nada hubiera pasado. Para comprobarlo, ejecutemos la misma consulta del principio de esta entrada sobre la vista. Debería devolver los mismos tres resultados, ¿verdad? Veámoslo:

Msg 207, Level 16, State 1, Procedure PedidosCliente, Line 3 [Batch Start Line 44]El nombre de columna 'Nombre' no es válido.Msg 4413, Level 16, State 1, Line 46No se pudo usar la vista o función 'web.PedidosCliente' debido a errores de enlace.

¡Oh! ¡Oh! Hemos roto nuestra vista y no nos hemos dado cuenta hasta que hemos ejecutado una consulta sobre ella.

Sin embargo, esta es la buena noticia dentro de la gravedad del problema. Ha ocurrido un error y SQL Server nos lo ha notificado. Este error se produce porque la vista está definida dentro del esquema web. Al estar dentro de este esquema, pese a que el Management Studio nos insista en que la vista hace referencia a la tabla dbo.Cliente, no es así: al crear la tabla web.Cliente, la vista hace referencia a ella, porque está en su mismo esquema. Como esta tabla no tiene la columna Nombre, se produce el error.

El mismo error lo podríamos obtener si recreásemos la vista o la intentásemos modificar sin alterar la definición.

Aún puede ser peor


Sí, podría serlo. Si nuestra nueva tabla web.Cliente también tuviese un campo Nombre -imaginemos que queremos poner nombre de usuario web, pero llamamos a la columna Nombre- no se produciría ningún error. Simplemente cambiaría el conjunto de resultados devuelto, circunstancia que podría pasar inadvertida hasta que, por ejemplo, un usuario detectase la anomalía. Por ejemplo:

¡Oooooh! ¡Ooooooh! ¿Ningún resultado? ¿Qué ha pasado con nuestros tres pedidos de Juan y María? Fácil: la consulta sobre la vista ha funcionado, pero haciendo referencia a la tabla web.Cliente, donde no tenemos ningún registro. Así que la vista nos devuelve eso: ningún registro.

La solución


Es sencilla: Nombrar a los objetos por su nombre completo, que incluye esquema.nombre. Si en la definición de la vista original simplemente hubiésemos puesto dbo.Cliente y dbo.Pedido, no habría forma de haberla roto, ni con error ni inadvertidamente. Así:


Recuérdalo, llama a las cosas por su nombre, o podrás tener problemas que, por muy poco probables que parezcan, ocurrirán en el momento más inoportuno. Siempre es mejor no dejarlo en manos del azar.

Con SCHEMABINDING esto no pasaría


Efectivamente, hay otra solución. SQL Server nos permite definir nuestras vistas (y funciones) con la opción SCHEMABINDING. Esta opción hace que no se pueda cambiar la definición de los objetos a los que hace referencia la vista (las tablas Pedido y Cliente en nuestro caso). Además, tal y como explica la MSDN, se nos obliga a usar el esquema siempre cuando definimos una vista con WITH SCHEMABINDING.

Cuando se utiliza SCHEMABINDING, select_statement debe incluir los nombres de dos partes (schema.object) de las tablas, vistas o funciones definidas por el usuario a las que se hace referencia.
Por ejemplo, si intentamos definir nuestra vista web.PedidosCliente así:
Recibiremos el siguiente error:

Msg 4512, Level 16, State 3, Procedure PedidosCliente, Line 4 [Batch Start Line 27]No se puede enlazar a esquema vista 'web.PedidosCliente' porque el nombre 'Pedido' no es válido para enlazar a esquema. Los nombres deben constar de dos partes y los objetos no pueden hacer referencia a sí mismos.

Comentarios

  1. Este comentario ha sido eliminado por un administrador del blog.

    ResponderEliminar

Publicar un comentario

Entradas populares de este blog

Aprendiendo a usar LEFT OUTER JOIN

En esta entrada pretendemos explicar los diferentes resultados obtenidos por distintas construcciones de consultas que, aparentemente, deberían producir el mismo conjunto de resultados. Así, veremos las diferencias entre filtrar los resultados de una query en la unión (Join) mediante condiciones ON y mediante cláusulas WHERE.

Variantes del SELECT COUNT con DISTINCT

Seguramente, muchos de vosotros habréis usado en innumerables ocasiones la función de T-SQL COUNT , que no hace sino devolver un número de registros: de una tabla, de un conjunto de resultados, etc... En una de sus aplicaciones, combinado con el DISTINCT -uno de los dos argumentos que admite- COUNT nos devuelve el número de valores únicos no nulos de la tabla o conjunto de resultados que estemos consultando. Pero ¡ojo! Cuidado con la sintaxis , o podemos obtener el valor equivocado sin darnos cuenta. No es lo mismo: SELECT COUNT (DISTINCT NombreCampo) FROM NombreTabla que: SELECT COUNT(*), DISTINCT NombreCampo FROM NombreTabla

Script para obtener el tamaño de todas las tablas de la base de datos

En algunas ocasiones podemos vernos con la necesidad de conocer qué tablas de nuestra base de datos están ocupando más espacio en disco. Por ejemplo, si disponemos de SQL Server Express , cuyas bases de datos están limitadas a 4GB o 10GB, según la versión que estemos usando -4, hasta 2005; 10, a partir de 2008-, aparte de usar las opciones de comprimir la base de datos, poner el log en el modo simple de recuperación o ajustar las políticas de crecimiento automático de nuestros ficheros, podemos necesitar averiguar qué tablas crecen más para tomar las decisiones oportunas.