使用 LINQ 查询庞大的 CSV 和 Excel
本文关键字:CSV Excel LINQ 查询 使用 | 更新日期: 2023-09-27 18:33:11
我正在尝试找到访问大型Excel或CSV文件并执行聚合操作的方法,例如总和,计数和某些SQL操作,例如选择,分组依据等。我知道 LINQ 可以帮助我做到这一点。我更喜欢使用 C#。我的问题是:
1( 在执行任何查询时,来自 excel 或 CSV 的数据是否会加载到内存中?所有文件都在 12GB 左右。我问的原因是我不希望应用程序挂起。
2(我可以创建一个表单应用程序吗,其中我有一个文本区域,其中列出了CSV/Excel的所有列。用户可以选择从文本区域中选择任何列。我计划有SQL选项,如选择,分组依据,总和,平均和许多其他选项。用户可以选择其中之一,并在内部使用 LINQ 构建查询并获取结果。结果可以存储在文本文件中。
我不确定这是否可行。请就此建议我。我是使用 LINQ 的新手。如果通过 LINQ 无法做到这一点,您能否建议其他方法来有效地做到这一点。
提前谢谢。
内存不是你的问题 - 性能才是。
我全心全意地同意有关将其加载到适当的数据库中的评论,该数据库将索引行等,以使其更有效率。话虽如此,通过直接阅读 CSV 来执行您描述的各种查询是可能且直接的(如果非常不可取(。
我认为在这个任务中测试 LINQtoCSV 会很有趣。
手头没有任何现成的巨大 csv 文件,我只是生成了一个具有以下标题的文件:
"row,Guid,Grouping1,Grouping2,Metric1,Metric2"
其中row
是行号,Guid
只是一个随机的GUID Grouping1
是0到49之间的随机整数,Grouping2
是0到50,000之间的随机整数,Metric1
和Metric2
分别是随机整数和双精度。基本上只是一些随机数据。
然后我生成了一个包含 99999999 行的 7Gb+ 文件......(我的目标是 100,000,000,但在某个地方遇到了我的零索引盲点!
所以好消息是 LINQtoCSV 会很乐意解析这个坏男孩,而不必将文件加载到内存中。只需在 csv 中创建由一行表示的类:
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);
}
然后你走了。
您现在有一个功能齐全的 IEnumerable,可以使用所有 GroupBy((、Select((、Sum((、Count(( 等以及 Linq 的所有其他类似 SQL 的乐趣进行查询。
只是一个小问题。
它不快!
使用这个简单的 LINQtoCSV 设置,我并行运行了 3 个简单的 linq 查询
- 计算所有条目
- 计算组 1==1 的所有条目
- 总和指标 2,其中组 2==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);
现在诚然,这种狡猾的并行性不是测试它的最佳方式,但我只是从臀部射击。
然而,在我配备相当快的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 分钟。如果这不能说服您将其放入正确索引的数据库中,那么什么都不会!