【问题标题】:Excel VBA: Extract substrings from multiple comma separated strings using formulas or macro in ExcelExcel VBA:使用 Excel 中的公式或宏从多个逗号分隔的字符串中提取子字符串
【发布时间】:2016-12-15 04:32:05
【问题描述】:

我在 Sheet1 上有以下列表:

    COLUMN A    COLUMN B
 1  ADDRESS     VEHICLE(S) USED
 2  Address1    Vehicle1, Vehicle3, Vehicle4
 3  Address2    Vehicle1, Vehicle3, Vehicle4
 4  Address3    Vehicle1, Vehicle2, Vehicle5
 5  Address4    Vehicle1, Vehicle6 
 6  Address1    Vehicle2, Vehicle4, Vehicle6 
 7  Address2    Vehicle2, Vehicle3
 8  Address1    Vehicle2, Vehicle5

在 Sheet2 上,当我在单元格 B1 中输入“Address1”时,我希望 D 列中的以下输出

   COLUMN A    COLUMN B    COLUMN C         COLUMN D
1  ADDRESS     Address 1   VEHICLE(S) USED  Vehicle1
2                                           Vehicle2
3                                           Vehicle3
4                                           Vehicle4
5                                           Vehicle5
6                                           Vehicle6

有没有办法使用 Visual Basic 宏来执行此操作?

【问题讨论】:

  • 您可以使用Dictionary
  • 我提供了一种手动执行此操作的方法-您已询问如何使用 VBA 执行此操作-到目前为止您尝试了什么?而不是仅仅要求您提供代码,您应该至少自己尝试一下,如果您随后提供您尝试过的代码,我(和其他人)将更有可能帮助您解决您遇到的任何问题。
  • @Phil S. 请在下面查看我的答案(详细而详细),如果它适合您,请告诉我...

标签: vba excel macros


【解决方案1】:

Phil,您可以使用评论中提到的 Dictionary 对象,这是下面的小示例(但没有排序 venicles,我认为这对您来说很容易)。

所以我的输入是:

基于字典的解决方案:

Public Sub ExractSubstringsFromBlaBlaBla(ByVal GiveMeAddress As String)
    Dim GatheredStrings As Object
    Dim Addresses As Variant
    Dim VeniclesUsed As Variant
    Dim SubResult() As String
    Dim i As Long
    Dim j As Long

    'Setting up info
    Set GatheredStrings = CreateObject("Scripting.Dictionary")
    Addresses = Sheets(1).[A2:A8].Value2
    VeniclesUsed = Sheets(1).[B2:B8].Value2

    'Gathering dict
    For i = LBound(Addresses) To UBound(Addresses)
        If GiveMeAddress = Addresses(i, 1) Then
            SubResult = Split(Expression:=VeniclesUsed(i, 1), Delimiter:=", ")
            For j = LBound(SubResult) To UBound(SubResult)
                If Not GatheredStrings.Exists(SubResult(j)) Then _
                        Call GatheredStrings.Add(Key:=SubResult(j), Item:=SubResult(j))
            Next
        End If
    Next

    'If dictionary is empty - lets quit
    If GatheredStrings.Count = 0 Then _
            Exit Sub

    Sheets(2).[A1].Value2 = GiveMeAddress
    'Resize and transpose array to fit in vertical direction
    Sheets(2).[B1].Resize(GatheredStrings.Count).Value2 = _
            Application.Transpose(GatheredStrings.Keys)
End Sub

我的输出是(没有分类静脉):

干杯!

【讨论】:

  • 谢谢常识!我必须承认我对使用 VBA 很陌生。你能解释一下如何运行这个脚本吗?我去了开发人员选项卡,并选择了 Visual Basic。然后我选择了插入>模块。我复制/粘贴代码并运行它。此时会出现宏框并要求提供名称。输入名称后,它只会给我一个创建选项,这会将我带回 VBA 编辑器。
  • 另外,我想澄清一件事。此示例显示 4 个地址和 6 个车辆。实际上,只有 6 辆车可用,但不同地址的数量只会随着时间的推移而增加。我从 Google Forms 响应表单中提取此输出,该表单将每周更新大约 4 次。每次更新时,它都会将响应添加到只有一行的多列(大约 70 列)。这就解释了为什么车辆会在一个以逗号分隔的单元格中返回。
  • Address1 有 3 行的原因是因为它们是在 3 个不同的日期提交的。这样做的目的是跟踪一段时间内某个地址使用了哪些车辆。
  • @Phil S. 你好,这段代码只是示例,你不能直接运行它,因为你需要传递参数GiveMeAddress。您可以将GiveMeAddress 的声明移动到子声明中,并从输入框中获取名称,例如GiveMeAddress = InputBox("Give me some input")。除了设置“地址”和VeniclesUsed 的行之外,您的地址数量无关紧要 - 您需要将这两行 'Sheets(1).[:]' 替换为一些实际范围。尝试谷歌如何找到你的床单的使用范围!
【解决方案2】:

您可以使用“文本到列”功能以及“转置”复制和粘贴功能来完成此任务。

在 Excel 2010 中,这可以在“数据”选项卡下的功能区中找到

您选择要拆分的列,在本例中为“列 B”,然后单击功能区中的“文本到列”按钮。

这会打开一个向导来指导您完成整个过程, 在第一个屏幕上,您将选择“分隔”,因为您已经声明您有逗号分隔的字符串,在第二个屏幕上,选择分隔符标题下的逗号。 第三个屏幕允许您选择列数据格式(常规、文本、日期)

单击完成后,它将分离出选定的列。 您可以复制结果,然后使用“特殊粘贴”和转置将它们粘贴到新区域 - 这会将数据从多列交换到多行。

【讨论】:

  • 你读过帖子的最后一行吗?他问“有没有办法使用 Visual Basic 宏来执行此操作?”
  • 是的,我做到了-我正在提供一种手动执行此操作的方法,因为问题标题提到了公式……
【解决方案3】:

这个答案有点长,但是代码很简单,步骤很详细。

流程/代码步骤

  1. 代码放置在Worksheet_Change事件中的“Sheet2”模块中,并检查B列中的值是否被修改(如果需要,可以扩展到“B1”的单个单元格),如果有的话它调用FilterAddress Sub,并发送Target.Value

  2. 根据“Sheet2”单元格B1中输入的值,在“Sheet1”中使用AutoFilter

  3. 循环通过可见单元格,使用 SpecialCells(xlCellTypeVisible) 并使用 Dictionary 对象,只保留唯一的“车辆”。

  4. 将字典中唯一的“车辆”存储到VehicleArr 数组中。

  5. VehicleArr 数组按其字符串值排序(从小到大)。

  6. 将根据 PO 请求的值粘贴到“Sheet2”。


Worksheet_Change Code(“Sheet2”模块)

Private Sub Worksheet_Change(ByVal Target As Range)

' call Function only if modifed cell is in Column "B"
If Not Intersect(Target, Range("B:B")) Is Nothing Then
    Application.EnableEvents = False

    Call FilterAddress(Target.Value)
End If

Application.EnableEvents = True

End Sub

子过滤器地址代码(常规模块)

Option Explicit

Sub FilterAddress(FilterVal As String)

Dim LastRow As Long
Dim FilterRng As Range, cell As Range
Dim Dict As Object
'Dim ID
Dim Vehicle     As Variant
Dim VehicleArr  As Variant
Dim i As Long, j As Long


With Sheets("Sheet1")
    ' find last row with data in column "A" (Adress)
    LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row

    Set FilterRng = .Range("A1:B" & LastRow)

    .Range("A1").AutoFilter
    ' AutoFilter "Sheet1" according to value in "Sheet2" in Column B
    FilterRng.AutoFilter Field:=1, Criteria1:=FilterVal

    Set Dict = CreateObject("Scripting.Dictionary")

    ' create an array with size up to number of rows >> will resize it later
    ReDim VehicleArr(1 To LastRow)
    j = 1 ' init array counter

    For Each cell In .Range("B2:B" & LastRow).SpecialCells(xlCellTypeVisible)
        ' read values from cell to array using the Split function
        Vehicle = Split(cell.Value, ",")

        For i = LBound(Vehicle) To UBound(Vehicle)
            Vehicle(i) = Trim(Vehicle(i)) ' remove extra spaces from string

            If Not Dict.exists(Vehicle(i)) Then
                Dict.Add Vehicle(i), Vehicle(i)

                ' save Vehicle Name to array >> will use it later for "Bubble-sort" and paste in "Sheet2"
                VehicleArr(j) = Vehicle(i)
                j = j + 1 ' increment VehicleArr counter
            End If
        Next i

    Next cell
    ' resize array up to number of actual Vehicle
    ReDim Preserve VehicleArr(1 To j - 1)

End With

Dim VehicleTmp As Variant
' Bubble-sort Vehicle Array >> sorts the Vehicle array from smallest to largest
For i = 1 To UBound(VehicleArr) - 1
    For j = i + 1 To UBound(VehicleArr)
        If VehicleArr(j) < VehicleArr(i) Then
            VehicleTmp = VehicleArr(j)
            VehicleArr(j) = VehicleArr(i)
            VehicleArr(i) = VehicleTmp
        End If
    Next j
Next i

' now the "fun" part >> paste to "Sheet2"
With Sheets("Sheet2")
    .Range("A1").Value = "ADDRESS"
    .Range("B1").Value = FilterVal
    .Range("C1").Value = "VEHICLE(S) USED"

    ' clear contents from previous run
    .Range("D1:D" & .Cells(.Rows.Count, "D").End(xlUp).Row).ClearContents
    .Range("D1:D" & UBound(VehicleArr)) = WorksheetFunction.Transpose(VehicleArr)
End With

End Sub

【讨论】:

  • 谢谢@Shai Rado。由于某种原因,我无法运行此代码。我打开了一个空白工作簿,从原始帖子中复制了我的数据,然后打开了 Visual Basic,然后将上述代码复制到了适当的位置。当我尝试运行代码时,会弹出宏框并要求输入名称。它没有让我选择我粘贴的那些(FilterAddress)。
  • @PhilS。你不需要粘贴它。一旦您将Worksheet_Change 中的代码复制到“Sheet2”,另一个复制到常规的单独模块。将 Excel 文件另存为 .xlsm。然后,一旦您在“Sheet2”中的单元格 B1 中输入“Address1”的值(或其他有效选项),代码就会运行。
  • 谢谢。在 Sheet2 上输入“Address1”后,我能够得到正确的结果。如果我删除 B1 中的值并添加另一个值,你能告诉我是否有办法重置工作表?我能做到这一点的唯一方法是不保存文件,关闭它,如果我想查看另一个地址 (Address2) 的结果,然后重新打开它
  • @PhilS。您可以更改工作表 2 中单元格 B1 中的值,每次您都可以即时获得新结果。这不是你想要的方式?
  • 这就是我希望发生的事情。但是,当我将 B1 更改为另一个地址时,工作表不会更新。我注意到在 Sheet2 上删除或更改 B1 后,过滤器仍在 Sheet1 上。我在代码末尾添加了“FilterRng.Parent.AutoFilterMode = False”。这会在将行复制到 Sheet2 后清除过滤器,并允许我毫无问题地更改 B1 上的值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-28
  • 1970-01-01
  • 2018-09-14
  • 2015-08-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多