如何对DataTable进行动态分组和动态统计[C#]
来源:互联网 发布:apache下载安装 win7 编辑:程序博客网 时间:2024/05/21 14:04
C#的DataTable中,没有提供分组统计的功能,因此需要对此进行功能扩展。 方法无非是两种,一种是直接利用DataTable的PrimaryKey对DataTable原始数据进行分组统计,一种是使用linq的group by功能进行统计。
使用linq的好处是代码简洁。但由于实际需求中,分组的字段是动态的,而且被统计的字段也是动态,每个被统计字段的统计算法也不同,简单的linq无法满足此实际需求。
我们假设原始的表格如下所示:
dt = new DataTable();
dt.Columns.AddRange(new DataColumn[] {
new DataColumn("A1", typeof(double)),
new DataColumn("A2", typeof(double)),
new DataColumn("A3", typeof(double)),
new DataColumn("S1", typeof(double)),
new DataColumn("S2", typeof(double)),
new DataColumn("S3", typeof(double)),
new DataColumn("S4", typeof(double))
});
dt.Rows.Add(new object[] { 1, 1, 1, 1, 1, 1, 1 });
dt.Rows.Add(new object[] { 1, 1, 1, 2, 2, 2, 2 });
dt.Rows.Add(new object[] { 1, 2, 1, 3, 3, 3, 3 });
dt.Rows.Add(new object[] { 1, 2, 1, 4, 4, 4, 4 });
dt.Rows.Add(new object[] { 1, 2, 2, 5, 5, 5, 5 });
dt.Rows.Add(new object[] { 1, 2, 2, 6, 6, 6, 6 });
dt.Rows.Add(new object[] { 1, 2, 2, 7, 7, 7, 7 });
需要进行的查询如下所示:
DataTable result = new DataTableGroupBy().GroupByLinq(dt, new string[] { "A1", "A2", "A3" },
new string[] { "S1", "S2", "S3", "S4" },
new string[] { "AVG", "MAX", "MIN", "COUNT" });
Assert.AreEqual(result.Rows[0][3].ToString(), "1.5");
Assert.AreEqual(result.Rows[0][4].ToString(), "2");
Assert.AreEqual(result.Rows[0][5].ToString(), "1");
Assert.AreEqual(result.Rows[0][6].ToString(), "2");
Assert.AreEqual(result.Rows[2][3].ToString(), "6");
Assert.AreEqual(result.Rows[2][4].ToString(), "7");
Assert.AreEqual(result.Rows[2][5].ToString(), "5");
Assert.AreEqual(result.Rows[2][6].ToString(), "3");
即以A1,A2,A3进行分组,对S1,S2,S3,S4进行统计,它们各自的统计算法均不同。
那么如何实现?
首先看如何实现对动态字段的分组。一个基本的思路是,在分组时,利用linq的key信息,把每个分组的A1,A2,A3进行字符串连接,形成一个字符串key,然后再对结果集中的key进行拆分,得到分组中的A1,A2和A3。
private string GroupData(DataRow dataRow)
{
StringBuilder builder = new StringBuilder();
builder.Remove(0, builder.Length);
foreach (string field in groupByFields)
{
builder.Append(dataRow[field].ToString() + ";");
}
string key = builder.ToString();
return key.Remove(key.Length - 1);
}
再看如何实现对动态字段的统计。在上述思路的基础上,只需要把结果集中的每个分组的item数据得到,就能够形成一个新的DataTable,对其中的统计字段,可以使用DataTable的Compute方法进行计算。Compute方法,只需要提供一个计算的表达式字符串即可,这样就可以根据输入的算法,动态生成Compute的参数。
private void FillResultStatistics(ref DataRow resultRow, DataTable temp)
{
for (int i = 0; i < statisticsFields.Length; i++)
{
string statisticsFieldCaption = GetStatisticsCaption(statisticsFields[i], methods[i]);
resultRow[statisticsFieldCaption] = temp.Compute(statisticsFieldCaption, "");
if (methods[i] == "AVG")
{
object obj = resultRow[statisticsFieldCaption];
if (obj.ToString() != "")
{
double t = Math.Round(Convert.ToDouble(obj), 2);
resultRow[statisticsFieldCaption] = t;
}
}
}
}
在此基本思路基础上,形成的完整计算过程如下:
public DataTable GroupByLinq(DataTable source, string[] groupByFields, string[] statisticsFields, string[] methods)
{
TimeSpan start = new TimeSpan(DateTime.Now.Ticks);
Init(groupByFields, statisticsFields, methods);
DataTable result = new DataTable();
AddGroupByFieldsHead(ref result);
AddStatisticsFieldsHead(ref result);
EnumerableDataRowList<DataRow> enumerableRowCollection = new EnumerableDataRowList<DataRow>(source.Rows);
Func<DataRow, String> groupingFunction = GroupData;
var groupedDataRows = enumerableRowCollection.GroupBy(groupingFunction);
foreach (var keys in groupedDataRows)
{
DataRow resultRow = result.NewRow();
FillResultGroupBy(ref resultRow, keys.Key.Split(';'));
DataTable temp = source.Clone();
ChangeDataType(ref temp);//因原始数据从csv中读出,默认都是string,所以需要转换下,否则求AVG会出异常。
foreach (var item in keys)
{
temp.ImportRow(item);
}
FillResultStatistics(ref resultRow, temp);
result.Rows.Add(resultRow);
}
TimeSpan end = new TimeSpan(DateTime.Now.Ticks);
double allTime = end.Subtract(start).Duration().TotalSeconds;
return result;
}
internal class EnumerableDataRowList<T> : IEnumerable<T>, IEnumerable
{
IEnumerable dataRows;
internal EnumerableDataRowList(IEnumerable items)
{
dataRows = items;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
foreach (T dataRow in dataRows)
yield return dataRow;
}
IEnumerator IEnumerable.GetEnumerator()
{
IEnumerable<T> iEnumerable = this;
return iEnumerable.GetEnumerator();
}
}
另外一种不使用linq的方法如下。其基本思路是,遍历DataTable中的每一行,查看其PrimaryKey是否已经在结果集的DataTable中,如果没有,则在原始DataTable中,Select所有此PrimaryKey的行,对这些行进行Compute,然后把计算结果放入结果集的DataTable中。
public DataTable GroupByImp(DataTable source, string[] groupByFields, string[] statisticsFields, string[] methods)
{
Init(groupByFields, statisticsFields, methods);
DataTable result = new DataTable();
AddGroupByFieldsHead(ref result);
AddStatisticsFieldsHead(ref result);
result.PrimaryKey = CreatePrimary(result);
foreach (DataRow row in source.Rows)
{
DataRow complete = result.Rows.Find(CreateObjects(row));
if (complete == null)
{
DataRow[] rows = source.Select(CreateSelect(row));
DataTable temp = source.Clone();
for (int i = 0; i < rows.Length; i++)
{
temp.Rows.Add(rows[i].ItemArray);
}
DataRow resultRow = result.NewRow();
FillResultGroupBy(ref resultRow, row);
FillResultStatistics(ref resultRow, temp);
result.Rows.Add(resultRow);
}
}
return result;
}
其实,还可以使用另外一种思路,就是直接把csv数据,bulk insert到数据库中,直接使用select A1,A2,A3,AVG(S1),MAX(S2),MIN(S3),COUNT(S4) from a group by A1,A2,A3进行查询。在此不展开讨论。
对DataTable进行Groupby的查询,还需要满足一定的性能要求。
利用linq的性能测试结果大致如下(不能保证此性能测试结果可以在其他场景下适用):
75484行70列的DataTable(原始文件大约20.8M的csv),对2个字段进行分组,对4个字段进行统计,大约需要1.8s。
19124行70列的DataTable(原始文件大约5.41M的csv),对2个字段进行分组,对4个字段进行统计,大约需要0.6s。
可以看出,此性能不是非常的理想,因此主要看数据量大小,如果数据量控制在2w行以内,0.6s的结果,对于数据展示来讲,基本是可以接受的。
如果需要对10w以上的DataTable进行分组,建议还是先对原始数据进行Filter,再在小结果集上进行Group by。
- 如何对DataTable进行动态分组和动态统计[C#]
- 对datatable进行分组操作
- datatable使用groupby进行分组统计
- datatable使用groupby进行分组统计
- C# datatable使用groupby进行分组统计
- 如何对DataTable 进行检索和排序
- 如何对DataTable进行检索和排序
- 如何对DataTable进行检索和排序
- 如何对DataTable进行检索和排序
- 如何对DataTable进行检索和排序
- 对已经得到的DataTable进行分组
- Linq to DataTable之动态分组
- 用linq对datatable进行分组查询并返回datatable
- C# 对数组进行分组统计
- 【转】如何避免IE对动态页面进行缓存?
- 如何动态对action bar中的items进行操作
- c# datatable 分组与统计
- 如何操作datatable 代替动态行转列
- 使用AsyncHttpClient 框架提交数据
- 仿魅族数字图案解锁
- STM32系列第8篇--串口配置步骤
- 集合-3
- 欢迎使用CSDN-markdown编辑器
- 如何对DataTable进行动态分组和动态统计[C#]
- blueGreen develop , 敏捷开发
- 【小镇的技术天梯】从头写数据结构,C语言实现双向链表
- linux下安装svn
- Google cardBoard Android的两个jar包,以及Demo
- JavaScrit常用的简单交互
- Mybatis使用generator自动生成映射配置文件信息
- 探究imageNamed 与imageWithContentsOfFile加载图片本质区别
- android 补间动画 属性动画 总结