在C#中,如何按照日期时间将数据表中的行分组?

4
我有一张大型数据表格(50万-100万行),不详细说明,这是因为最终用户需要/希望能够查看所有数据。这在本地服务器上,所以带宽等对我来说不是问题。
DataTable中有一个DateTime字段,我需要对其进行分组,让我解释一下我的意思...这可能与您从其他问题上看到的不同。
        var table = new DataTable();
        table.Columns.Add("EventTime", typeof(DateTime));
        table.Columns.Add("Result", typeof(String));
        table.Columns.Add("ValueOne", typeof(Int32));
        table.Columns.Add("ValueTwo", typeof(Int32));
        table.Rows.Add("2012-02-06 12:41:45.190", "A", "7", "0");
        table.Rows.Add("2012-02-06 12:45:41.190", "B", "3", "89");
        table.Rows.Add("2012-02-06 12:59:41.190", "C", "1", "0");
        table.Rows.Add("2012-02-06 13:41:41.190", "D", "0", "28");
        table.Rows.Add("2012-02-06 17:41:41.190", "E", "0", "37");
        table.Rows.Add("2012-02-07 12:41:45.190", "F", "48", "23");

我希望上述表格能够分组,以便我获得“ValueOne”列的总和和“ValueTwo”列的平均值。我需要分组有一定的灵活性,以便我可以指定按分钟(只有第一行和最后一行会被分组,其余行仅提供它们的值)或按天(除了最后一行外,所有行都会被分组成一行),等等。
我尝试过几次,但没有成功。我的LINQ知识不是很好,但我认为我应该能够完成这个任务!
请注意:DataTable已经在计算机上进行了计算/视图,无法更改,因此说“停止愚蠢的想法,使用SQL过滤器!”也是一个有效的答案,但对我来说毫无用处! :-D
另外,请注意,如果您在标题中错过了它,我需要使用C# - 我正在使用.NET 4.0...
提前感谢,假设您决定帮助我! :-)

@StriplingWarrior:为什么LINQ-To-SQL查询比LINQ-To-DataSet查询更容易? - Tim Schmelter
Stripling - 你能详细解释一下你刚才说的吗?我并不反对提高性能!只是每当我像这样提出问题时,人们通常会对我大喊大叫,说我正在加载过多数据,而且我很蠢! :) 请解释一下,我真的很想理解你刚才说的话! - Faraday
@StriplingWarrior:为什么 row.Field<DateTime>("EventTime") 是一种扭曲或索引?(更不用说是一个类型化的 DataSet 了) - Tim Schmelter
@TimSchmelter: row.Field<DateTime>("EventTime") 感觉要做很多无用功,相比之下 event.EventTime 简单明了。它需要进行转换和使用 "magic string" 字符串值。它是索引器,因为我正在请求行中指定 "EventTime" 索引处的值,而且它不是类型安全的,因为如果您更改了 "EventTime" 字段的类型,编译器不会报错。我不清楚类型化数据集与此有何关系,但我愿意接受启示。 - StriplingWarrior
@user1311339:算了吧,当我回答时,我意识到在其中一个框架中实现这个功能会更加复杂,因为你试图从DateTime值中获取单独的部分,这需要一些特殊的方法调用。 - StriplingWarrior
显示剩余3条评论
4个回答

5

其他三个答案很接近,但正如你所指出的,它们将发生在同一分钟内的事件分组,而不是在同一秒钟内发生的事件,这就是你想要的。试试这个:

var query = from r in table.Rows.Cast<DataRow>()
        let eventTime = (DateTime)r[0]
        group r by new DateTime(eventTime.Year, eventTime.Month, eventTime.Day, eventTime.Hour, eventTime.Minute, eventTime.Second)
            into g
        select new {
                g.Key,
                Sum = g.Sum(r => (int)r[2]),
                Average = g.Average(r => (int)r[3])
            };

您可以调整传递给DateTime构造函数的信息,以按不同的时间部分进行分组。


请明确一下,您是说他们的答案将把03/04/2012 10:00:01和11/10/2099 10:00:01分为一组吗? - Faraday
1
是的,那就是我想说的。 - David Nelson
2
我的错,没有注意到。我混淆了方法语法和推导式语法。我再次更新,并检查了它是否可以编译通过。 - David Nelson
1
构造一个新的DateTime,省略小时后面的所有内容,就像我的示例一样。 - David Nelson
1
键是按照分组表达式计算得出的值。如果您想选择其他属性,则需要通过将分组表达式设置为匿名类型来包含它们:new { EventTime = new DateTime(...), Name = (string)r[1] }。 - David Nelson
显示剩余5条评论

1

你需要改变的唯一事情就是你想要分组的属性。

var query = from x in DataSource
            group x by x.EventTime.Minute into x
            select new
            {
              Unit = x.Key,
              SumValueOne = x.Sum(y => y.ValueOne),
              AverageValueTwo = x.Average(y => y.ValueTwo), 
            };

我真的很喜欢这个答案的样子...只是我把它剪切/粘贴到Visual Studio中,以便确切地了解你在做什么,但它不喜欢DataSource是一个DataTable,如果我放置DataTable.Rows,那么它会抱怨更多!希望你知道我的意思... - Faraday

1

类似这样的代码应该可以工作:

DataTable dt = GetDataTableResults();

var results = from row in dt.AsEnumerable()
              group row by new { EventDate = row.Field<DateTime>("EventTime").Date } into rowgroup
              select new
              {
                  EventDate = rowgroup.Key.EventDate,
                  ValueOne = rowgroup.Sum(r => r.Field<int>("ValueOne")),
                  ValueTwo = rowgroup.Average(r => r.Field<decimal>("ValueTwo"))
              };  

我可能漏掉了非常明显的东西,但是它如何知道按照日期时间的哪个部分进行分组? - Faraday
在这个例子中,它仅按日期分组,忽略时间。如果您需要不同的分组标准,可以将 row.Field<DateTime>("EventTime").Date 更改为您需要的内容。 - James Johnson
如果我选择按秒分组,那么这是否会忽略年/月/日,只是说“秒数相同,所以我将它们分组”?还是它也考虑了其他字段? - Faraday
我如何获取完整的 EventTime 而不是被截断的?我的意思是,如果我选择了小时,则不想要小时数的 int,我想要完整的日期,直到小时... - Faraday
我会将其格式化为字符串并在字符串上进行分组。您可以使用“MM/dd/yyyy HH”作为格式(抱歉,我是在手机上操作)。 - James Johnson

0
这是您基线代码的样子:
var query = table.Rows.Cast<DataRow>()
    .GroupBy(r => ((DateTime)r[0]).Second)
    .Select(g => new
                 {
                    g.Key, 
                    Sum = g.Sum(r => (int)r[2]),
                    Average = g.Average(r => (int)r[3])
                 });

为增加灵活性,您可以像这样做:
IEnumerable<IGrouping<object, DataRow>> Group(IEnumerable<DataRow> rows, GroupType groupType)
{
    // switch case would be preferable, but you get the idea.
    if(groupType == GroupType.Minutes) return rows.GroupBy(r => ((object)((DateTime)r[0]).Minute));
    if(groupType == GroupType.Seconds) return rows.GroupBy(r => ((object)((DateTime)r[0]).Second));
    ...
}

var baseQuery = table.Rows.Cast<DataRow>();
var grouped = Group(baseQuery, groupType);
var query = grouped
    .Select(g => new
                 {
                    g.Key, 
                    Sum = g.Sum(r => (int)r[2]),
                    Average = g.Average(r => (int)r[3])
                 });

这样做不会完全忽略日期的其他部分吗? - Faraday
我的意思是,这样不会忽略日期,只会说“第二个相同,所以我将它分组”吗?此外,我认为这不是您建议的“对Linq-to-SQL或实体上下文执行LINQ查询”的答案... - Faraday
@user1311339:那只是为了让你知道从哪里开始。请看我的更新,了解如何根据参数更改分组方式。 - StriplingWarrior
哦,哇...好的,最后一个问题...... Query现在是什么数据类型,我如何将它变成DataTable(我们使用的图表控件需要一个DataTable :-S抱歉打扰您,感谢您已经给出的建议! - Faraday
@user1311339:query现在是一个匿名类型的IEnumerable<>,具有Key、Sum和Average属性。你需要自己创建一个datatable。是的,我实现的方式只适用于查找哪些月份最繁忙,例如,而你需要将我的策略与David Nelson的GroupBy结构相结合,以实际按月分组,就像你想要的那样。 - StriplingWarrior

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接