Página principal
Artículos y trucos
Catálogo de productos
Ejemplos y descargas
Mis libros
Cursos de formación
Investigación y desarrollo
Libros recomendados
Mis páginas favoritas
Acerca del autor
 
En colaboración con Amazon
 
Intuitive Sight

Campos lookup más rápidos

"Mi aplicación utiliza 200 tablas" - me dice con cierto orgullo un desarrollador. Y me muestra el código fuente. Realmente, se trata de una buena aplicación, con un código excepcionalmente claro. Funciona estupendamente con tablas Paradox, y ahora desea portarla a cliente/servidor. Y es aquí donde comienzan los problemas. Aunque la velocidad de la nueva versión es "aceptable", incluso con un solo usuario conectado se nota que el sistema se toma su tiempo para devolver y modificar datos. Evidentemente, es el momento de optimizar algunas operaciones, y una de las muchas operaciones a mejorar es el uso de campos de búsqueda (lookup fields).

De las doscientas tablas mencionadas, la mayor parte corresponden habitualmente a tablas de referencia. ¿Qué es una tabla de referencia, y cómo es posible que sean necesarias tantas? Supongamos que estamos trabajando en un lenguaje de programación tradicional, y que intentamos definir un tipo que represente a Personas. Uno de los atributos de una persona sería su estado civil. El dominio de los distintos estados civiles se reduce a un conjunto finito de valores, y casi siempre bastante reducido. Si nuestro lenguaje es ObjectPascal podemos representarlo mediante un enumerativo:

type
   TEstadoCivil = (ecSoltero, ecCasado, ecDivorciado);

Sin embargo, lo que es aceptable para un lenguaje de programación tradicional, no es válido para un diseño de bases de datos. ¿Qué pasa si queremos distinguir también a los viudos y a los separados? En ObjectPascal modificaríamos la definición de EstadoCivil, recompilaríamos la aplicación y punto. Pero una base de datos plantea otras necesidades, pues estos cambios dinámicos no pueden afectar a los datos ya existentes. Y nos interesa además que el propio usuario de la aplicación pueda añadir estas opciones sin la intervención nuestra.

Sí, s&é que usted ya sabe la solución: utilizar tablas de referencia, que pueden ser mantenidas por los usuarios. Pero ver las cosas desde un punto de vista "filosófico" a veces ayuda; siempre es bienvenido un cambio del punto de vista. En este caso, le propongo que vea a las tablas de referencia como los tipos "enumerativos" de su modelo de datos. ¿Características generales? La que más nos interesa en estos momentos es que estas tablas casi siempre contienen una cantidad finita y bastante pequeña de valores. Para mí, por ejemplo, la tabla de clientes no entra dentro de esta categoría, debido a su tamaño potencial.

Al editar objetos de tipo Persona en la base de datos, específicamente atributos del tipo EstadoCivil, el programador casi siempre recurre a controles que le permitan seleccionar uno de los posibles valores del dominio correspondiente. En Delphi y C++ Builder: el componente TDBLookupComboBox. En la mayoría de los casos, a partir de Delphi 2, se definen campos de búsqueda en la tabla de Personas, y se le suministran directamente al combo. El problema consiste en que cada vez que se selecciona una persona de la tabla, es necesario recalcular los valores de los campos de búsqueda asociados.

Si utilizamos un componente TTable para las referencias, cada vez que cambia la fila activa de Personas, se lanza una operación Lookup contra la base de datos, que se implementa mediante un select de SQL. Como beneficio, solamente se traen desde el servidor las filas estrictamente necesarias.

Si utilizamos un componente TQuery para las referencias, desde el principio se realiza una operación FetchAll, y se traen todas las filas. La parte positiva consiste en que, en lo adelante, la búsqueda se realiza directamente desde la parte cliente de la aplicación.

Es evidente que, para el caso que hemos analizado (conjuntos de datos pequeños utilizados como referencia) es preferible el uso de una TQuery como conjunto de datos de consulta (recuerde que estoy hablando de programación cliente/servidor). Ahora bien, existe otra posibilidad: utilizar un conjunto de datos cliente (TClientDataSet) como tabla de referencia. En particular, podemos utilizar la técnica del maletín (briefcase): el contenido de la tabla de referencia puede almacenarse en una caché local, en un fichero plano de extensión CDS. La primera vez que se ejecute la aplicación, el TClientDataSet se alimenta a partir del contenido de una consulta. De ahí en adelante, los datos siempre salen del CDS local. La ventaja con respecto a las TQuery es evidente: el uso de una TQuery obliga siempre a la aplicación a recuperar los datos de búsqueda durante la carga de la misma. En conclusión: si la aplicación tarda mucho en arrancar debido a estas consultas, considere el uso del modelo del maletín para acelerar la carga.

"¡Qué listo el tío! ¿Y qué pasa si alguien modifica una tabla de referencia?" - Fácil: en primer lugar debemos tener una opción explícita en el menú para refrescar la caché de referencias sin salir de la aplicación. Pero la parte más importante es detectar durante la carga si es necesario releer la caché o no. La forma más sencilla es utilizar triggers en las operaciones de modificación de una tabla de referencia para almacenar en algún lugar (otra tabla) la fecha de última modificación de dicha tabla. Durante la carga de la aplicación, se realiza una consulta sobre dicha tabla. Esta consulta solamente necesita traer un registro hasta el cliente. Después de analizar las fechas, la aplicación puede decidir si prescindir de la caché o seguir con la misma.


No he analizado aquí explícitamente el uso de la propiedad LookupCache, pues es similar al uso de consultas como conjuntos de referencia: la caché vale durante la ejecución de la aplicación, pero durante la carga es necesario traer todo el conjunto de datos.