使用LINQ查询巨大的CSV和Excel

时间:2014-12-04 14:47:27

标签: c# sql excel linq csv

我正在尝试找到访问巨大的Excel或CSV文件的方法,并执行聚合操作,如Sum,count和某些SQL操作,如Select,group by等。 我知道LINQ可以帮助我做到这一点。我更喜欢使用C#。我的问题是:

1)执行任何查询时,是否会将excel或CSV中的数据加载到内存中?所有文件大约12GB。我问的原因是我不希望应用程序挂起。

2)我可以创建一个Form应用程序,其中我有一个TextArea,其中列出了CSV / Excel的所有列。用户可以选择TextArea中的任何列。我打算使用SELECT,GROUP BY,SUM,AVERAGE等SQL选项。用户可以选择其中一个,并在内部使用LINQ构建查询并获得结果。结果可以存储在文本文件中。

我不确定这是否可行。请建议我这个。我是使用LINQ的新手。 如果通过LINQ无法做到这一点,你能否建议其他方法有效地做到这一点。

提前致谢。

1 个答案:

答案 0 :(得分:1)

记忆不是你的问题 - 性能是。

我完全同意关于将此加载到正确的数据库中的注释,该数据库将对行等进行索引以使其更有效。话虽如此,通过直接阅读CSV,执行您描述的各种查询是可行和直接的(如果非常不可取)。

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

没有现成的HUGE csv文件,我只使用以下标题生成了一个:

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

其中row是行号,Guid只是一个随机guid Grouping1是0到49之间的随机int,Grouping2是0到0之间的随机int 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. 计算所有条目
  2. 计算Group1 == 1
  3. 的所有条目
  4. Sum Metric2,其中Group2 == 1
  5. 使用此代码:

        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分钟。如果这并不能让你把它变成一个正确索引的数据库,那么什么都不会!