【问题标题】:Sum Values based on Duplicates - VBA基于重复的总和值 - VBA
【发布时间】:2021-03-11 14:33:43
【问题描述】:

我正在寻找能够:

  1. 在“A”列和格式中查找重复值。 (可以使用下面的代码)
  2. 找到每个后续重复项后,代码应将列“J”到“N”的所有值求和到第一个值上,并将重复的单元格填充为黑色(帮助)
Sub CombineDuplicates()

Dim Cell As Variant
Dim PList As Range

lRow = Worksheets("Material Planning").Cells(Rows.Count, 1).End(xlUp).Row

Set PList = Worksheets("Material Planning").Range("A4:A" & lRow)

For Each Cell In PList
    
    'Checking whether value in cell already exist in the source range
    If Application.WorksheetFunction.CountIf(PList, Cell) > 1 Then
        
        'Highlight duplicate values in red color
        cRow = Cell.Row
        
        Range("A" & cRow & ":R" & cRow).Interior.Color = RGB(0, 0, 0)
    Else
        Cell.Interior.Pattern = xlNone
    End If
Next


End Sub

请看图片以供参考。顶部是未过滤的数据,底部是宏运行后的外观。如果您需要更多信息,请告诉我。提前致谢!

【问题讨论】:

  • 问题是什么?宏有什么作用,你希望它没有做什么?
  • 看起来您正在尝试手动创建数据透视表?
  • 目前宏能够找到重复值并将重复的整行格式化为填充黑色。我如何/如何为宏编写代码以对所有重复值的相邻列的值求和,并将求和值输入到第一次出现的位置。
  • @DarrellH,你没有错。不幸的是,每次都为这些信息做一个支点并不是这个文件的目标。我们使用此文件进行分析,并试图减少不精通 excel 的用户创建的数据透视/新选项卡的数量。
  • 看起来代码会隐藏所有重复的值,在这种情况下有效地产生一堆黑色行。

标签: excel vba formatting


【解决方案1】:

这使用字典来检测重复项并使用类来保持数据井井有条

将此部分放在类模块中:

Option Explicit

Private data As datasum
Private prow As Long
Private ptargetsheet As Worksheet

Private Type datasum
    thirtyday As Long
    threemonth As Long
    expectedusage As Double
    ordertarget As Double
    stock As Long
    avgdayleft As Long
    dayleft As Long
    pending As Long
End Type

Sub initialize(targetsheet As Worksheet, row As Long)
    Set ptargetsheet = targetsheet
    prow = row
End Sub

Sub addData(dataArray As Variant)
    data.thirtyday = data.thirtyday + dataArray(1, 1)
    data.threemonth = data.threemonth + dataArray(1, 2)
    data.expectedusage = data.expectedusage + dataArray(1, 3)
    data.ordertarget = data.ordertarget + dataArray(1, 4)
    data.stock = data.stock + dataArray(1, 5)
    data.avgdayleft = data.avgdayleft + dataArray(1, 6)
    data.dayleft = data.dayleft + dataArray(1, 8)
    data.pending = data.pending + dataArray(1, 9)
End Sub

Sub placeData()
    With ptargetsheet
        .Cells(prow, 6).Value = data.thirtyday
        .Cells(prow, 7).Value = data.threemonth
        .Cells(prow, 8).Value = data.expectedusage
        .Cells(prow, 9).Value = data.ordertarget
        .Cells(prow, 10).Value = data.stock
        .Cells(prow, 11).Value = data.avgdayleft
        .Cells(prow, 13).Value = data.dayleft
        .Cells(prow, 14).Value = data.pending
    End With
End Sub

这块在您的工作表模块或常规模块中:

Option Explicit

Sub CombineDuplicates()
    Dim i As Long
    Dim lRow As Long
    
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")
    
    Dim data As DataClass
    
    With Sheets("Material Planning")
        lRow = .Cells(.Rows.Count, 1).End(xlUp).row
        For i = 4 To lRow
            If Not dict.exists(.Cells(i, 1).Value) Then
                Set data = New DataClass
                data.initialize Sheets("Material Planning"), i
                data.addData .Range(.Cells(i, 6), .Cells(i, 14)).Value
                dict.Add .Cells(i, 1).Value, data
            Else
                dict(.Cells(i, 1).Value).addData .Range(.Cells(i, 6), .Cells(i, 14)).Value
                dict(.Cells(i, 1).Value).placeData
                .Range(.Cells(i, 1), .Cells(i, 14)).Interior.Color = RGB(0, 0, 0)
            End If
        Next i
    End With
        
End Sub

【讨论】:

    【解决方案2】:

    这将是一种简单但可能不是最快的方法:

    Sub CombineDuplicates()
    
    Dim Cell As Variant, PList As Range
    Dim i As Long, j As Long, a As Long
    Dim k(7) As Long
    LRow = Worksheets(1).Cells(Rows.Count, 1).End(xlUp).Row
    
    For i = 4 To LRow
        Erase k
        If Not Range("A" & i).Interior.Color = RGB(0, 0, 0) Then
            For j = i + 1 To LRow
                If Range("A" & i).Value = Range("A" & j).Value Then
                    For a = 0 To 7
                        k(a) = k(a) + Cells(j, a + 2)
                    Next a
                    Range("A" & j & ":N" & j).Interior.Color = RGB(0, 0, 0)
                End If
            Next j
            For a = 0 To 7
                Cells(i, a + 2) = Cells(i, a + 2) + k(a)
            Next a
        End If
    Next i
    
    End Sub
    

    基本上,对于不是黑色的每一行(为了避免不必要的计算),我们会循环该范围的其余部分以查找重复项。在数组k 中添加值并继续查找。
    然后我们通过将数组中的数字添加到当前行来结束子循环,然后继续。
    应该先添加一些东西来清除内部格式,以便后续运行。

    【讨论】:

      【解决方案3】:

      因此,在坐下来集思广益之后,我发现我试图让事情变得过于复杂。感谢您的回复,它帮助我弄清楚了我想去的方向。这是我拥有的当前代码,它可以完美运行!它有点慢,但由于我不会在数千个数据点中移动,所以它是可以管理的。

      我尝试在代码中插入增值 cmets 来展示流程:

      Sub CombineDuplicates()
      
      Dim Cell As Variant
      Dim PList As Range
      
      Worksheets("Material Planning").Unprotect
      
      Set ws = Worksheets("Material Planning")
      'set last row of working range
      lRow = Worksheets("Material Planning").Cells(Rows.Count, 1).End(xlUp).Row
      
      
      'Toggle parameter. If any cells in range are not colored then it will execute the macro to add common values
      If Range("A4:A" & lRow).Interior.ColorIndex = xlColorIndexNone Then
          
          For i = 1 To lRow
          Application.ScreenUpdating = False
          Application.EnableEvents = False
          
          'since all of the "duplicate" values are listed near each oter, I just need to compare them one after another
          Fst = ws.Range("A" & i)
          Snd = ws.Range("A" & i + 1)
          
          If Snd = Fst Then
          
          'saves the Formula from the cell but just adds the value from the current cell to the next one
          'this way even if there are more than 2 duplicates, the sum will continue on to the next cell
          ws.Range("F" & i + 1).Formula = ws.Range("F" & i + 1).Formula & "+" & ws.Range("F" & i).Value
          ws.Range("G" & i + 1).Formula = ws.Range("G" & i + 1).Formula & "+" & ws.Range("G" & i).Value
          ws.Range("J" & i + 1).Formula = ws.Range("J" & i + 1).Formula & "+" & ws.Range("J" & i).Value
          
          'The whole Row will be filled black so that it is not considered in the analysis
          Range("A" & i & ":U" & i).Interior.Color = RGB(0, 0, 0)
          End If
          Next
      Application.ScreenUpdating = True
      Application.EnableEvents = True
      Else
      
          'if there is already formatting on any cells in column A, this will remove the filled black formatting from all cells in the range
          Range("A4:U" & lRow).Interior.Color = xlNone
          ws.Range("F4:N" & ws.Cells(Rows.Count, 6).End(xlUp).Row).FillDown
          ws.Range("P4:U" & ws.Cells(Rows.Count, 6).End(xlUp).Row).FillDown
           
      End If
      Application.ScreenUpdating = True
      Application.EnableEvents = True
      
      Worksheets("Material Planning").Protect
      End Sub
      

      感谢大家对此的帮助和建议!

      【讨论】:

      • 一个非常小的点:如果if中的第一个子句为true,则运行两行代码将ScreenUpdating和EnableEvents设置为true两次。您不需要紧接在“Else”之前的两行。
      【解决方案4】:

      Excel 具有内置的去重功能。您不能以编程方式将顶部的“简单描述”列复制到下方区域,在包含副本的范围内运行 dedup,然后将 sumifs 添加到其余列吗?

      下面的代码从图中所示的顶部表格创建底部表格。

      Sub Dedup()
      
         Range("A1:A9").Copy
         Range("A12").PasteSpecial
         
         Range("B1:E1").Copy
         Range("B12").PasteSpecial
         
         Range("A13:A20").RemoveDuplicates Columns:=1
         
         Range("B13").Formula = "=SUMIF($A$2:$A$9,$A13,B$2:B$9)"
         
         Range("B13").Copy Destination:=Range("B13:E17")
      
      End Sub
      

      当然,这并不能保持黑色行的结构,但我不明白你为什么需要它,因为你仍然有原始表。

      并且您需要做一些更复杂的事情来识别正确的范围,特别是对于复制的表格以及将 sumif 公式从第一个单元格复制到重复数据删除产生的范围内的最后一个单元格时。为了方便起见,我在这里保持简单。

      编辑:如果您希望底部表格反映原始表格的结构,您可以对副本中的每一行进行计数,并插入所需的行数,然后创建新行黑色。

      【讨论】:

        【解决方案5】:

        特殊粘贴xlPasteSpecialOperationAdd

        • 这是一个缓慢的解决方案,但可能很容易理解。
        • 它遍历A 列中的单元格并使用Application.Match 查找第一次出现的索引(位置)。如果不相同,则为行着色并使用PasteSpecialxlPasteSpecialOperationAdd 将找到的值添加到索引定义的值中。
        • Application.ScreenUpdating 将加速隐藏正在进行的“工作表舞”的代码。

        守则

        Option Explicit
        
        Sub CombineDuplicates()
        
            Dim ws As Worksheet
            Dim PList As Range
            Dim Cell As Range
            Dim ColsAll As Range
            Dim Cols1 As Range
            Dim Cols2 As Range
            Dim cIndex As Variant
            Dim lRow As Long
            Dim cRow As Long
            
            Set ws = Worksheets("Material Planning")
            lRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
            Set PList = ws.Range("A4:A" & lRow)
            
            Set ColsAll = ws.Columns("A:N")
            Set Cols1 = ws.Columns("F:K")
            Set Cols2 = ws.Columns("M:N")
            
            Application.ScreenUpdating = False
                
            For Each Cell In PList.Cells
                cRow = Cell.Row
                cIndex = Application.Match(Cell.Value, PList, 0) + 3
                If cIndex < cRow Then
                    ColsAll.Rows(cRow).Interior.Color = RGB(0, 0, 0)
                    Cols1.Rows(cRow).Copy
                    Cols1.Rows(cIndex) _
                        .PasteSpecial xlPasteValues, xlPasteSpecialOperationAdd
                    Cols2.Rows(cRow).Copy
                    Cols2.Rows(cIndex) _
                        .PasteSpecial xlPasteValues, xlPasteSpecialOperationAdd
                Else
                    ColsAll.Rows(cRow).Interior.Pattern = xlNone
                End If
            Next
            Application.CutCopyMode = False
            ws.Range("A3").Select
            
            Application.ScreenUpdating = True
            
        End Sub
        

        【讨论】:

          【解决方案6】:

          请试试这个代码。它应该非常快,使用数组并且只在内存中工作并且不需要着色任何东西。处理结果,这意味着只有每列具有必要总和的唯一值才会被丢弃在处理后添加的新工作表上:

          Sub CombineDuplicates()
           `It needs a reference to 'Microsoft Scripting Runtime'
           Dim LROW As Long, arrA, arr, arrR(4), arrF, dict As New Scripting.Dictionary
           Dim sh As Worksheet, resSh As Worksheet, i As Long, j As Long, arrFin
          
           Set sh = Worksheets("Material Planning")
           LROW = sh.cells(rows.Count, 1).End(xlUp).row
          
           arrA = sh.Range("A4:A" & LROW).value
           arr = sh.Range("J4:N" & LROW).value
          
           For i = 1 To UBound(arrA)
              If Not dict.Exists(arrA(i, 1)) Then
                  For j = 0 To 4
                      arrR(j) = arr(i, j + 1)
                  Next j
                  dict.Add arrA(i, 1), arrR
              Else
                  For j = 0 To 4
                      arrR(j) = dict(arrA(i, 1))(j) + arr(i, j + 1)
                  Next j
                  dict(arrA(i, 1)) = arrR
              End If
           Next i
          
           ReDim arrFin(1 To dict.Count, 1 To 5)
           ReDim arrF(1 To dict.Count, 1 To 1)
           For i = 0 To dict.Count - 1
              arrF(i + 1, 1) = dict.Keys(i)
              For j = 0 To 4
                  arrFin(i + 1, j + 1) = dict.items(i)(j)
              Next
           Next i
           Set resSh = Worksheets.Add(After:=sh) 'add a new sheet aftere the active one and drop the array at once
          
           resSh.Range("A2").Resize(UBound(arrF), 1).value = arrF
           resSh.Range("J2").Resize(UBound(arrFin), UBound(arrFin, 2)).value = arrFin
          End Sub
          

          这种方法将允许在最终更新后或以防万一时多次运行代码。否则,它会在下次返回双日期...

          如果您在添加必要的引用时遇到问题,请在能够处理您的数据的代码之前运行下一个代码:

          Sub addScrRunTimeRef()
            'Add a reference to 'Microsoft Scripting Runtime':
            'In case of error ('Programmatic access to Visual Basic Project not trusted'):
            'Options->Trust Center->Trust Center Settings->Macro Settings->Developer Macro Settings->
            '         check "Trust access to the VBA project object model"
            Application.VBE.ActiveVBProject.References.AddFromFile "C:\Windows\SysWOW64\scrrun.dll"
          End Sub
          

          已编辑

          如果您坚持保留所有范围,并将重复的内部设置为黑色,您可以尝试下一个代码,速度也很快。它还将返回一个新创建的工作表,但仅出于测试原因。如果它符合您的要求,则可以轻松调整代码以覆盖活动工作表的现有范围:

          Sub CombineDuplicatesKeepAll()
           Dim LROW As Long, arrA, arrR(14), arrF, dict As New Scripting.Dictionary
           Dim sh As Worksheet, resSh As Worksheet, i As Long, j As Long, arrFin, firstR As Long
           Dim rngCol As Range, k As Long
          
           Set sh = Worksheets("Material Planning")
           LROW = sh.cells(rows.Count, 1).End(xlUp).row
           firstR = 4 'first row of the range to be processed
          
           arrA = sh.Range("A" & firstR & ":N" & LROW).value     'place the range to be processed in an array
           ReDim arrFin(1 To UBound(arrA), 1 To UBound(arrA, 2)) 'set the final array at the same dimensions
          
           For i = 1 To UBound(arrA) 'iterate between the array elements
              If Not dict.Exists(arrA(i, 1)) Then 'if not a dictionary key as value in column A:A (array column 1):
                  arrR(0) = sh.Range("A" & i + firstR - 1).Address 'place the cell address like forst dictionary item array element
                  arrR(1) = i                                      'the array second element will be the array row (to update it later)
                  arrFin(i, 1) = arrA(i, 1)     'first element of the final array, on i row will be the first column value
                  For j = 2 To 14
                      arrR(j) = arrA(i, j)      'input the rest of the row values in the array to be the dictionary item
                      arrFin(i, j) = arrA(i, j) 'place the same values in the final array
                  Next j
                  dict.Add arrA(i, 1), arrR     'add the array built above like dictionary item
              Else
                  arrR(0) = dict(arrA(i, 1))(0) 'keep the same call address like the first element of the array to be input as item
                  arrFin(i, 1) = arrA(i, 1)     'place the value in column A:A in the first column of the final array
                  arrR(1) = dict(arrA(i, 1))(1) 'keep the row of the first dictionary key occurrence
                  For j = 2 To 14    'fill the array with the values of all row columns
                      If j <= 9 Then 'for first 9 columns keep their value
                          arrR(j) = dict(arrA(i, 1))(j)
                      Else           'for the rest (J to N) add the existing value (in dictionary) to the cells value
                          arrR(j) = dict(arrA(i, 1))(j) + arrA(i, j)
                      End If
                      arrFin(i, j) = arrA(i, j) 'fill the final array with the row data
                  Next j
                  dict(arrA(i, 1)) = arrR       'place the array like dictionary item
                  If rngCol Is Nothing Then     'if range to be colored does not exist, create it:
                      Set rngCol = sh.Range("A" & i + firstR - 1 & ":N" & i + firstR - 1)
                  Else                          'if it exists, make a Union between existing and the new one:
                      Set rngCol = Union(rngCol, sh.Range("A" & i + firstR - 1 & ":N" & i + firstR - 1))
                  End If
              End If
           Next i
           
           'adapt te final array rows which used to be the first occurrence of the same dictionary key:
           For i = 0 To dict.Count - 1
              k = dict.items(i)(1)  'extract the previously memorized row to be updated
              For j = 2 To 14       'adapt the row content, for the row range equivalent columns
                  arrFin(k, j) = dict.items(i)(j)
              Next
           Next i
           'just for testing, paste the result in a new added sheet.
           'If everything OK, the code can drop the value in the active sheet
           Set resSh = Worksheets.Add(After:=sh)
           'drop the array content at once:
           resSh.Range("A4").Resize(UBound(arrFin), UBound(arrFin, 2)).value = arrFin
           If Not resSh Is Nothing Then _
               resSh.Range(rngCol.Address).Interior.Color = vbBlack 'color the interior of the next occurrences
          End Sub
          

          我尝试以易于理解的方式注释代码行。如果有不清楚的地方,请随时要求澄清。

          请在测试后发送一些反馈。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-10-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多