使用 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 无法做到这一点,您能否建议其他方法来有效地做到这一点。

提前谢谢。

使用 LINQ 查询庞大的 CSV 和 Excel

内存不是你的问题 - 性能才是。

我全心全意地同意有关将其加载到适当的数据库中的评论,该数据库将索引行等,以使其更有效率。话虽如此,通过直接阅读 CSV 来执行您描述的各种查询是可能且直接的(如果非常不可取(。

我认为在这个任务中测试 LINQtoCSV 会很有趣。

手头没有任何现成的巨大 csv 文件,我只是生成了一个具有以下标题的文件:

"row,Guid,Grouping1,Grouping2,Metric1,Metric2"

其中row是行号,Guid只是一个随机的GUID Grouping1是0到49之间的随机整数,Grouping2是0到50,000之间的随机整数,Metric1Metric2分别是随机整数和双精度。基本上只是一些随机数据。

然后我生成了一个包含 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. 计算所有条目
  2. 计算组 1==1 的所有条目
  3. 总和指标 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 分钟。如果这不能说服您将其放入正确索引的数据库中,那么什么都不会!