使用LINQ查询巨大的CSV和Excel
我试图find方法来访问巨大的Excel或CSV文件,并执行聚合操作,如总和,计数和特定的SQL像操作select,分组等。我意识到的事实,即LINQ可以帮助我做到这一点。 我更喜欢使用C#。 我的问题是:
1)在执行任何查询时,将excel或CSV中的数据加载到内存中? 所有文件大约12GB。 我问的原因是我不希望应用程序挂起。
2)我可以创build一个表单应用程序,其中我有一个TextArea其中列出了CSV / Excel的所有列。 用户可以select从TextArea中select任何列。 我打算有SELECT,GROUP BY,SUM,AVERAGE等许多SQL选项。 用户可以select其中之一,并在内部使用LINQ构build查询并获得结果。 结果可以存储在一个文本文件中。
我不确定这是否可行。 请在此build议我。 我是使用LINQ的新手。 如果这不可能通过LINQ,请您build议其他方法来有效地做到这一点。
提前致谢。
内存不是你的问题 – 性能是。
我完全同意关于把这个加载到一个合适的数据库的索引的意见,索引行等,使它更有效率。 如此一来,通过直接阅读CSV来执行您描述的各种查询是可能和简单的(如果非常不明智的话)。
我认为在这个任务中testingLINQtoCSV会很有趣。
没有任何准备好的巨大的csv文件手,我只是用下面的标题生成一个:
"row,Guid,Grouping1,Grouping2,Metric1,Metric2"
其中row
是行号, Guid
只是一个随机guid Grouping1
是0到49之间的随机int, Grouping2
是0到50,000之间的随机int, Metric1
和Metric2
分别是随机整数和双精度。 基本上只是一些随机数据。
然后我生成了一个99999999行的7Gb +文件…(我瞄准了100,000,000,但是在某个地方打了我的零索引盲点!)
所以好消息是LINQtoCSV会高兴地parsing这个坏男孩,而不必将文件加载到内存中。 只需在csv中创build一行代表的类:
class Entry { [CsvColumn(FieldIndex = 1, Name="row")] public long Id { get; set; } [CsvColumn(FieldIndex = 2)] public string Guid { get; set; } [CsvColumn(FieldIndex = 3)] public int Grouping1 { get; set; } [CsvColumn(FieldIndex = 4)] public int Grouping2 { get; set; } [CsvColumn(FieldIndex = 5)] public int Metric1 { get; set; } [CsvColumn(FieldIndex = 6)] public double Metric2 { get; set; } }
然后将条目读入IEnumerable
:
IEnumerable<Entry> ReadEntries() { CsvFileDescription inputFileDescription = new CsvFileDescription { SeparatorChar = ',', FirstLineHasColumnNames = true }; CsvContext ctx = new CsvContext(); return ctx.Read<Entry>("test.csv", inputFileDescription); }
并离开你去。
你现在已经有了一个function齐全的IEnumerable,可以用它来查询所有的GroupBy(),Select(),Sum(),Count()等等和Linq的所有其他类似SQL的乐趣。
只是一个小问题….
它不是快!
使用这个简单的LINQtoCSV设置,我并行运行了3个简单的linq查询
- 统计所有条目
- 统计Group1 == 1的所有条目
- Sum Metric2其中Group2 == 1
使用这个代码:
static IEnumerable<Entry> entries; static void Main(string[] args) { entries = ReadEntries(); var tasks = new List<Task> {timeEntryCount,timeEntryCountWhereG1_equals_1,timeMetric2SumWhereG2_equals_1}; tasks.ForEach(t => t.Start()); Task.WaitAll(tasks.ToArray()); Console.WriteLine("Entry count took " + timeEntryCount.Result); Console.WriteLine("Entry count where G1==1 took " + timeEntryCountWhereG1_equals_1.Result); Console.WriteLine("Metric1 sum where G2==1 took " + timeMetric2SumWhereG2_equals_1.Result); Console.ReadLine(); } static Task<TimeSpan> timeMetric2SumWhereG2_equals_1 = new Task<TimeSpan>(() => { DateTime start = DateTime.Now; double sum = entries .Where(e => e.Grouping2 == 1) .Sum(e=>e.Metric2); Console.WriteLine("sum: " + sum); DateTime end = DateTime.Now; return end - start; },TaskCreationOptions.LongRunning); static Task<TimeSpan> timeEntryCountWhereG1_equals_1 = new Task<TimeSpan>(() => { DateTime start = DateTime.Now; long count = entries .Where(e=>e.Grouping1==1) .Count(); DateTime end = DateTime.Now; Console.WriteLine("countG1: " + count); return end - start; }, TaskCreationOptions.LongRunning); static Task<TimeSpan> timeEntryCount = new Task<TimeSpan>(() => { DateTime start = DateTime.Now; long count = entries.Count(); Console.WriteLine("CountAll: " + count); DateTime end = DateTime.Now; return end - start; }, TaskCreationOptions.LongRunning);
现在可以肯定的是,这种狡猾的平行主义不是testing这个最好的方法,但我只是从臀部开始拍摄。
然而,结果是真的非常难看,我的合理快SSD配备笔记本电脑:
countG1: 2003023 CountAll: 99999999 sum: 1236810295.16543 Entry count took 00:25:26.3767852 Entry count where G1==1 took 00:24:41.9855814 Metric1 sum where G2==1 took 00:25:30.9080960
这大概是25分钟来查询文件。 如果这不促使你把这个数据库转换成索引数据库,