VBA 搜索已关闭的工作簿以获取价值?

Posted

技术标签:

【中文标题】VBA 搜索已关闭的工作簿以获取价值?【英文标题】:VBA search closed workbook(s) for value? 【发布时间】:2017-04-20 09:06:54 【问题描述】:

我正在尝试在文件夹(和子文件夹)中搜索所有 Excel 工作簿以获取值。

我的excel工作簿所在的文件夹结构是这样的:

destPath = "G:\WH DISPO\(3) PROMOTIONS\(18) Food Specials Delivery Tracking\Archive\"

然后在我的存档文件夹中有各种子文件夹,例如

+ 2017
- April
- May

+ 2016
- April
- May

工作簿的名称可能都不同,因此代码可能需要使用通配符 *.xlsm 之类的东西

这是我目前所拥有的:

Sub Search()
Dim srcWorkbook As Workbook
    Dim destWorkbook As Workbook
    Dim srcWorksheet As Worksheet
    Dim destWorksheet As Worksheet
    Dim SearchRange As Range
    Dim destPath As String
    Dim destname As String
    Dim destsheet As String
    Set srcWorkbook = ActiveWorkbook
    Set srcWorksheet = ActiveSheet
    Dim vnt_Input As String

    vnt_Input = Application.InputBox("Please Enter Client Name", "Client Name")

    destPath = "G:\WH DISPO\(3) PROMOTIONS\(18) Food Specials Delivery Tracking\Archive\"
    destname = "*.xlsm"


    On Error Resume Next
    Set destWorkbook = ThisWorkbook
    If Err.Number <> 0 Then
    Err.Clear
    Set wbTarget = Workbooks.Open(destPath & destname)
    CloseIt = True
    End If

    For Each c In wbTarget.Sheets(1).Range("A:Q") 'No need for the .Cells here

       If InStr(c, vnt_Input) > 0 Then 'vnt_Input is a variable that holds a string, so you can't put quotes around it, or it will search the string for "vnt_Input"

          MsgBox "Found"
       End If
    Next c

End Sub

每个工作簿中的范围应始终保持不变。

我正在尝试一些简单的事情,例如在找到值时显示一条消息。但目前,尽管工作簿中存在价值,但我没有得到任何结果/没有消息。

我在这一行得到一个对象需要错误:

For Each c In wbTarget.Sheets(1).Range("A:Q") 'No need for the .Cells here

请谁能告诉我哪里出错了?

编辑:

我可以将消息框更改为 for each loop 以列出每个结果,如下所示:

Dim i As Integer
For i = 20 To 100

For Each rngFound In rngFound

ThisWorkbook.ActiveSheet.Range("E" & i).Value = "1 Result found for " & rngFound & " in " & wbTarget.Path & "\" & wbTarget.Name & ", on row " & rngFound.Address

Next rngFound

Next i

想要的结果

【问题讨论】:

在顶部写Option Explicit,然后尝试调试它。你必须定义 CloseItc 可能还有别的东西。 ***.com/questions/1139321/… 将所有位置收集在一个字符串中并在最后打印它们可能会更好,或者您是否希望每次都停下来并在找到值后做一些事情?如果这是您需要的功能,则很难停止中间代码并更新工作表。 @tompreston 在功能方面我只想显示一条消息,给出工作簿的名称和文件路径以及其中的值 【参考方案1】:

您的代码设置方式不起作用。您不能将Workbooks.Open() 方法与通配符一起使用,因为它一次只会打开一个文件并且不会搜索文件。有两种方法可以在目录中搜索具有我所知道的特定命名模式的文件。最简单的方法是使用Dir() 函数,但这不会很容易递归到子文件夹中。

第二种方式(下面为您编码)是一种使用FileSystemObject 递归遍历文件和子文件夹的方式。为了使用它,您需要将项目的引用添加到 Microsoft Scripting Runtime 库中。您可以通过 Tools->References 添加引用。

另请注意,此方法使用Range.Find() 方法在您的工作簿中查找客户名称,因为它应该比您当前查找客户名称是否在工作表中的方法更快、更容易理解。

Option Explicit

Sub Search()

Dim myFolder As Folder
Dim fso As FileSystemObject
Dim destPath As String
Dim myClient As String

myClient = Application.InputBox("Please Enter Client Name", "Client Name")

Set fso = New FileSystemObject

destPath = "G:\WH DISPO\(3) PROMOTIONS\(18) Food Specials Delivery Tracking\Archive\"

Set myFolder = fso.GetFolder(destPath)

'Set extension as you would like
Call RecurseSubfolders(myFolder, ".xlsm", myClient)

End Sub

Sub RecurseSubfolders(ByRef FolderToSearch As Folder, _
           ByVal fileExtension As String, ByVal myClient As String)

Dim fileCount As Integer, folderCount As Integer
Dim objFile As File
Dim objSubfolder As Folder

fileCount = FolderToSearch.Files.Count
'Loop over all files in the folder, and check the file extension
If fileCount > 0 Then
  For Each objFile In FolderToSearch.Files
    If LCase(Right(objFile.Path, Len(fileExtension))) = LCase(fileExtension) Then
      'You can check against "objFile.Type" instead of the extension string,
      'but you would need to check what the file type to seach for is
      Call LookForClient(objFile.Path, myClient)
    End If
  Next objFile
End If

folderCount = FolderToSearch.SubFolders.Count
'Loop over all subfolders within the folder, and recursively call this sub
If folderCount > 0 Then
  For Each objSubfolder In FolderToSearch.SubFolders
    Call RecurseSubfolders(objSubfolder, fileExtension, myClient)
  Next objSubfolder
End If

End Sub

Sub LookForClient(ByVal sFilePath As String, ByVal myClient As String)

Dim wbTarget As Workbook
Dim ws As Worksheet
Dim rngFound As Range
Dim firstAddress As String
Static i As Long           'Static ensures it remembers the value over subsequent calls

'Set to whatever value you want
If i <= 0 Then i = 20

Set wbTarget = Workbooks.Open(Filename:=sFilePath)    'Set any other workbook opening variables as appropriate

'Loop over all worksheets in the target workbook looking for myClient
For Each ws In wbTarget.Worksheets
  With ws.Range("A:Q")
    Set rngFound = .Find(What:=myClient, LookIn:=xlValues, LookAt:=xlPart)

    If Not rngFound Is Nothing Then
      firstAddress = rngFound.Address

      'Loop finds all instances of myClient in the range A:Q
      Do
        'Reference the appropriate output worksheet fully, don't use ActiveWorksheet
        ThisWorkbook.Worksheets("SomeSheet").Range("E" & i).Value = _
                     "1 Result found for " & myClient & " in " & sFilePath _
                     & ", in sheet " & ws.Name & ", in cell " & rngFound.Address
        i = i + 1
        Set rngFound = .FindNext(After:=rngFound)
      Loop While (Not rngFound Is Nothing And rngFound.Address <> firstAddress)
    End If
  End With
Next ws

'Close the workbook
wbTarget.Close SaveChanges:=False

End Sub

【讨论】:

感谢您,效果很好。但是有没有办法我可以更改消息框以列出每个结果?请参阅有问题的编辑 您想在单个工作表/工作簿中查找多个myClient 实例的位置吗?或者只是工作簿中myClient 的第一个实例,但在宏工作表中列出每个找到的工作簿? 请查看在编辑中上传的图片。我想列出每个找到的值以及每个找到的值出现的行、工作簿路径和名称【参考方案2】:

如果不知道客户 ID,我需要浏览一份工作报告文件列表并搜索客户 ID 号或公司名称的通配符部分选择。

我清理了查询以删除大部分多余的不必要字段,然后停在那里。我还打算将 2 个不同的查询合并到 1 个程序语句中,但它与我抗争,我停在那里。

为临时查询放置制作一个名为“输出”的工作表。它只是复制数据结果而不是标题,因为我将多个结果串在一起。您当然需要记录宏和数据/获取数据/从文件/从工作簿,打开工作簿,转换数据,选择要返回的列,然后在列上输入搜索参数,然后关闭并返回到您的电子表格,最后停止宏以获取您自己的查询。

    Sub XLDataScan()
    
       ' Send File path and Name of XL file, Specific data, OR Contains data to search for. 
       ExternalXLScan "PATH/FILENAME", "SPECIFIC_Data", "CONTAINS_Data"
    
    End Sub
    
    Sub ExternalXLScan (sPath As String, sSubID As String, sOrg As String)
    
        Dim DoSearch  As String
    
        Sheets("Output").Select
    
            ' The 2 data needed for either query is "sPath", which is the file to be checked, and the "sSubID" OR "sOrg".
           
            ' SPECIFIC or PARTIAL
        If sSubID <> "" Then
            DoSearch = "([Subscriber ID] = " & sSubID
        
            ActiveWorkbook.Queries.Add Name:="Add-On Pull", Formula:= _
            "let" & Chr(13) & "" & Chr(10) & "    Source = Excel.Workbook(File.Contents(""" & sPath & """), null, true)," & Chr(13) & "" & Chr(10) & "    #""Add-On Pull_Sheet"" = Source[Item=""Add-On Pull"",Kind=""Sheet""][Data]," & Chr(13) & "" & Chr(10) & "    #""Promoted Headers"" = Table.PromoteHeaders(#""Add-On Pull_Sheet"", [PromoteAllScal" & _
            "ars=true])," & Chr(13) & "" & Chr(10) & "    #""Changed Type"" = Table.TransformColumnTypes(#""Promoted Headers"",""Pull Date"", type date, ""Mail Date"", type date, ""Job Line"", type any, ""Account Name"", type text, ""Account State"", type text, ""Last Name"", type text, ""Suffix"", type any, ""First Name"", type text, ""Middle Name"", type text, ""Subscriber ID"", Int64" & _
            ".Type, ""CertificateDeductibleperCoveredPerson"", type any, ""CertificateDeductibleperFamily"", type any)," & Chr(13) & "" & Chr(10) & "    #""Removed Other Columns"" = Table.SelectColumns(#""Changed Type"",""Mail Date"", ""Account Name"", ""Last Name"", ""First Name"", ""Subscriber ID"")," & Chr(13) & "" & Chr(10) & "    #""Filtered Rows"" = Table.SelectRows(#""Removed " & _
            "Other Columns"", each " & DoSearch & "))" & Chr(13) & "" & Chr(10) & "in" & Chr(13) & "" & Chr(10) & "    #""Filtered Rows"""
      
        End If
        If sOrg <> "" Then
       ' Text.Contains([Account Name], ""Series"
            Debug.Print "sOrg: " & sOrg
            DoSearch = "Text.Contains([Account Name], """ & sOrg '"([Subscriber ID] = " & sOrg
    
            ActiveWorkbook.Queries.Add Name:="Add-On Pull", Formula:= _
            "let" & Chr(13) & "" & Chr(10) & "    Source = Excel.Workbook(File.Contents(""" & sPath & """), null, true)," & Chr(13) & "" & Chr(10) & "    #""Add-On Pull_Sheet"" = Source[Item=""Add-On Pull"",Kind=""Sheet""][Data]," & Chr(13) & "" & Chr(10) & "    #""Promoted Headers"" = Table.PromoteHeaders(#""Add-On Pull_Sheet"", [PromoteAllScala" & _
            "rs=true])," & Chr(13) & "" & Chr(10) & "    #""Changed Type"" = Table.TransformColumnTypes(#""Promoted Headers"",""Pull Date"", type date, ""Mail Date"", type date, ""Job Line"", type any, ""Account Name"", type text, ""Account State"", type text, ""Last Name"", type text, ""Suffix"", type any, ""First Name"", type text, ""Middle Name"", type text, ""Subscriber ID"", Int64." & _
            "Type,  ""CertificateDeductibleperFamily"", Int64.Type)," & Chr(13) & "" & Chr(10) & "    #""Removed Other Columns"" = Table.SelectColumns(#""Changed Type"",""Pull Date"", ""Account Name"", ""Last Name"", ""First Name"", ""Subscriber ID"")," & Chr(13) & "" & Chr(10) & "    #""Filtered Rows"" = Table.SelectRows(#""Re" & _
            "moved Other Columns"", each " & DoSearch & """))" & Chr(13) & "" & Chr(10) & "in" & Chr(13) & "" & Chr(10) & "    #""Filtered Rows"""
      
        End If
           
        With ActiveSheet.ListObjects.Add(SourceType:=0, Source:= _
            "OLEDB;Provider=Microsoft.Mashup.OleDb.1;Data Source=$Workbook$;Location=""Add-On Pull"";Extended Properties=""""" _
            , Destination:=Range("$A$1")).QueryTable
            .CommandType = xlCmdSql
            .CommandText = Array("SELECT * FROM [Add-On Pull]")
'        .RowNumbers = True
            .ListObject.DisplayName = "Add_On_Pull"
            .Refresh BackgroundQuery:=False
        End With
        
        ' Remove Query and Connection
        KillQueries
    
            'If data, copy it over
        If Range("A2") <> "" Then
       
            ' Just copy data found, not including header
            Dim AllFound As Integer
            AllFound = Worksheets("Output").Range("A" & Rows.Count).End(xlUp).Row
    Workbooks("Transconnect_Production.xlsm").Worksheets("Output").Range("A2:E" & AllFound).Copy _
    Destination:=Workbooks("Transconnect_Production.xlsm").Worksheets("Find Mail Date").Range("B" & RowPlace + 1)
        
     Range("Add_On_Pull[#All]").Delete
    
        Sheets("Sheet1").Select
    
    End Sub
    
    
    
    Sub KillQueries()
        Dim xConnect As Object
    Dim cn As WorkbookConnection
    Dim qr As WorkbookQuery
    On Error Resume Next
    For Each cn In ThisWorkbook.Connections
        cn.Delete
    Next
    For Each qr In ThisWorkbook.Queries
        qr.Delete
    Next
    End Sub

【讨论】:

【参考方案3】:

我更新了我的代码以使用 ADO 来查询已关闭的工作簿。对于搜索的 50 个文件,这比我之前发布的代码快了大约 10 秒,完成时间是 40 秒,而完成时间大约是 50 秒。

Sub XLDataScan()

   ' Send File path and Name of XL file, Specific data, OR Contains data to search for. 
   ExternalXLScan "PATH/FILENAME", "SPECIFIC_Data", "CONTAINS_Data"

End Sub

    Sub XLDataScan(strSourceFile As String, sSubID As String, sOrg As String)
        Dim RowPlace As Integer
        Dim strSQL As String
         Dim cn As Object, rs As Object, output As String, sql As String
       
        ' Start writing data to row:
        RowPlace = 1
    
        ' Exact match search:
        If sSubID <> "" Then
            sql = "Select [Pull Date],[Account Name],[Last Name],[First Name],[Subscriber ID] from [Add-On Pull$] Where [Subscriber ID] = " & sSubID
        End If
        ' Wildcard search:
        If sOrg <> "" Then
             sql = "Select [Pull Date],[Account Name],[Last Name],[First Name],[Subscriber ID] from [Add-On Pull$] Where [Account Name] LIKE '%" & sOrg & "%'"
        End If
    
    
        '---Connecting to the Data Source---
        Set cn = CreateObject("ADODB.Connection")
        With cn
            .Provider = "Microsoft.ACE.OLEDB.12.0"
            .ConnectionString = "Data Source=" & strSourceFile & ";" & "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
            .Open
        End With
        
        Set rs = cn.Execute(sql)
    
        ' Get Header Fields:  
    '         For f = 0 To rs.Fields.Count - 1
    '            On Error Resume Next
    '           .Cells(r, c + f).Formula = rs.Fields(f).Name
    '                 Debug.Print rs.Fields(f).Name
    '            On Error GoTo 0
    '        Next f
            
            On Error Resume Next
            rs.MoveFirst
            On Error GoTo 0
            Do While Not rs.EOF
                 For f = 0 To rs.Fields.Count - 1
                    On Error Resume Next
     '               .Cells(r, c + f).Formula = rs.Fields(f).value
                           Debug.Print "R: " & RowPlace & ", " & "f: " & f & " -> " & rs.Fields(f).value
                    'Write found record to Sheet:
                    Cells(RowPlace, 2 + f).value = rs.Fields(f).value
                    On Error GoTo 0
                Next f
                rs.MoveNext
                RowPlace = RowPlace + 1
            Loop
        
        '---Clean up---
        rs.Close
        cn.Close
        Set cn = Nothing
        Set rs = Nothing  
    
    End Sub

【讨论】:

以上是关于VBA 搜索已关闭的工作簿以获取价值?的主要内容,如果未能解决你的问题,请参考以下文章

Excel VBA 从关闭的工作簿中读取数据,带有 ADODB、动态范围和标题可选

怎么用VBA复制另外一个工作簿内容?

Excel VBA 关闭当前工作簿错误 1004

VBA 其他工作簿未正确关闭

在工作簿关闭的Excel VBA中禁用剪贴板提示

Excel 2010 vba 复制选择工作表,保存并关闭两个工作簿