【问题标题】:How do I extract unique values from multiple columns and use them to populate one column?如何从多列中提取唯一值并使用它们填充一列?
【发布时间】:2019-06-03 09:22:58
【问题描述】:

我有一张包含大量数据的大表,但我正在查看的是该表的六列 - 一起从事特定工作的人的姓名。像这样的:

+-------+--------+--------+-------+--------+-------+
| Name1 | Name2  | Name3  | Name4 | Name5  | Name6 |
+-------+--------+--------+-------+--------+-------+
| Rod   | Jane   |        |       |        |       |
| Jane  | Freddy | Peter  | Paul  |        |       |
| Paul  |        |        |       |        |       |
| Mary  | Jane   | Rod    | Peter | Freddy | Paul  |
| Paul  | Rod    | Freddy |       |        |       |
+-------+--------+--------+-------+--------+-------+

我最终想要的是这个(在另一张纸上):

+--------+
|  Name  |
+--------+
| Rod    |
| Jane   |
| Freddy |
| Peter  |
| Paul   |
| Mary   |
+--------+

我希望能够识别这六列中的所有唯一条目,然后将它们填充到不同的工作表上。我的第一个想法是用公式来做,这很有效(我在 MATCH 部分使用了带有 COUNTIF 的 INDEX MATCH),但是表中有 11000 条记录和 1200 条可能涉及的不同名称,而且它占用了大部分处理的日期。我想,希望,使用 VBA 可以让它运行得更快。

我查看了许多可能的答案。首先,我来到这里:Populate unique values into a VBA array from Excel,并查看了 brettdj 的答案(因为我有点理解它的去向),最后得到了以下代码:

Dim X
Dim objDict As Object
Dim lngRow As Long

Sheets("Data").Select
Set objDict = CreateObject("Scripting.Dictionary")
X = Application.Transpose(Range([be2], Cells(Rows.Count, "BE").End(xlUp)))

For lngRow = 1 To UBound(X, 1)
objDict(X(lngRow)) = 1
Next

Sheets("Crew").Select

Range("A2:A" & objDict.Count) = Application.Transpose(objDict.keys)
End Sub

效果很好,对于一列(BE 是上表中的 Name1 列 - Data 是存储数据的工作表,Crew 是我希望唯一值所在的工作表)。但我终其一生都无法弄清楚如何让它从多列(BE 到 BJ)中获取值。

然后我尝试了这个,源自 Jeremy Thompson 在Quicker way to get all unique values of a column in VBA? 中的回答:

Sheets("Data").Select

Range("BE:BJ").AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Sheets("Crew").Range("A:A"), Unique:=True

但同样,我无法将多列中的信息合二为一。第三次尝试,我查看了来自How to extract unique values from two columns Excel VBA 的 Gary's Student 的回答并尝试了这个:

Dim Na As Long, Nc As Long, Ne As Long
Dim i As Long
Na = Sheets("Stroke Data").Cells(Rows.Count, "BE").End(xlUp).Row
Nc = Sheets("Stroke Data").Cells(Rows.Count, "BF").End(xlUp).Row
Ne = 1

For i = 1 To Na
    Cells(Ne, "E").Value = Cells(i, "A").Value
    Ne = Ne + 1
Next i
For i = 1 To Na
    Cells(Ne, "E").Value = Cells(i, "C").Value
    Ne = Ne + 1
Next i

Sheets("Fail").Range("A:A").RemoveDuplicates Columns:=1, Header:=xlNo

(只尝试了其中的两列,看看我是否可以这样解决,但没有)

我真的很茫然。正如您可能从上面看到的那样,我正在疯狂地四处游荡,并尝试从三个不同的角度来解决这个问题,但一无所获。我觉得必须有办法让第一个工作,如果没有别的,因为它几乎工作。但我不明白。

我想我可以为四个单独的列运行它,然后有一个将四个合并为一个的过程。但即便如此,我也不确定如何删除会导致的重复项(如上表所示,名称可以出现在任何列中)。

只要我能得到一个包含唯一名称列表的列,并且不需要花费数小时来处理,我想我并不介意我是如何到达那里的。

【问题讨论】:

  • 退后一步 - 在顶部的数据中,您有六列。您基本上是在尝试从列中提取所有唯一名称吗?我不太清楚你最终是如何得到预期结果的。
  • 是的。我想从这六列中提取所有唯一值。抱歉没有说清楚。

标签: excel vba unique-values


【解决方案1】:

这有点冗长,但对您的示例数据有用。 (可能需要调整初始 rng 的设置方式。

Sub unique_names()
Dim rng As Range
Set rng = ActiveSheet.UsedRange

Dim col As Range, cel As Range
Dim names() As Variant
ReDim names(rng.Cells.Count)

Dim i As Long
i = 0
'First, let's add all the names to the array
For Each col In rng.Columns
    For Each cel In col.Cells
        If cel.Value <> "" Then
            names(i) = cel.Value
            i = i + 1
        End If
    Next cel
Next col

' Now, extract unique names from the array
Dim arr As New Collection, a
Set arr = unique_values(names)
For i = 1 To arr.Count
   Worksheets("Sheet1").Cells(i, 10) = arr(i)
Next

End Sub
Private Function unique_values(iArr As Variant) As Collection
' https://stackoverflow.com/a/3017973/4650297
Dim arr As New Collection, a
On Error Resume Next
  For Each a In iArr
     arr.Add a, a
  Next

Set unique_values = arr

End Function

【讨论】:

  • 效果很好 - 谢谢!以供将来参考 - 如果我再次尝试这样做,我可能不得不这样做 - 是否可以添加任何内容以将其限制为电子表格中的某些列(例如,如果我只想使用其中两列)? (我确信我应该知道这个问题的答案,但我已经工作了很长时间。)
  • @RolloTreadway 您可以通过多种方式做到这一点。一个快速的方法是如果你知道你可以做哪两个Set rng = Range("D1:E100")。或者让用户选择,查找“VBA让用户选择范围”或类似的东西。
  • 谢谢! (并为响应缓慢而道歉。)
【解决方案2】:

这是一种使用字典的方法。只需指定要搜索的范围,RangeToDictionary 函数即可完成其余工作。我假设你不想包含空格,所以我删除了这些。

Private Function RangeToDictionary(MyRange As Range) As Object
    If MyRange Is Nothing Then Exit Function
    If MyRange.Cells.Count < 1 Then Exit Function

    Dim cell  As Range
    Dim dict  As Object: Set dict = CreateObject("Scripting.Dictionary")

    For Each cell In MyRange
        If Not dict.exists(Trim$(cell.Value2)) And Trim$(cell.Value2) <> vbNullString Then dict.Add cell.Value2, cell.Value2
    Next

    Set RangeToDictionary = dict
End Function

Sub Example()
    Dim dict       As Object
    Dim rng        As Range:Set rng = ThisWorkbook.Sheets("Sheet1").Range("A1:f5")
    Dim outsheet   As Worksheet:Set outsheet = ThisWorkbook.Sheets("Sheet2")

    Set dict = RangeToDictionary(rng)

    outsheet.Range(outsheet.Cells(1, 1), outsheet.Cells(dict.Count, 1)) = Application.Transpose(dict.items())
End Sub

【讨论】:

  • 这也很有用,谢谢。虽然有些事情让我感到困惑 - 我责怪我很累,但我输入了错误的范围,当我返回并尝试再次运行它时,我收到一个错误:'key is already associated with an element of this collection' on the '然后 dict.Add cell.Value2, cell.Value2' 部分。你知道为什么吗?我确定是我搞错了。
  • 发布的代码不应该发生这种情况。 Not dict.Exists 部分应该处理这个问题。
【解决方案3】:

这将提示您选择一个范围(可以通过按住 CTRL 来选择一个不连续的范围),然后将从所选范围中提取唯一值并将结果输出到新工作表上:

Sub tgr()

    Dim wb As Workbook
    Dim wsDest As Worksheet
    Dim rData As Range
    Dim rArea As Range
    Dim aData As Variant
    Dim i As Long, j As Long
    Dim hUnq As Object

    'Prompt to select range.  Uniques will be extracted from the range selected.
    'Can select a non-contiguous range by holding CTRL
    On Error Resume Next
    Set rData = Application.InputBox("Select range of names where unique names will be extracted:", "Data Selection", Selection.Address, Type:=8)
    On Error GoTo 0
    If rData Is Nothing Then Exit Sub   'Pressed cancel

    Set hUnq = CreateObject("Scripting.Dictionary")
    For Each rArea In rData.Areas
        If rArea.Cells.Count = 1 Then
            ReDim aData(1 To 1, 1 To 1)
            aData(1, 1) = rArea.Value
        Else
            aData = rArea.Value
        End If

        For i = 1 To UBound(aData, 1)
            For j = 1 To UBound(aData, 2)
                If Not hUnq.Exists(aData(i, j)) And Len(Trim(aData(i, j))) > 0 Then hUnq(Trim(aData(i, j))) = Trim(aData(i, j))
            Next j
        Next i
    Next rArea

    Set wb = rData.Parent.Parent    'First parent is the range's worksheet, second parent is the worksheet's workbook
    Set wsDest = wb.Sheets.Add(After:=wb.Sheets(wb.Sheets.Count))
    wsDest.Range("A1").Resize(hUnq.Count).Value = Application.Transpose(hUnq.Items)

End Sub

【讨论】:

    【解决方案4】:

    假设您拥有 Excel 2016 及更高版本,您可以使用 Power Query 执行此操作。将您的数据范围转换为表格,在表格中选择一个单元格,在“数据”>“获取和转换”中选择“从表格”,然后将以下代码粘贴到 Power Query 编辑器的高级编辑器中(将 Table3 更改为您的表格名称结尾正在)。

    let
        Source = Excel.CurrentWorkbook(){[Name="Table3"]}[Content],
        #"Changed Type" = Table.TransformColumnTypes(Source,{{"Name1", type text}, {"Name2", type text}, {"Name3", type text}, {"Name4", type text}, {"Name5", type text}, {"Name6", type text}}),
        #"Replaced Value" = Table.ReplaceValue(#"Changed Type"," ","",Replacer.ReplaceText,{"Name1", "Name2", "Name3", "Name4", "Name5", "Name6"}),
        #"Added Custom" = Table.AddColumn(#"Replaced Value", "Text.Combine", each Text.Combine({[#"Name1"],[#"Name2"],[#"Name3"],[#"Name4"],[#"Name5"],[#"Name6"]},";")),
        #"Replaced Value1" = Table.ReplaceValue(#"Added Custom",";;","",Replacer.ReplaceText,{"Text.Combine"}),
        #"Split Column by Delimiter" = Table.ExpandListColumn(Table.TransformColumns(#"Replaced Value1", {{"Text.Combine", Splitter.SplitTextByDelimiter(";", QuoteStyle.Csv), let itemType = (type nullable text) meta [Serialized.Text = true] in type {itemType}}}), "Text.Combine"),
        #"Changed Type1" = Table.TransformColumnTypes(#"Split Column by Delimiter",{{"Text.Combine", type text}}),
        #"Removed Duplicates" = Table.Distinct(#"Changed Type1", {"Text.Combine"}),
        #"Filtered Rows" = Table.SelectRows(#"Removed Duplicates", each ([Text.Combine] <> "")),
        #"Removed Columns" = Table.RemoveColumns(#"Filtered Rows",{"Name1", "Name2", "Name3", "Name4", "Name5", "Name6"}),
        #"Renamed Columns" = Table.RenameColumns(#"Removed Columns",{{"Text.Combine", "UniqueList"}})
    in
        #"Renamed Columns"
    

    【讨论】:

    • 我刚刚意识到不需要删除连续的分号,因为稍后会过滤掉空格...只需在 PQE 中右键单击删除,代码就会自动调整。
    • 不幸的是,我被 2013 年困住了,但还是谢谢!
    • 它是 2013 年的一个插件。如果你做很多这样的工作,我会认真看看 Power Query。通常,它比设置工作表公式和使用 VBA 循环遍历数据要高效得多。您还可以使用 VBA 自动创建查询。我能想到的唯一缺点是一些 IT 经理讨厌 Excel 加载项,因为它们有时会造成混乱。但是,一旦您证明了此工具的有用性,您就会对升级 Office 有一个严肃的论据。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-16
    • 2018-06-29
    • 2013-06-26
    • 2021-01-04
    • 2021-09-13
    • 2020-09-10
    • 1970-01-01
    相关资源
    最近更新 更多