Utilizar funciones de agregación en un DataSet con C# (COUNT, SUM, AVG)
Éste artículo fue escrito en base a una respuesta/solución brindada a la consulta planteada en stackoverflow: How do I use SELECT GROUP BY in DataTable.Select(Expresion)?
[banner id=”1861″]
En dicho hilo la solución que me pareció más interesante fue la siguiente:
public static DataTable GroupBy(string i_sGroupByColumn, string i_sAggregateColumn, DataTable i_dSourceTable) { DataView dv = new DataView(i_dSourceTable); //getting distinct values for group column DataTable dtGroup = dv.ToTable(true, new string[] { i_sGroupByColumn }); //adding column for the row count dtGroup.Columns.Add("Count", typeof(int)); //looping thru distinct values for the group, counting foreach (DataRow dr in dtGroup.Rows) { dr["Count"] = i_dSourceTable.Compute("Count(" + i_sAggregateColumn + ")", i_sGroupByColumn + " = '" + dr[i_sGroupByColumn] + "'"); } //returning grouped/counted result return dtGroup; }
Suponiendo que tenemos un DataTable con dos columnas, de la siguiente manera:
Si queremos hacer un COUNT para saber cuántos registros tiene asociado cada TeamID, haríamos la llamada a nuestra función de la siguiente manera:
DataTable desiredResult = GroupBy("TeamID", "MemberID", dt);
Y el resultado obtenido es el siguiente:
¿Por qué me gustó ésta solución? He tenido la necesidad de mostrar en pantalla cierta información (que tengo en un DataTable) agrupada de distinta forma (para ayudar al usuario a realizar un cuadre de varias formas) y estaba convencido de que podía hacerlo sin tener que hacer más de una llamada a la base de datos.
Me funcionó a la perfección, debo aclarar que en mis pruebas mi DataTable no llegó a tener más de 500 filas y estimo que en un ambiente productivo no llegaría a tener más de 1,000. Por lo que no sé a ciencia cierta cómo se comportaría ésta función teniendo muchas más filas.
EXTRA:
Muy bonito y todo, pero que tal si lo que necesito hacer es un SUM o un AVG? Con muy pequeñas y simples adaptaciones en el código podemos lograr lo que nos planteamos:
public static DataTable Sum(string i_sGroupByColumn, string i_sAggregateColumn, DataTable i_dSourceTable) { DataView dv = new DataView(i_dSourceTable); //getting distinct values for group column DataTable dtSum = dv.ToTable(true, new string[] { i_sGroupByColumn }); //adding column for the row count dtSum.Columns.Add("Sum", typeof(int)); dtSum.Columns.Add("Count", typeof(int)); //looping thru distinct values for the group, counting foreach (DataRow dr in dtSum.Rows) { dr["Sum"] = i_dSourceTable.Compute("Sum(" + i_sAggregateColumn + ")", i_sGroupByColumn + " = '" + dr[i_sGroupByColumn] + "'"); dr["Count"] = i_dSourceTable.Compute("Count(" + i_sAggregateColumn + ")", i_sGroupByColumn + " = '" + dr[i_sGroupByColumn] + "'"); } //returning grouped/counted result return dtSum; } public static DataTable Avg(string i_sGroupByColumn, string i_sAggregateColumn, DataTable i_dSourceTable) { DataView dv = new DataView(i_dSourceTable); //getting distinct values for group column DataTable dtAvg = dv.ToTable(true, new string[] { i_sGroupByColumn }); //adding column for the row count dtAvg.Columns.Add("Avg", typeof(int)); //looping thru distinct values for the group, counting foreach (DataRow dr in dtAvg.Rows) { dr["Avg"] = i_dSourceTable.Compute("Avg(" + i_sAggregateColumn + ")", i_sGroupByColumn + " = '" + dr[i_sGroupByColumn] + "'"); } //returning grouped/counted result return dtAvg; }
En este punto solo hay que tener cuidado con algo, si los campos que queremos sumar u obtener promedio son tipo decimal, debemos de indicarlo al momento de agregar la columna correspondiente al DataTable que devuelve la función como respuesta, podemos usar typeof(decimal) o typeof(int), dependiendo de lo que necesitemos:
dtSum.Columns.Add("Sum", typeof(int));
ó
dtSum.Columns.Add("Sum", typeof(decimal));
La llamada a la función que necesitemos no cambia en nada:
DataTable desiredResult = Sum("TeamID", "MemberID", dt); DataTable desiredResult = Avg("TeamID", "MemberID", dt);
[banner id=”1861″]