Group rows by value over more than one sheet - excel

ive been looking into adding a groupBy function on a couple of sheets however i want to reference 5 sheets. In total i have about 7 but two of the worksheets have the common values in different columns (the five sheets have common variables in column B however two have the same variables in column G) otherwise i would have used ActiveSheet.
I've attached Chris Neilsen's example
Chris Neilsen's Group By Function
Sub demo()
Dim r As Range
Dim v As Variant
Dim i As Long, j As Long
With ActiveSheet
On Error Resume Next
' expand all groups on sheet
.Outline.ShowLevels RowLevels:=8
' remove any existing groups
.Rows.Ungroup
On Error GoTo 0
Set r = .Range("B1", .Cells(.Rows.Count, 2).End(xlUp))
End With
With r
'identify common groups in column B
j = 1
v = .Cells(j, 1).Value
For i = 2 To .Rows.Count
If v <> .Cells(i, 1) Then
' Colum B changed, create group
v = .Cells(i, 1)
If i > j + 1 Then
.Cells(j + 1, 1).Resize(i - j - 1, 1).Rows.Group
End If
j = i
v = .Cells(j, 1).Value
End If
Next
' create last group
If i > j + 1 Then
.Cells(j + 1, 1).Resize(i - j - 1, 1).Rows.Group
End If
' collapse all groups
.Parent.Outline.ShowLevels RowLevels:=1
End With
End Sub
I have tried the below code to link the sheets in the above example however it is out of range
With Worksheets(Array("Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5")).Select

This can be easily done by adding a parameter to the sub code from Chris Neilsen:
Sub demo(ByRef ws As Worksheet)
Dim r As Range
Dim v As Variant
Dim i As Long, j As Long
With ws
On Error Resume Next
' expand all groups on sheet
.Outline.ShowLevels RowLevels:=8
' remove any existing groups
.Rows.Ungroup
On Error GoTo 0
Set r = .Range("B1", .Cells(.Rows.Count, 2).End(xlUp))
End With
With r
'identify common groups in column B
j = 1
v = .Cells(j, 1).Value
For i = 2 To .Rows.Count
If v <> .Cells(i, 1) Then
' Colum B changed, create group
v = .Cells(i, 1)
If i > j + 1 Then
.Cells(j + 1, 1).Resize(i - j - 1, 1).Rows.Group
End If
j = i
v = .Cells(j, 1).Value
End If
Next
' create last group
If i > j + 1 Then
.Cells(j + 1, 1).Resize(i - j - 1, 1).Rows.Group
End If
' collapse all groups
.Parent.Outline.ShowLevels RowLevels:=1
End With
End Sub
Now call that routine with any worksheet, or in a loop:
Sub Test()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Sheets
demo ws
Next ws
End Sub
PS: I do suggest changing the name of demo to something more appropriate, such as GroupDataOnSheet.

Related

Insert new rows to separate groups of data via a for loop

I'm using a for loop but I'm open suggestions if there's a better way to separate the data!
I want to insert two new rows whenever the integer in Column 11 or "K" changes. Column K represents groups of data and each is named with integers between 1 and 10 (inclusive). Each group varies in size, hence why I wanted a for loop to check each time the group increments to trigger the insertion of the rows.
For example:
From the data below two blank rows should be inserted below K11 and below K18. This will result in the data being separated by two blank rows whenever two groups were 'touching' each other.
K2 = 1, K3 = 1, K4 = 1 ... K11 = 1
K12 = 2, K13 = 2, K14 = 2... K18 = 2
K19 = 3, K20 = 3 ...
I've put together the following for loop but it inserts 500 (the counter limit) rows after the first group and no row inserts for the remaining groups. Can you explain why this happens and how I can work around this?
Dim LCounter As Integer
For LCounter = 2 To 500
If Cells(LCounter + 1, 11).Value <> Cells(LCounter, 11) Then
Rows(LCounter + 1).Insert shift:=xlShiftDown
End If
Next LCounter
Try this way, please. It should be very fast even for big ranges:
Sub SeparateGroupsByEmptyRows()
Dim LCounter As Long, col As Long, rng As Range
col = 11
For LCounter = 2 To 500
If cells(LCounter + 1, col).Value <> cells(LCounter, col).Value Then
If rng Is Nothing Then
Set rng = cells(LCounter + 1, col)
Else
Set rng = Union(rng, cells(LCounter + 1, col))
End If
End If
Next LCounter
'For the case of two or more consecutive groups of only one row each:
If InStr(rng.Address(0, 0), ":") > 0 Then Set rng = makeDiscontinuu(rng)
rng.EntireRow.Insert Shift:=xlDown
End Sub
Function makeDiscontinuu(rng As Range) As Range
Dim A As Range, c As Range, strAddress As String
For Each A In rng.Areas
If A.cells.count = 1 Then
strAddress = strAddress & A.Address(0, 0) & ","
Else
For Each c In A.cells
strAddress = strAddress & c.Address(0, 0) & ","
Next c
End If
Next A
Set makeDiscontinuu = Range(left(strAddress, Len(strAddress) - 1))
End Function
try this, should be one empty row separation (not tested)
Dim LCounter As Integer, lcEnd as integer: lcEnd =500
For LCounter = 2 To lcEnd
If Cells(LCounter + 1, 11).Value <> Cells(LCounter, 11) and Cells(LCounter + 1, 11)<> "" Then
Rows(LCounter + 1).Insert shift:=xlShiftDown
lcEnd =lcEnd +1
End If
Next LCounter
Insert Rows Before Change of Cell Value
The first procedure uses For...Next to solve the problem by looping backwards.
The second procedure uses Do...Loop illustrating the complications when looping forwards.
The Code
Option Explicit
Sub insertBeforeChangeForNext()
Const iRows As Long = 2 ' Number of Rows to Insert
Const cCol As Long = 11 ' Criteria Column
Const fRow As Long = 2 ' First Row
' Either...
Const lRow As Long = 500 ' Last Row
' ...or rather determine the last non-empty row:
'Dim lRow As Long: lRow = Cells(Rows.Count, cCol).End(xlUp).Row ' LR
If lRow <= fRow Then
Exit Sub
End If
Application.ScreenUpdating = False
Dim LCounter As Long ' Rows Counter
For LCounter = lRow - 1 To fRow Step -1
If Cells(LCounter + 1, cCol).Value <> Cells(LCounter, cCol).Value Then
Rows(LCounter + 1).Resize(iRows).Insert
End If
Next LCounter
Application.ScreenUpdating = True
End Sub
Sub insertBeforeChangeDoLoop()
Const iRows As Long = 2 ' Number of Rows to Insert
Const cCol As Long = 11 ' Criteria Column
Const fRow As Long = 2 ' First Row
' Either...
Const lRow As Long = 500 ' Initial Last Row
' ...or rather determine the last non-empty row:
'Dim lRow As Long: lRow = Cells(Rows.Count, cCol).End(xlUp).Row ' ILR
Dim Current As Long: Current = fRow ' Current Row
Dim Last As Long: Last = lRow ' Current Last Row
Application.ScreenUpdating = False
Do While Current < Last
If Cells(Current + 1, cCol).Value <> Cells(Current, cCol).Value Then
Rows(Current + 1).Resize(iRows).Insert
Last = Last + iRows
Current = Current + iRows
End If
Current = Current + 1
Loop
Application.ScreenUpdating = True
End Sub

I want to run the same macro on a selection of worksheets in a workbook

This code successfully performs calculations on one of the worksheets in my workbook
Sub test()
Dim r As Range, j As Long, k As Long
j = Range("A1").End(xlToRight).Column
'changing the first value of k stops it adding up columns not required
For k = 8 To j - 1
Set r = Range(Cells(1, k), Cells(1, k).End(xlDown))
r.End(xlDown).Offset(2, 0) = WorksheetFunction.Sum(r)
Next k
End Sub
However I want the macro to run the same procedure on a selection of worksheets in my workbook. I found some code to 'wrap around' my existing code to allegedly perform the same procedure on each of the selected sheets but unfortunately it only performs the calculations on the sheet that is active at the time. So here is the code I am using (I am new to VBA)...
Sub test()
Dim WkSheets As Variant, SheetName As Variant, ws As Worksheet
'** SET The Sheet Names - MUST Reflect Each Sheet Name Exactly!
WkSheets = Array("Amazon DE FBA", "Amazon Fr", "Amazon Japan", "Bol", "CDiscount", "EBAY4", "Fragrancia")
For Each SheetName In WkSheets
MsgBox SheetName
For Each ws In ActiveWorkbook.Worksheets
If ws.Name = SheetName Then
Dim r As Range, j As Long, k As Long
j = Range("A1").End(xlToRight).Column
'changing the first value of k stops it adding up un-needed columns
For k = 8 To j - 1
Set r = Range(Cells(1, k), Cells(1, k).End(xlDown))
r.End(xlDown).Offset(2, 0) = WorksheetFunction.Sum(r)
Next k
'End If
Next
Next
End Sub
The problem is that all Range and Cells calls need to be qualified with the Worksheet in question in these lines, otherwise they implicitly refer to the ActiveSheet.
j = Range("A1").End(xlToRight).Column
...
Set r = Range(Cells(1, k), Cells(1, k).End(xlDown))
That is easily done with a With statement and added periods:
With ws
Dim r As Range, j As Long, k As Long
j = .Range("A1").End(xlToRight).Column
'changing the first value of k stops it adding up un-needed columns
For k = 8 To j - 1
Set r = .Range(.Cells(1, k), .Cells(1, k).End(xlDown))
r.End(xlDown).Offset(2, 0) = WorksheetFunction.Sum(r)
Next k
End With
Note the periods . in .Range("A1") and .Range and .Cells(1, k)... now each of those is qualified with the worksheet in question, namely ws.

VBA: Condense worksheet (multiple cols) to 2 columns based on header name and column value

I have a workbook that contains several sheets of data that I have combined. I removed some unnecessary sheets and cells (that are colour filled) and removed blanks (code sample below). I now have one work sheet with dates as headers and item numbers (col length vary).
I need to condense this again. I need two columns, columns A and B, B for every item number pulled back from the sheet and the Col A needs to be the header name of the column the item number was pulled from. The amount of columns will extend over time as more dates are added.
I just don't know where to go from here... The script is basic 'and then' I have quality checked it and it works up to this point.
Worksheets.Add Sheets(1)
ActiveSheet.Name = "Combined"
For i = 2 To Sheets.Count
Set xRg = Sheets(1).UsedRange
If i > 2 Then
Set xRg = Sheets(1).Cells(xRg.Rows.Count + 1, 1)
End If
Sheets(i).Activate
ActiveSheet.UsedRange.Copy xRg
Next i
Sheets("Data").Delete
For Each ws In Worksheets
If ws.Name <> "Combined" Then
ws.Visible = xlSheetHidden
End If
Next ws
I then have a box pop up to delete specific coloured cells and end with this:
Columns("A:MK").SpecialCells(xlCellTypeBlanks).Delete Shift:=xlUp
I can copy column values over, after the above, to a new sheet but then adding header values based on the last cell in that column reaches my limitations of VBA.
I can't see that this has been asked and answered previously, any ideas?
Try this code
Sub Test()
Dim a, ws As Worksheet, sh As Worksheet, i As Long, j As Long, k As Long
Set ws = ThisWorkbook.Worksheets("Combined")
Set sh = ThisWorkbook.Worksheets("Condensed")
a = ws.Range("A1").CurrentRegion.Value
ReDim b(1 To UBound(a, 1) * UBound(a, 2), 1 To 2)
For j = LBound(a, 2) To UBound(a, 2)
For i = 2 To UBound(a)
k = k + 1
b(k, 1) = a(1, j)
b(k, 2) = a(i, j)
Next i
Next j
With sh.Range("A1")
.Resize(1, 2).Value = Array("Header1", "Header2")
.Offset(1).Resize(k, UBound(b, 2)).Value = b
End With
End Sub
you could use Dictionary object
assuming you want to condense data in a worksheet named "Condensed" already in place
Sub Condense()
Dim cel As Range
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
With Worksheets("Combined")
For Each cel In .Range("A1", .Cells(1, .Columns.Count).End(xlToLeft))
dict.Add cel.Value, .Range(cel.Offset(1), cel.End(xlDown)).Value
Next
End With
Dim key As Variant
With Worksheets("Condensed")
For Each key In dict.keys
With .Cells(.Rows.Count, 1).End(xlUp).Offset(1).Resize(UBound(dict(key)))
.Value = key
.Offset(, 1) = dict(key)
End With
Next
End With
End Sub

fastest way to merge duplicate cells in without looping Excel

I have cells containing duplicate values that i want to merge quickly. The table looks like this:
Sub MergeCells()
Application.DisplayAlerts = False
Dim n As Name
Dim fc As FormatCondition
Dim Rng As Range, R As Range
Dim lRow As Long
Dim I&, J&
Dim arr As Variant
ReDim arr(1 To 1) As Variant
With ThisWorkbook.Sheets("tst")
Set Rng = .Range("A2:D11")
lRow = Rng.End(xlDown).Row
For J = 1 To 4
For I = lRow To 2 Step -1 'last row to 2nd row
If Trim(UCase(.Cells(I, J))) = Trim(UCase(.Cells(I - 1, J))) Then
Set R = .Range(.Cells(I, J), .Cells(I - 1, J))
arr(UBound(arr)) = R.Address
ReDim Preserve arr(1 To UBound(arr) + 1)
End If
Next I
Next J
ReDim Preserve arr(1 To UBound(arr) - 1)
Set R = .Range(Join(arr, ","))
'MsgBox R.Areas.Count
'R.Select
'R.MergeCells = True
With R
.Merge
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
Stop
End With
Application.DisplayAlerts = True
End Sub
The duplicate cells ranges could be disjointed or non-adjacent cells. I want a way to quickly identify such duplicate ranges and merge them without using a For loop. [Don't know, but think there could be a fastest innovative way without loops probably using some combination of Excel array formulae and VBA code, to select and merge duplicate cell ranges.]
BTW the above code works fine till it shoots up the following error at line .Merge.
EDIT
This is a snapshot of the Watch window showing the arr content as well as R.Address.
OUTPUT:
Don't need any selections, this is just for demonstration purpose:
Output should look like this:
EDIT...
Suppose the duplicate values were same across the rows? So only duplicate columns values to be merged. There has to be an quick, innovative way to do this merge.
Final Output Image:
The issue is that your code can only find 2 adjacent cells and is not looking for a third one with this code: Set R = .Range(.Cells(I, J), .Cells(I - 1, J))
After the first loop it adds these 2 cells
After another loop it adds the next 2 cells
And this results in an overlapping
which you can see at the darker shading of the selection.
I just edited some part of your code with comments, so you can see how it could be done. But I'm sure there is still space for improvements.
Sub MergeCellsNew()
Application.DisplayAlerts = False
Dim n As Name
Dim fc As FormatCondition
Dim Rng As Range, R As Range
Dim lRow As Long
Dim I&, J&
Dim arr As Variant
ReDim arr(1 To 1) As Variant
With ThisWorkbook.Sheets("tst")
Set Rng = .Range("A2:D11")
lRow = Rng.End(xlDown).Row
For J = 1 To 4
I = 2 'I = Rng.Row to automatically start at the first row of Rng
Do While I <= lRow
Set R = .Cells(I, J) 'remember start cell
'run this loop as long as duplicates found next to the start cell
Do While Trim(UCase(.Cells(I, J))) = Trim(UCase(.Cells(I + 1, J)))
Set R = R.Resize(R.Rows.Count + 1) 'and resize R + 1
I = I + 1
Loop
'now if R is bigger than one cell there are duplicates we want to add to the arr
'this way single cells are not added to the arr
If R.Rows.Count > 1 Then
arr(UBound(arr)) = R.Address
ReDim Preserve arr(1 To UBound(arr) + 1)
End If
I = I + 1
Loop
Next J
ReDim Preserve arr(1 To UBound(arr) - 1)
Set R = .Range(Join(arr, ","))
With R
.Merge
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
Stop
End With
Application.DisplayAlerts = True
End Sub

Removing duplicates given row's value VBA

I have a spreadsheet with thousands of rows and in column B there are numerous duplicates and then in column G there is that row's respective value. I need to remove the duplicates from column B, but leave in the row that has the highest value (i.e max column G). Is there a way to automate this via VBA as it'll need to be done on numerous occasions?
Thanks
You can try this:
Sub test()
Dim i As Long, j As Long
Dim wb As Workbook, ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1") ' Change the name of your sheet
With ws
i = 1 ' Start at Row 1
Do While Not IsEmpty(.Cells(i, 2))
j = 1 ' Start at Row 1
Do While Not IsEmpty(.Cells(j, 2))
If i <> j Then
If .Cells(i, 2).Value = Cells(j, 2).Value Then
If .Cells(i, 7).Value > Cells(j, 7) Then
Rows(j).EntireRow.Delete
j = j - 1
Else
Rows(i).EntireRow.Delete
If i > 1 Then i = i - 1
j = 1
End If
End If
End If
j = j + 1
Loop
i = i + 1
Loop
End With
End Sub

Resources