在具有多个条件的二维数组中搜索数值。

huangapple go评论127阅读模式
英文:

Search values in a two dimensional array with multiple criteria

问题

假设我有以下包含三列的表格。我想要在Column1是给定值的条件下,从Column3中搜索精确匹配或下一个前一个日期。

这可以通过XLOOKUP轻松完成。但是,我需要在VBA中完成,因为我将在用户窗体文本框中显示找到的日期。根据我迄今为止的搜索,Application.Worksheetfunction.Xlookup 对于多个条件中的 & 不起作用,因此解决此问题将涉及操纵数组。

我通过以下方式从该表格创建了一个变体:

  1. Dim TBL As ListObject
  2. Set TBL = Sheets("sheet1").ListObjects("Table1")
  3. Dim DirArray As Variant
  4. DirArray = TBL.DataBodyRange

有关如何使用数组获得近似匹配的任何建议?

英文:

Suppose I have the following table with three columns. I want to search for an exact match or next previous date from Column3, conditional to Column1 being a given value.

This can be easily done with XLOOKUP. However, I need to do so in VBA because I'll show the date found in a userform Textbox to the user. From what I have searched so far, Application.Worksheetfunction.Xlookup won't work with an & for multiple criteria, so the solution for this would involve manipulating arrays.

在具有多个条件的二维数组中搜索数值。

I created a variant from that table by writing:

  1. Dim TBL As ListObject
  2. Set TBL = Sheets("sheet1").ListObjects("Table1")
  3. Dim DirArray As Variant
  4. DirArray = TBL.DataBodyRange

Any advice on how to get that approximate match using arrays?

答案1

得分: 1

以下是翻译后的代码部分:

  1. Option Explicit
  2. Public Function findEntryByCol1andCol3(dataToSearch As Range, findCol1, findCol3) As Variant
  3. ' 用于存储最接近条件的行的变量
  4. Dim matchRow As Range
  5. Set matchRow = Nothing
  6. ' 用于正在检查的行的变量
  7. Dim checkRow As Range
  8. Dim ix As Long
  9. For ix = 1 To dataToSearch.Rows.Count
  10. ' 获取要检查的下一行
  11. Set checkRow = dataToSearch.Rows(ix)
  12. ' 此行的第一列是否与第一列的搜索条件匹配?
  13. If checkRow.Cells(1, 1).Value = findCol1 Then
  14. ' 现在检查行中的日期是否小于搜索日期
  15. If findCol3 >= checkRow.Cells(1, 3).Value Then
  16. ' 如果尚未找到匹配项,则使用此已检查的行作为第一个找到的匹配项
  17. If matchRow Is Nothing Then
  18. Set matchRow = checkRow
  19. ' 如果已经有先前的匹配项,请检查新日期是否比先前找到的日期晚
  20. ElseIf matchRow.Cells(1, 3).Value < checkRow.Cells(1, 3).Value Then
  21. Set matchRow = checkRow
  22. End If
  23. End If
  24. Else
  25. End If
  26. Next ix
  27. ' 现在返回搜索的结果
  28. If matchRow Is Nothing Then
  29. findEntryByCol1andCol3 = "未找到"
  30. Else
  31. findEntryByCol1andCol3 = matchRow.Cells(1, 3)
  32. End If
  33. End Function

希望这对您有帮助!

英文:

There may well be a neater answer, but here is a simple brute-force function that just scans down every row in the given data looking for the closest match to the given criteria. The function returns the date of the closest match, but maybe it would be more useful to you if it returned, say, the row number of the row that is the closest match. Put this function in a new code module so that it can be called as a function from a cell, for example =findEntryByCol1andCol3(Table1,F1,F2)

  1. Option Explicit
  2. Public Function findEntryByCol1andCol3(dataToSearch As Range, findCol1, findCol3) As Variant
  3. &#39;// variable to hold the row with the closest match to criteria
  4. Dim matchRow As Range
  5. Set matchRow = Nothing
  6. &#39;// variable to hold the row being checked
  7. Dim checkRow As Range
  8. Dim ix As Long
  9. For ix = 1 To dataToSearch.Rows.Count
  10. &#39;// get the next row to be checked
  11. Set checkRow = dataToSearch.Rows(ix)
  12. &#39;// does column 1 in this row match the search criterion for column 1?
  13. If checkRow.Cells(1, 1).Value = findCol1 Then
  14. &#39;// now see if the date in the row is less than the search date
  15. If findCol3 &gt;= checkRow.Cells(1, 3).Value Then
  16. &#39;// If there has been no match then use this checked row as the first found match
  17. If matchRow Is Nothing Then
  18. Set matchRow = checkRow
  19. &#39;// If there has been a previous match check
  20. &#39;// if the new date is later that the previously found date
  21. ElseIf matchRow.Cells(1, 3).Value &lt; checkRow.Cells(1, 3).Value Then
  22. Set matchRow = checkRow
  23. End If
  24. End If
  25. Else
  26. End If
  27. Next ix
  28. &#39;// Now return the result of the search
  29. If matchRow Is Nothing Then
  30. findEntryByCol1andCol3 = &quot;Not found&quot;
  31. Else
  32. findEntryByCol1andCol3 = matchRow.Cells(1, 3)
  33. End If
  34. End Function

在具有多个条件的二维数组中搜索数值。

答案2

得分: 1

使用值数组将比每次引用单元格更快,尤其是如果您的表格要大得多。

您可以使用此函数 - 如果未找到有效日期,则将返回0。

由于我正在使用 sortBy,因此需要 Excel 365 才能使用此功能。

通过使用 SortBy,在找到匹配的日期后,可以安全地退出 for 循环。

  1. Public Function nearestDate(lo As ListObject, valueColumn1 As String, valueColumn3 As Date) As Date
  2. Dim arrValues As Variant
  3. arrValues = Application.WorksheetFunction.SortBy(lo.DataBodyRange, lo.ListColumns(1).DataBodyRange, 1, lo.ListColumns(3).DataBodyRange, 1)
  4. Dim i As Long
  5. For i = 1 To UBound(arrValues, 1)
  6. If arrValues(i, 1) = valueColumn1 Then
  7. If arrValues(i, 3) = valueColumn3 Then
  8. '找到了我们要找的日期
  9. nearestDate = arrValues(i, 3)
  10. ElseIf arrValues(i, 3) < valueColumn3 Then
  11. '我们必须检查下一行 - 如果有的话
  12. If i < UBound(arrValues, 1) Then
  13. If arrValues(i + 1, 1) = valueColumn1 And arrValues(i + 1, 3) > valueColumn3 Then
  14. '相同的列1,但列3大于valueColumn3
  15. nearestDate = arrValues(i, 3)
  16. ElseIf arrValues(i + 1, 1) <> valueColumn1 Then
  17. '新的列1 --> 因此我们采用当前日期
  18. nearestDate = arrValues(i, 3)
  19. End If
  20. Else
  21. '最后一个值 --> ok
  22. nearestDate = arrValues(i, 3)
  23. End If
  24. End If
  25. End If
  26. If nearestDate > 0 Then Exit For
  27. Next
  28. End Function

您可以像这样调用此函数:

  1. Public Sub test()
  2. Dim ws As Worksheet: Set ws = Thisworkbook.Worksheets("sheet1")
  3. Dim lo As ListObject: Set lo = ws.ListObjects("Table1")
  4. Dim valueColumn1 As String: valueColumn1 = ws.Range("F1")
  5. Dim valueColumn3 As Date: valueColumn3 = ws.Range("F2")
  6. Debug.Print nearestDate(lo, valueColumn1, valueColumn3)
  7. End Sub
英文:

Using an array of values will be faster than referencing a cell for each check - esp. if your table is much larger.

You can use this function - it will return 0 in case no valid date is found.

As I am using sortBy you will need Excel 365 for this to work.

By using SortBy it is safe to exit the for-loop in case we have found a matching date.

  1. Public Function nearestDate(lo As ListObject, valueColumn1 As String, valueColumn3 As Date) As Date
  2. Dim arrValues As Variant
  3. arrValues = Application.WorksheetFunction.SortBy(lo.DataBodyRange, lo.ListColumns(1).DataBodyRange, 1, lo.ListColumns(3).DataBodyRange, 1)
  4. Dim i As Long
  5. For i = 1 To UBound(arrValues, 1)
  6. If arrValues(i, 1) = valueColumn1 Then
  7. If arrValues(i, 3) = valueColumn3 Then
  8. &#39;we found what we are looking for
  9. nearestDate = arrValues(i, 3)
  10. ElseIf arrValues(i, 3) &lt; valueColumn3 Then
  11. &#39;we have to check next row - if there is one
  12. If i &lt; UBound(arrValues, 1) Then
  13. If arrValues(i + 1, 1) = valueColumn1 And arrValues(i + 1, 3) &gt; valueColumn3 Then
  14. &#39;same column1 but column3 greater than valueColumn3
  15. nearestDate = arrValues(i, 3)
  16. ElseIf arrValues(i + 1, 1) &lt;&gt; valueColumn1 Then
  17. &#39;new column1 value --&gt; therefore we take current date
  18. nearestDate = arrValues(i, 3)
  19. End If
  20. Else
  21. &#39;last value --&gt; ok
  22. nearestDate = arrValues(i, 3)
  23. End If
  24. End If
  25. End If
  26. If nearestDate &gt; 0 Then Exit For
  27. Next
  28. End Function

You can call this function like this:

  1. Public Sub test()
  2. Dim ws As Worksheet: Set ws = Thisworkbook.Worksheets(&quot;sheet1&quot;)
  3. Dim lo As ListObject: Set lo = ws.ListObjects(&quot;Table1&quot;)
  4. Dim valueColumn1 As String: valueColumn1 = ws.Range(&quot;F1&quot;)
  5. Dim valueColumn3 As Date: valueColumn3 = ws.Range(&quot;F2&quot;)
  6. Debug.Print nearestDate(lo, valueColumn1, valueColumn3)
  7. End Sub
  8. </details>

huangapple
  • 本文由 发表于 2023年1月8日 22:25:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75048518.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定