GuilleSQL :: Microsoft SQL Server, SSIS, y más !!

Acumulado: Pasar múltiples Filas a una Columna, en una consulta SQL sin cursores


Este artículo muestra una problemática algo habitual al desarrollar consultas SQL, el problema de convertir múltiples filas en una única fila (pasar filas a columna) con todos los valores de las anteriores (por ejemplo, separados por punto y coma), una forma de construir un acumulado (no confundir con las funciones PIVOT y UNPIVOT de SQL Server). El motivo por el que he decidido incluir esto como artículo, es porque quiero mostrar como es posible desarrollar dicha consulta SQL y calcular dicho acumulado concatenando todas las filas, SIN CURSORES (como dios manda ;-) a través de un ejemplo.

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 ;-)



Comentarios

karla - 23/03/2015 (UTC)
Buena tu solucion. yo tengo un problema parecido, debo de mostrar en la cabecera todas las preguntas de una encuesta, en la primera columna debo de mostrar el nombre del encuestado y en las filas y columnas intermedias debo de mostrar sus respectivas respuestas, estos datos son de 3 tablas diferentes. y no se como hacerlo :(
me gustaria q me den una solucion o una pista please.



Miembros de
Miembros de GITCA (Global IT Community Association)

Menu de Usuario
  Iniciar Sesión
  Registrarse
  Restablecer Contraseña
  Ventajas de Registrarse

Acerca de
  Contigo desde Oct 2007
  771 usuarios registrados
  86146 pageloads/mes
  Ranking Alexa 498160

Social Networks
Sigue a Portal GuilleSQL en Linkedin !!
Sigue a Portal GuilleSQL en Twitter !!



Archivo

Marzo de 2019 (1)
Octubre de 2018 (1)
Julio de 2018 (1)
Junio de 2018 (4)
Mayo de 2018 (5)
Abril de 2018 (3)
Marzo de 2018 (2)
Febrero de 2018 (7)
Enero de 2018 (1)
Diciembre de 2017 (15)
Noviembre de 2017 (7)
Junio de 2017 (3)
Mayo de 2017 (1)
Marzo de 2017 (3)
Enero de 2017 (4)
Junio de 2016 (1)
Mayo de 2016 (2)
Abril de 2016 (2)
Septiembre de 2015 (2)
Agosto de 2015 (2)
Junio de 2015 (10)
Mayo de 2015 (4)
Abril de 2015 (8)
Marzo de 2015 (11)
Octubre de 2014 (3)
Septiembre de 2014 (7)
Agosto de 2014 (5)
Julio de 2014 (2)
Mayo de 2014 (4)
Abril de 2014 (4)
Marzo de 2014 (4)
Febrero de 2014 (1)
Enero de 2014 (5)
Diciembre de 2013 (8)
Noviembre de 2013 (2)
Octubre de 2013 (7)
Septiembre de 2013 (6)
Agosto de 2013 (1)
Julio de 2013 (6)
Junio de 2013 (11)
Mayo de 2013 (7)
Abril de 2013 (6)
Febrero de 2013 (5)
Enero de 2013 (7)
Diciembre de 2012 (12)
Noviembre de 2012 (13)
Octubre de 2012 (5)
Septiembre de 2012 (3)
Agosto de 2012 (6)
Julio de 2012 (4)
Junio de 2012 (1)
Mayo de 2012 (2)
Abril de 2012 (7)
Marzo de 2012 (16)
Febrero de 2012 (9)
Enero de 2012 (5)
Diciembre de 2011 (10)
Noviembre de 2011 (10)
Octubre de 2011 (4)
Septiembre de 2011 (5)
Agosto de 2011 (2)
Julio de 2011 (2)
Junio de 2011 (4)
Mayo de 2011 (2)
Abril de 2011 (6)
Marzo de 2011 (4)
Febrero de 2011 (10)
Enero de 2011 (5)
Diciembre de 2010 (6)
Noviembre de 2010 (4)
Octubre de 2010 (8)
Septiembre de 2010 (4)
Agosto de 2010 (1)
Julio de 2010 (3)
Mayo de 2010 (5)
Abril de 2010 (6)
Marzo de 2010 (8)
Febrero de 2010 (3)
Enero de 2010 (1)
Diciembre de 2009 (9)
Noviembre de 2009 (14)
Octubre de 2009 (2)
Septiembre de 2009 (8)
Agosto de 2009 (2)
Julio de 2009 (10)
Junio de 2009 (9)
Mayo de 2009 (10)
Abril de 2009 (9)
Marzo de 2009 (3)
Febrero de 2009 (2)
Enero de 2009 (3)
Noviembre de 2008 (2)
Octubre de 2008 (2)
Septiembre de 2008 (2)
Agosto de 2008 (5)
Julio de 2008 (5)
Junio de 2008 (1)
Mayo de 2008 (3)
Abril de 2008 (2)
Marzo de 2008 (2)
Febrero de 2008 (2)
Enero de 2008 (5)
Noviembre de 2007 (2)
Octubre de 2007 (2)






Copyright © 2007 GuilleSQL, todos los derechos reservados.