En muchas ocasiones necesitamos escribir consultas SQL que obtengan resultados agrupados utilizando la cláusula GROUP BY, algo que se complica cuando en una misma sentencia SQL necesitamos realizar diferentes agrupamientos, y que podemos solventar con las opciones CUBE, ROLLUP, y GROUPING SETS.
Pongamos por ejemplo, que necesitamos obtener la información agrupada por las columnas Pais y Color. Este sencillo ejemplo, lo resolveremos con una cláusula GROUP BY Pais, Color. Fácil y sencillo.
Bien. Imaginemos ahora que necesitamos obtener la información agrupada por las columnas Pais y Color, así como también un total general, y un sub-total por cada Color (indiferentemente del Pais). ¿Qué hacemos? Una opción es escribir varias consultas SQL con las correspondientes cláusulas GROUP BY, y unir todas estas consultas con UNION ALL. Cuando son consultas pequeñas, quizás no sea una gran molestia. Cuando estamos trabajando con grandes consultas SQL (decenas o centenas de líneas), esto empieza a complicarse, ya que no sólo se aumenta el tiempo de codificación inicial, sino que para su mantenimiento, cuando sea necesario meter mano ahí, también nos costará mucho más tiempo. Esto ya no es fácil y sencillo. Esto es un marrón.
Afortunadamente, tenemos a nuestra disposición las opciones CUBE, ROLLUP, y GROUPING SETS, así como las opciones GROUPING y GROUPING_ID, que nos ayudarán en este menester. Empezamos.
GROUP BY CUBE
GROUP BY CUBE genera todas las posibles combinaciones de agrupamiento. Por ejemplo, GROUP BY CUBE (Pais, Color) además de generar las diferentes filas para las agrupaciones por Pais, Color (como una sentencia GROUP BY tradicional), generará también:
- Una fila con el Total General.
- Las diferente filas para las agrupaciones por Pais.
- Las diferentes filas para las agrupaciones por Color.
A continuación se muestra una sentencia SQL de ejemplo, para que podamos observar la sintaxis:
SELECT Pais, Color, SUM(Importe) AS Total FROM dbo.Ventas GROUP BY CUBE(Pais, Color)
|
Alternativamente, también podemos utilizar la sintaxis WITH CUBE. Sin embargo, dicha sintaxis no sigue el standard ISO y desaparecerá en futuras versiones de SQL Server, por lo que debemos evitar utilizarla. En cualquier caso, a continuación se muestra una sentencia SQL de ejemplo:
SELECT Pais, Color, SUM(Importe) AS Total FROM dbo.Ventas GROUP BY Pais, Color WITH CUBE
|
GROUP BY ROLLUP
GROUP BY ROLLUP genera las diferentes combinaciones de agrupamiento de forma jerárquica, por lo que en cierto modo, genera un subconjunto de las combinaciones que genera GROUP BY CUBE. Por ejemplo, GROUP BY ROLLUP (Pais, Color) además de generar las diferentes filas para las agrupaciones por Pais, Color (como una sentencia GROUP BY tradicional), generará también:
- Una fila con el Total General.
- Las diferente filas para las agrupaciones por Pais.
La diferencia con GROUP BY CUBE en este ejemplo particular, es que con ROLLUP no se generan las diferentes filas para las agrupaciones por Color.
A continuación se muestra una sentencia SQL de ejemplo, para que podamos observar la sintaxis:
SELECT Pais, Color, SUM(Importe) AS Total FROM dbo.Ventas GROUP BY ROLLUP (Pais, Color)
|
Alternativamente, también podemos utilizar la sintaxis WITH ROLLUP. Sin embargo, dicha sintaxis no sigue el standard ISO y desaparecerá en futuras versiones de SQL Server, por lo que debemos evitar utilizarla. En cualquier caso, a continuación se muestra una sentencia SQL de ejemplo:
SELECT Pais, Color, SUM(Importe) AS Total FROM dbo.Ventas GROUP BY Pais, Color WITH ROLLUP
|
GROUP BY GROUPING SETS
GROUP BY GROUPING SETS nos permite poder personalizar las diferentes combinaciones de agrupamiento que deseemos. Por ejemplo, GROUP BY GROUPING SETS ( (Pais, Color), (Color), () ) además de generar las diferentes filas para las agrupaciones por Pais, Color (como una sentencia GROUP BY tradicional, que es lo especificado en el primer agrupamiento), generará también:
- Las diferente filas para las agrupaciones por Color (especificado en el segundo agrupamiento).
- Una fila con el Total General (especificado en el tercer agrupamiento).
A continuación se muestra una sentencia SQL de ejemplo, para que podamos observar la sintaxis:
SELECT Pais, Color, SUM(Importe) AS Total FROM dbo.Ventas GROUP BY GROUPING SETS ( (Pais, Color), (Color), () )
|
Las funciones GROUPING y GROUPING_ID
Además de lo ya visto, también podemos utilizar las funciones GROUPING y/o GROUPING_ID, las cuales nos pueden ayudar a identificar las diferentes agrupaciones realizadas en el conjunto de filas resultado de nuestra consulta, en caso de estar utilizando cualquiera de las cláusulas que acabamos de ver: CUBE, ROLLUP, ó GROUPING SETS.
Por ejemplo, si ejecutamos una sentencia GROUP BY CUBE, podemos utilizar la función GROUPING para identificar qué fila es el Total General, así como identificar los diferentes Sub-Totales, y el resto de filas. A continuación se muestra una sentencia SQL de ejemplo, para que podamos observar la sintaxis:
SELECT Pais, Color, SUM(Amount) AS Total, CASE WHEN GROUPING(Pais)=0 AND GROUPING(Color)=1 THEN 'Total Pais' WHEN GROUPING(Pais)=1 AND GROUPING(Color)=0 THEN 'Total Color' WHEN GROUPING(Pais)=1 AND GROUPING(Color)=1 THEN 'Total General' ELSE 'Fila Normal' END AS TipoFila FROM dbo.Ventas GROUP BY CUBE(Pais, Color)
|
Lo mismo ocurre con la función GROUPING_ID, que utilizamos a modo de ejemplo en el siguiente código SQL.
SELECT Pais, Color, SUM(Amount) AS Total, CASE GROUPING_ID(Pais, Color) WHEN 1 THEN 'Total Pais' WHEN 2 THEN 'Total Color' WHEN 3 THEN 'Total General' ELSE 'Fila Normal' END AS TipoFila FROM dbo.Ventas GROUP BY CUBE(Pais, Color)
|
Consideraciones de Rendimiento
En términos generales, no es una buena práctica de rendimiento la utilización de agrupaciones y ordenaciones (GROUP BY / ORDER BY), especialmente en entornos puramente transaccionales, en los que deben ejecutarse simultaneamos gran cantidad de consultas SQL, y dónde debemos mimar mucho estos detalles para no tirar por tierra todo el rendimiento de nuestro motor SQL por problemas típicos de bloqueos (locks y blocks), esperas, efectos colaterales con la configuración del Paralelismo (MDOP), y otros mil problemas de rendimiento de SQL Server que nos podemos encontrar.
Al final, la mayoría de las veces las cosas no son blancas o negras, y en muchas ocasiones acabaremos utilizando sentencias con GROUP BY, sin que esto represente ningún problema. En este tipo de situaciones, podremos aprovechar las ventajas de las cláusulas CUBE, ROLLUP y/o GROUPING SETS.
Despedida y Cierre
Hasta aquí llega el presente artículo. Como hemos visto, las cláusulas CUBE, ROLLUP y GROUPING SETS, pueden sernos de gran ayuda, aunque por supuesto, debemos ser conscientes de que la utilización de las agrupaciones no es suele ser una buena práctica de rendimiento (ojo, hablando en términos generales, en el caso de sistemas puramente transaccionales). Evidentemente, en entornos de Reporting y Data Warehouse, aportan una gran riqueza al lenguaje SQL.
Por último, sólo recordar que WITH CUBE, WITH ROLLUP, y la función GROUPING están disponibles desde SQL Server 2000, mientras que GROUPING SETS y la función GROUPING_ID están disponibles desde SQL Server 2008.
Poco más por hoy. Como siempre, confío que la lectura resulte de interés.