Javi Moreno Apuntes Fichas de Lectura Archivo Sobre mi
Blog Logo

Javi


5 minutos de lectura

Hace unos años, en un proyecto lleno de anécdotas y experiencias, el grupo de desarrolladores me contaba que habían creado servicios para cada una de las tablas del modelo de datos con el objetivo de centralizar los accesos a la tablas. La idea me pareció interesante: estaba harto de tocar programas que tenían la misma query y había cambiado algún criterio por lo que unificar las consultas en un único servicio era genial.

Les sugerí que, a la hora de montar los cursores, preparasen toda salida de datos de forma que fuera reutilizable. De esta forma, a medida que se fueran creando nuevas consultas, solo habría que incluir la nueva query. "No habrá que dar de alta nuevas consultas, hacemos cursores dinámicos..." dijo uno de ellos con gran orgullo.

CURSORES DINÁMICOS... Dios mío... se han atrevido a utilizar SQL dinámico, el mayor sacrilegio en cualquier instalación con un mainframe, pensé yo. Cuando les dije que no se autorizaría la subida a producción de ningún programa con SQL dinámico me dijeron: "No, no. Si los hacemos con el SQL de toda la vida".

Lo que hacían era montar el where de tal forma que incluían todos los campos por los que se podría preguntar y comprobaban si el campo de la tabla era igual a la variable o si la variable estaba vacía. Esto tenía dos resultados: con un único cursor se obtenían los mismo resultados que con cursores ad-hoc (menos código que picar) y al abrir el cursor, el DB2 hacía un TABLESPACE SCAN y el programa aparecía en todas las estadísticas de pésimo rendimiento (gran bronca del DBA).

SELECT * FROM PERSONAS
 WHERE (EDAD = :EDAD OR :EDAD = 0)
   AND (SEXO = :SEXO OR :SEXO = ' ')
   AND (ALTURA = :ALTURA OR :ALTURA = 0)
   AND (PESO = :PESO OR :PESO = 0)
   AND (COLOROJOS = :COLOROJOS OR :COLOROJOS = ' ')
   AND (COLORPELO = :COLORPELO OR :COLORPELO = ' ');

Por eso, cuando la semana pasada me empeñe en montar un predicado variable para un proyecto de iOS y termine encontrando NSCompoundPredicate, pensé: "Esto si que es un cursor dinámico"

NSPredicate es una clase que se utiliza para definir condiciones lógicas que acoten una búsqueda. Esta clase se puede utilizar para obtener un array de objetos más pequeño a partir de otro más grande utilizando un método de la clase NSArray que es filteredArrayUsingPredicate:

Por ejemplo: tenemos un array llamado personas que contiene objetos de tipo _Individuo. _El objeto individuo tiene diferentes propiedades: edad, sexo, altura, peso, colorOjos, colorPelo, etc. Si queremos tener un array más pequeño con las personas que son mujeres y tiene los ojos azules haríamos lo siguiente:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(sexo == %@ AND colorOjos == %@)", individuo.sexo, individuo.colorOjos];
NSArray *mujeresDeOjosAzules = [personas filteredArrayUsingPredicate:predicate];

En el ejemplo anterior, individuo.sexo contendría el valor "Mujer" e individuo.colorOjos contendría el valor "Azul". Bastante fácil, no?.

Bueno, supongamos ahora que estamos montando una especie de "Quien es quien" en el que dejamos que pregunten de forma aleatoria por un campo, por dos, por todos, etc... pero sin ningún control. En este caso deberíamos evaluar todas las posibilidades y definir un NSPredicate para cada una de ellas, evaluando previamente cada situación. Con pocas propiedades es asumible montar los diferentes NSPredicate, con seis propiedades las combinaciones van a ser unas cuantas (si mis recuerdos de combinatoria no fallan serían 63 combinaciones diferentes)... ¿tenemos que codificarlas todas? No, existe NSCompoundPredicate, la panacea de los cursores dinámicos.

El ejemplo quedaría así:

NSMutableArray *predicateArray = [NSMutableArray array];
if (individuo.edad) {
   [predicateArray addObject:[NSPredicate predicateWithFormat:@"(edad == %@)", individuo.edad]];
}
if (individuo.sexo) {
   [predicateArray addObject:[NSPredicate predicateWithFormat:@"(sexo == %@)", individuo.sexo]];
}
if (individuo.altura) {
   [predicateArray addObject:[NSPredicate predicateWithFormat:@"(altura == %@)", individuo.altura]];
}
if (individuo.peso) {
   [predicateArray addObject:[NSPredicate predicateWithFormat:@"(peso == %@)", individuo.peso]];
}
if (individuo.colorOjos) {
   [predicateArray addObject:[NSPredicate predicateWithFormat:@"(colorOjos == %@)", individuo.colorOjos]];
}
if (individuo.colorPelo) {
   [predicateArray addObject:[NSPredicate predicateWithFormat:@"(colorPelo == %@)", individuo.colorPelo]];
}
NSCompoundPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicateArray];

Lo que hemos hecho es un NSPredicate por cada propiedad que tenemos que evaluar. Comprobando previamente que esa propiedad esté informada. Cada NSPredicate lo incluimos en un array que posteriormente le pasaremos a la clase NSCompoundPredicate a través del método correspondiente. Esta clase solo posee tres métodos: andPredicateWithSubpredicates para recuperar los objetos que cumplan todas las condiciones, orPredicateWithSubpredicates para recuperar los objetos que cumplen alguna condición y notPredicateWithSubpredicates que, creo, sirve para recuperar los objetos que no cumplen ninguna condición.

Si queréis saber más sobre NSPredicate y NSCompoundPredicate, lo mejor es consultar la documentación de Apple:

NSPredicate NSCompoundPredicate