希望在Excel中使用VBA读取非常大的CSV文件,并创建一个小子集的输出文件。

11

我有一个包含120万条文本记录的csv文件。其中字母数字字段用引号括起来,日期/时间或数值字段不用。

例如 "Fred","Smith",01/07/1967,2,"7, The High Street","Anytown","Anycounty","LS1 7AA"

我想在Excel中编写一些VBA代码(这是我唯一熟练使用的工具),逐条读取CSV记录,执行检查(实际上是对最后一个字段“邮政编码”进行检查),然后将1.2m记录的一个小子集输出到新的输出文件中。

我知道如何打开两个文件、读取记录、对数据进行处理并将其写出(我只需输出带有表示异常类型的前缀的输入记录)。

但我不知道如何在VBA中正确解析CSV。我不能简单地扫描文本并搜索逗号,因为文本中有时会有逗号(这就是为什么文本字段是以文本限定符为界定符的原因)。

是否有一个神奇的命令可以让我快速获取记录中第n个字段的数据?

我想要的是 s_work = field(s_input_record,5),其中5是CSV中的字段号。

非常感谢, C

7个回答

8
不过 VBScript 也可以,不过这个方法同样适用于 Excel:
Set cn = CreateObject("ADODB.Connection")

'Note HDR=Yes, that is, first row contains field names '
'and FMT delimted, ie CSV '

strCon="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\Docs\;" _
& "Extended Properties=""text;HDR=Yes;FMT=Delimited"";"

cn.open strcon

'You would not need delimiters ('') if last field is numeric: '    
strSQL="SELECT FieldName1, FieldName2 INTO New.csv FROM Old.csv " _
& " WHERE LastFieldName='SomeTextValue'"

'Creates new csv file
cn.Execute strSQL

我以前使用过类似的技术来完成同样的事情。 - Mitch Wheat
+1 这是处理如此大的数据集的最佳解决方案,我以前曾经处理过聚合数据。 - Our Man in Bananas
@Fionnuala,我正在使用您的答案将CSV文件读入内存。 然而,正如我在这个问题中指出的那样,我遇到了280个字符的限制:http://stackoverflow.com/questions/34230062/excel-vba-reading-csv-file-with-large-multiline-values-using-adodb-with-jet-or-a 您是否遇到过这种情况,并且有什么办法可以克服这个限制? - izce

8
以下代码应该能解决问题。我没有面前的Excel,所以我没有测试过,但这个思路是正确的。
如果这样做变得太慢,我们可以考虑改进效率的方法。
Sub SelectSomeRecords()
    Dim testLine As String

    Open inputFileName For Input As #1
    Open outputFileName For Output As #2

    While Not EOF(1)
        Line Input #1, testLine
        If RecordIsInteresting(testLine) Then
            Print #2, testLine
        End If
    Wend

    Close #1
    Close #2
End Sub

Function RecordIsInteresting(recordLine As String) As Boolean
    Dim lineItems(1 to 8) As String

    GetRecordItems(lineItems(), recordLine)

    ''// do your custom checking here:
    RecordIsInteresting = lineItems(8) = "LS1 7AA"
End Function

Sub GetRecordItems(items() As String, recordLine as String)
    Dim finishString as Boolean
    Dim itemString as String
    Dim itemIndex as Integer
    Dim charIndex as Long
    Dim inQuote as Boolean
    Dim testChar as String

    inQuote = False
    charIndex = 1
    itemIndex = 1
    itemString = ""
    finishString = False

    While charIndex <= Len(recordLine)
        testChar = Mid$(recordLine, charIndex, 1)

        finishString = False

        If inQuote Then
            If testChar = Chr$(34) Then
                inQuote = False
                finishString = True
                charIndex = charIndex + 1 ''// ignore the next comma
            Else
                itemString = itemString + testChar
            End If
        Else
            If testChar = Chr$(34) Then
                inQuote = True
            ElseIf testChar = "," Then
                finishString = True
            Else
                itemString = itemString + testChar
            End If
        End If

        If finishString Then
            items(itemIndex) = itemString
            itemString = ""
            itemIndex = itemIndex + 1
        End If

        charIndex = charIndex + 1
    Wend
End Sub

4

这并不直接回答你的问题,但 grep (或者Windows的等效工具) 对于这个问题非常适用,例如:

grep -e <regex_filter> foo.csv > bar.csv

1
Windows平台下的grep工具有三种:WinGrep(http://www.wingrep.com/)、PowerGrep(http://www.powergrep.com/)和GNU Grep for Windows(http://gnuwin32.sourceforge.net/packages/grep.htm)。 - Hank Gay
1
鉴于grep以某种形式可用,它的全部目的是打印与正则表达式匹配的行,这似乎足以满足问题中提到的过滤需求,因此它似乎是自然适合的选择。 - Hank Gay

3
我使用了以上代码的导数,成功地在Excel的VBA中打开了任意的CSV文件。

选项显式
公共CN作为连接
公共子过程DoIt()
Dim strcon As String
Dim strsql As String
Dim rs作为记录集

Set cn = CreateObject("ADODB.Connection")

strcon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\bin\HomePlanet\;" _
& "Extended Properties=""text;HDR=Yes;FMT=Delimited"";"

cn.Open strcon

strsql = "SELECT * FROM astuname.csv "
Set rs = New ADODB.Recordset
rs.Open strsql, cn
DoEvents '在此暂停以检查对象和属性 rs.Close
End Sub

rs(记录集)具有一组字段,其中包含Count属性。每个字段都有一个Type属性。

您可以按顺序引用这些字段...

Debug.Print rs.Fields(rs.Fields.Count - 1).Type

这样足够了吗?

如果不行,请发布输入文件的前几行,我会继续完成其余部分。


2

请查看Excel帮助中的输入 #语句

示例用法如下:

Input #fnInput, s_Forename, s_Surname, dt_DOB, i_Something, s_Street, s_Town, s_County, s_Postcode

然后使用Write #语句再次写出匹配的记录。

唯一的问题可能是输出中的日期格式将变为#1967-07-01#,但这种格式是明确无误的,不像01/07/1967,它在英国表示7月1日,在美国表示1月7日。如果您需要保留日期的格式,则将其作为字符串写出:

s_DOB = Format(dt_DOB, "dd/mm/yyyy")

2

在Excel中,您可以使用VBA逐行执行任何操作,在Access中也可以使用VBA进行所有这些操作;并且由于它是数据库而不是电子表格,因此还有更多的功能。如果您无法使用Access,这将是一件遗憾。

与处理逻辑工作表、行和列相比,处理逻辑表、记录和字段要容易得多。

对于输入,为什么“/ Data /导入外部数据/文本/ csv”无法正常工作?输入是否不是真正的可移植csv格式?


Excel 中无法处理 120 万行数据,但可以使用基于 Jet 引擎的 ADO 操作数据,这也是我回复的原因。 - Fionnuala
1
好的 - 所以确认一下 - Access 不可用?无论如何,如果您可以使用 ADO 访问 Jet,则还可以使用 Excel VBA 从 Excel 中打开 Access mdb 数据库并将数据存储在那里 - 您甚至不需要安装 Access。 - dkretz

1
我建议您查看正则表达式库(您应该在“工具...引用”中看到它,名称为“Microsoft VBScript Regular Expressions 5.5”或非常相似)。
此位置有Reg Exp和相当全面的逐字符示例:http://www.xbeat.net/vbspeed/c_ParseCSV.php。请注意,Regexp版本要短得多!
祝玩得开心...

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