Aunque mi perfil profesional quizás esté algo más orientado a Técnica de Sistemas que a Desarrollo, debo admitir que el mundo del desarrollo siempre me ha picado, y aunque he hecho mis pinitos, tengo esa espina clavada de que me habría gustado hacer algo más (bueno... mucho más). En cualquier caso, no es la primera vez que escribo sobre asuntos más propios de desarrolladores (como la depuración de procedimientos almacenados, UDF y Triggers, Cómo trabajar con Fechas en SQL Server, SQL Injection, Claves Subrogadas, Tablas Versionadas, etc.), y es que la realidad de la realidad, es que para un Administrador de Base de Datos es de gran utilidad conocer con claridad ciertos conceptos de desarrolladores, que en la práctica pueden ser de mucha ayuda para optimizar sistemas de bases de datos, ofrecer un buen soporte a desarrollo, etc.
Continuando con el tema que nos ocupa, en esta ocasión vamos a realizar esta exposición basándonos en un ejemplo. Imaginemos que tenemos una tabla USUARIOS, que contiene un campo EMAIL, y se nos solicita que desarrollemos una consulta SQL (o un procedimiento almacenado, lo que sea) para obtener todas las direcciones de correo electrónico separadas por punto y coma.
La forma habitual de resolver esto para la mayoría de los programadores es iterando, lo cual en el desarrollo de base de datos, implica crear un cursor (algo así como un RecordSet para los programadores de ADO) para poder recorrer cada fila, y en una variable poder ir calculando nuestro valor acumulado (ya sea concatenando, o aplicando la fórmula que fuere).
En el caso particular de SQL Server, la utilización de cursores es una práctica poco recomendable, lo mismo que ocurre con Sybase (recordemos que SQL Server viene de Sybase, y que ambos motores han estado compartiendo código varios años), con IBM Informix, y con otros muchos motores de base de datos.
Quizás sea que la mayoría de los programadores están más tiempo desarrollando otras partes de la aplicación (ej: formularios) que la propia base de datos, y sea por esto por lo que se adquieran ciertas costumbres (como es el hecho de Iterar, y en consecuencia, de utilizar Cursores como método natural de trabajar).
Sin embargo, la realidad es que es posible desarrollar todo (o prácticamente todo) sin necesidad de utilizar Cursores, como ocurre en el caso de este ejemplo (pasar filas a columna), aunque quizás no sea la forma más natural de realizarlo (en un principio, que al final, lo de evitar cursores es como montar en bici ;-).
Sin más misterio, aprovecho para incluir un trozo de código Transact-SQL con la consulta SQL solicitada. Principalmente, existen dos trucos:
- El primer truco consiste en declarar una variable que utilizaremos para almacenar el valor Acumulado, en el caso del ejemplo, la variable @AcumuladoEmails.
- El segundo truco consiste en utilizar dicha variable (@AcumuladoEmails) en la sentencia SELECT para calcular nuestro valor acumulado, que en nuestro caso es concatenar direcciones de correo electrónico separadas por punto y coma. Para esto, hemos utilizado la función COALESCE, con el objetivo de que para la primera fila se tome sólo el EMAIL, y para las siguientes filas se mote el EMAIL precedido del punto y coma.
Por último, podremos realizar la acción que deseemos con nuestra variable de acumulado (en el ejemplo @AcumuladoEmails), como por ejemplo seleccionarla (es decir, mostrarla como parte de una sentencia SELECT), insertarla en una tabla, etc.
Aquí va el código:
DECLARE @AcumuladoEmails VARCHAR(max)
SELECT @AcumuladoEmails = COALESCE(@AcumuladoEmails + '; ' + EMAIL, EMAIL) FROM dbo.USUARIOS WHERE USUARIO_ACTIVO = 1 ORDER BY EMAIL
SELECT @AcumuladoEmails |
Como podréis comprobar, esta consulta SQL funciona, es rápida, y no utiliza cursores. Si estuviésemos trabajando con una tabla USUARIOS muy grande (ej: con muchos millones de filas), habría que ver si esta tarea sería recomendable hacerla en la base de datos o fuera de la base de datos, para sacar el mayor rendimiento del sistema, y teniendo en cuenta que el caso que nos ocupa se trata de una acción intensiva en concatenaciones (recordemos la existencia de la clase StringBuilder de Net Framework, el dios de las concatenaciones).
Y poco más. Sólo quería compartir este truquillo para quienes les pueda interesar, y también para mí, que tengo memoria de pez, y seguro que la próxima vez que me haga falta, se me ha olvidado... jeje ;-)