【问题标题】:VBA - Excel Getting same data from different sheets at different positionsVBA - Excel 从不同位置的不同工作表中获取相同的数据
【发布时间】:2019-06-17 04:06:34
【问题描述】:

我想创建一个宏来打开文件夹中的所有 Excel 书籍,读取填充的信息并将它们存储在代表我的数据库的工作表中。 我需要了解您的建议,以及获得快速灵活结果的最佳方法。

为了帮助您理解我的问题,让我们假设我有 3 个包含名字、姓氏和国家/地区的 excel 模板,但在这些图片的不同位置

模板 1

模板 2

模板 3

基于此,我想得到的最终结果是:

我通过这些图片给出的例子真的很简单,但这只是为了帮助你理解我想要什么。现在我将详细说明真正的需求。事实上,我有 3 个模板,但每个模板都包含大约 80 个要收集的数据字段(不仅是名字、姓氏和国家)。而且我不必只阅读 3 个文件,但我必须阅读放置在文件夹中的大约 200 个文件,每个文件要么是模板 1,要么是 2 或 3。将来我们可能有一个模板 4,这就是我需要的原因灵活的东西。

我考虑过命名范围,但模板 1,2,3 已经存在,我无法从 200 个用户那里收集 200 个现有的 excels 文件,并且在启动我的宏之前,为 80 字段提供命名范围在每个文件。如果将来它们将成为模板 4,我可以使用命名范围,因此在将文件发送给将填写 excel 的最终用户之前,我们命名范围并将其发送给他,但在模板 4 之前,我必须修复当前3个现有模板的问题。

我还考虑过根据列和行索引读取数据,例如,我检查文件的类型,如果我正在读取文件模板,我会从单元格 (2,3) 中获取名字,如果是模板 2,我从单元格 (5,6) 获取信息,如果是模板 3,我从 Cel (9,4) 获取信息,但问题是我的代码根本不灵活。

我也说过,我可能会喜欢一张名为reference的表格,其中我根据模板模型定义每个字段的位置,例如我说名字是模板1在位置2,3模板 2 的名字是 5,6,模板 3 的名字是 9,4。如下图所示,当我遍历我的 200 个文件时,我检查它是否是模板 1,我阅读了参考表,我知道名字将在这个位置,模板 2 也是如此,依此类推...... .这个解决方案看起来像以前的一个,但更灵活,因为如果有什么变化,我们只需要改变参考表,但我想知道如果我必须为每个字段读取 2 个单元格,它会快还是慢参考表了解位置。

我真的很迷茫,因为在开始编码之前我必须选择最好的方式来做我想做的事,以避免浪费时间。 如果任何专家可以通过告诉我什么是最好的或给我比我想象的更多的想法来帮助我,我将非常感激。

提前感谢任何帮助者

编辑: @PEH,如果我这样制作查找表,您会怎么想?

编辑2: @PEH,这就是最后评论中的建议

【问题讨论】:

  • Peh,你有回答吗?我看到你写了一些东西,但看不到你写了什么。
  • 查看Range.Find method。您可以使用它来搜索“名字”并使用找到的单元格,而无需事先知道其位置。然后你只需要学习如何Loop though files in a folder。这应该给你一个开始的地方。
  • @JustGreat 不,刚刚更正了您的标签。但我正要写和tigeravatar一样的评论。 • 您的问题过于宽泛,无法提供答案,因为您什么都没做。试试上面评论中的方法。
  • 多亏了这两个...循环一个文件夹我已经知道怎么做这不是问题...至于 Range.Find,你的意思是我检查每个数据的标题获得价值?它比告诉我需要位置 1,2 的数据更快吗?我也在考虑一些事情。 range.find 会给我标题的位置,所以我应该做一个偏移来获得正确的值?如果是,这意味着如果有一天,我们在标题和值之间添加一列或一行或一个小单元格,我应该检查整个代码对吗?
  • Model 1 等名称是否总是在 Cells(1, 6) 中?然后,您可以读取此单元格以确定它是哪个模型以及其他数据的预期位置。这会比使用find() 更快。

标签: excel vba optimization


【解决方案1】:

基本思想(除了循环文件):

  1. 将您的查找数据更改为以下内容:

  2. 然后阅读Cells(1, 6) 以获取您的模型。

    Dim Model As String
    Model = Worksheets("MyTemplate").Cells(1, 6).Value
    
  3. 使用WorksheetFunction.Match method 在查找表中查找您的字段。

    Dim FieldRow As Long
    FieldRow = Application.WorksheetFunction.Match(Model & "-First name", Worksheets("LookupTable").Range("A:A"), 0)
    
  4. 使用……

    fRow = Worksheets("LookupTable").Cells(FieldRow, 2)
    fColumn = Worksheets("LookupTable").Cells(FieldRow, 3)
    

    获取在模板中查找该字段的位置的行和列。

如果你把字段查找的东西放到一个方便的函数中,代码会更容易维护。例如将以下内容放入模块中:

Option Explicit

Public LookupCache As Variant
Public LookupResults As Variant

Public Function ReadField(Ws As Worksheet, FieldName As String) As Variant
    'Here we cache the lookup table. It reads the sheet LookupTable into an 
    'array if the array does not exist yet. If the function runs a second time,
    'the array exists already and is used directly (saves time).
    'Lookup in arrays is much faster than in cells.
    'Caching makes this function about 2 times faster than without.
    If IsEmpty(LookupCache) Or IsEmpty(LookupResults) Then
        With ThisWorkbook.Worksheets("LookupTable")
            Dim LastLookupRow As Long
            LastLookupRow = .Cells(.Rows.Count, "A").End(xlUp).Row
            LookupCache = .Range("A2", "A" & LastLookupRow).Value
            LookupResults = .Range("B2", "C" & LastLookupRow).Value
        End With
    End If

    Dim ModelName As String
    ModelName = Ws.Cells(1, 6).Value

    Dim LookupRow As Long
    On Error Resume Next
    LookupRow = Application.WorksheetFunction.Match(ModelName & "-" & FieldName, LookupCache, 0)
    On Error GoTo 0

    If LookupRow = 0 Then
        'field not found
        ReadField = CVErr(xlErrNA)
        Exit Function
    End If

    Dim fRow As Long, fColumn As Long
    fRow = LookupResults(LookupRow, 1)
    fColumn = LookupResults(LookupRow, 2)

    ReadField = Ws.Cells(fRow, fColumn).Value
End Function

所以你可以阅读像

这样的字段
Debug.Print ReadField(MyLoopWorkbook.Worksheets("MyTemplate"), "First name")
'MyLoopWorkbook should be the current workbook in your files loop

根据评论编辑……

如果我们将新字段Company 添加到新模型4,用户必须转到工作表查找表并在第 11 行添加 Model4-Company 以及行和列,但也必须在代码中添加ReadField(MyLoopWorkbook.Worksheets("MyNewTemplate"), "Company"),对吧?这就是为什么我不明白我怎么能指望那些不编码的人来添加它?你能澄清一下吗,因为你说的很重要。

如果您将ReadField 部分设为动态,则您也无需在此处编写代码。例如,如果你想得到这样的表格:

您只需在第 4 列中添加一个新标题,名称类似于字段,例如 Company。并编写一个循环遍历该标题行的列以收集所有字段。

Sub ReadAllFields()
    Dim wsData As Worksheet
    Set wsData = Worksheets("CollectedData")

    Dim FreeRow As Long 'find next free row in table
    FreeRow = wsData.Cells(wsData.Rows.Count, "A").End(xlUp).Row + 1

    Dim Fields() As Variant 'read headers into array
    Fields = wsData.Range("A1", wsData.Cells(1, wsData.Columns.Count).End(xlToLeft)).Value

    Dim iCol As Long
    For iCol = 1 To UBound(Fields, 2) 'loop through header columns
        wsData.Cells(FreeRow, iCol).Value = ReadField(MyLoopWorkbook.Worksheets("MyNewTemplate"), Fields(1, iCol)) 
        'reads fields dynamically depending on which headers exist in data sheet
    Next iCol
End Sub

【讨论】:

  • @JustGreat 您忘记将 PEH 的答案标记为“已选择”。
  • 将该表格从您的格式转换为我的格式将是同样的混乱,并且转换和不一致数据的缺点不会有很大的优势。以防万一你再问,我还是会告诉你“不要那样做”。 • 在Rngrow/col 中,范围可能会稍微快一些,因为您只需要处理1 个变量而不是2 个。范围对用户来说可能更方便。无论如何,两者都可以工作,不会有太大区别。
  • @JustGreat 几乎应该是Model - Field 来处理我的代码。 Excel 公式从左到右计算得更快,所以最好的列顺序是TemplateField,然后是Autofill
  • @JustGreat 范围和行/列之间的性能差异很小,因为您只将它读入数组缓存一次,因此您可以忽略这 1/10 秒,您可以在这里获胜。使用更适合您和您的用户的一种。 • Excel 中的计算顺序是从左/上到右/下,因此如果您在A1 = B1 + C1 中计算(并且 B1 和 C1 也是公式),则 Excel 从 A1 开始,然后计算 B1,这会再次触发 A1 中的更改,然后计算C1 再次触发 A1 的变化。所以它必须多次计算A1。所以最好计算C1 = A1 + B1
  • @JustGreat 这就是它的设计方式Obj.Object 问微软他们为什么这样做;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-24
  • 1970-01-01
  • 2017-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多