Macro in Excel to remove rows containing all 0's - string

Okay so here is the table set up I'm working with:
I need a macro to remove the rows containing four 0's, the only way I can think of at the moment requires the cells to be empty, i.e. ""
Does 0 actually count as a string or digit or is it equivalent to "" ?
I think the problem might be related to the fact that some of my 0's are text strings and others are numbers, I just didn't think this would matter.

Try this small macro.....it examines the sum of the section of each row:
Sub RowKiller()
Dim N As Long, i As Long, wf As WorksheetFunction
Dim rng As Range
N = Cells(Rows.Count, "A").End(xlUp).Row
Set wf = Application.WorksheetFunction
For i = N To 2 Step -1
Set rng = Range(Cells(i, 1), Cells(i, 4))
If wf.Sum(rng) = 0 Then
rng.EntireRow.Delete
End If
Next i
End Sub

Here, I got one for you. Try with this.
Public Sub removeRow()
Dim row As Integer
'Set the start row.
row = 1
'Loop all row from sheet until colum "A" cell is blank
Do While Sheets("sheetname").Range("A" & row) <> ""
'If all cell are 0.
If Sheets("sheetname").Range("A" & row) = 0 And Sheets("sheetname").Range("B" & row) = 0 And Sheets("sheetname").Range("C" & row) = 0 And Sheets("sheetname").Range("D" & row) = 0 Then
'Delete entire row
Sheets("sheetname").Range("A" & row).EntireRow.Delete
Else
'Increse row
row = row + 1
End If
Loop
End Sub

Create a string of rows to delete then do ONE delete. No need to poll backwards when you do it this way and it should be a LOT faster than deleting row by row:
Sub DeleteRows()
Dim i As Long, DelRange As String
For i = 1 To Cells(Rows.Count, 1).End(xlUp).Row 'Doesn't matter which way you go when you delete in one go
If CLng(Cells(i, 1)) = 0 And CLng(Cells(i, 2)) = 0 And CLng(Cells(i, 3)) = 0 And CLng(Cells(i, 4)) = 0 Then DelRange = DelRange & "," & i & ":" & i
Next i
Range(Right(DelRange, Len(DelRange) - 1)).Delete
End Sub
Used CLng to convert the string zero to a Long zero for the test.
A small word of warning though, CLng(activecell) will return 0 if the activecell is blank so blank rows will be deleted also.
Edit: Put in a IsNumeric test to counter errors when strings are encountered (Can't CLng a true string)
Sub DeleteRows()
Dim i As Long, DelRange As String
For i = 1 To Cells(Rows.Count, 1).End(xlUp).Row 'Doesn't matter which way you go when you delete in one go
If IsNumeric(Cells(i, 1)) And IsNumeric(Cells(i, 2)) And IsNumeric(Cells(i, 3)) And IsNumeric(Cells(i, 4)) Then
If CLng(Cells(i, 1)) = 0 And CLng(Cells(i, 2)) = 0 And CLng(Cells(i, 3)) = 0 And CLng(Cells(i, 4)) = 0 Then DelRange = DelRange & "," & i & ":" & i
End If
Next i
Range(Right(DelRange, Len(DelRange) - 1)).Delete
End Sub

Related

Sum Values based on Duplicates - VBA

I am looking for a VBA solution to be able to:
Look for duplicated values in column "A" and format. (Possible with the code below)
With each subsequent duplicate found, the code should sum all the values from Columns "J" through "N" on the first value and fill the duplicated cell black (help)
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
Please see the picture for reference. Top is unfiltered data and the bottom is how it should look after the macro runs. Please let me know if you need any more information. Thanks in advance!
This uses a dictionary to detect duplicates and a class to keep your data organized
Place this piece inside of a class module:
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
And this piece in either your sheet module or a regular module:
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
This would be a simple, but probably not the fastest way of doing it:
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
Essentially, for each row that isn't black (to avoid unnessecary calculaitons) we loop the rest of the range to look for duplicats. Add the values in the array k and keep looking.
Then we end the subloop by adding the number from the array to the current row, and keep going.
Should probably add something to clear the interior formatting first, for subsequent runs.
So after sitting and brainstorming for a while, I figured that I was trying to overcomplicate things. Thanks to your responses it helped me figure out the direction that I wanted to go. This is the current code that I have which is working flawlessly! It is a little slow, but since I am not going to be shifting through thousands of data points, its is manageable.
I tried to insert value added comments in the code to show the process:
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
Thank you all for your help and advice on this!
Excel has a built-in dedup function. Can you not programmatically copy the 'Simple Description' column at the top to the area underneath, run the dedup on the range containing the copy, then add sumifs to the remaining columns?
The code below creates the bottom table from the top table shown in the picture.
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
Of course, this doesn't maintain the structure with the black rows, but I haven't understood why you need that anyway, since you still have the original table.
And you'll want to do something a little more sophisticated about identifying the correct ranges, particularly for the copied table and when copying the sumif formula from the first cell to the last cell in the range that results from the deduplication. I've kept it simple here for expediency.
Edit: If you want the bottom table to reflect the structure of the original table, you could do a countif on each of the rows in the copy and insert the requisit number of rows that that gives you, and make the new rows black.
Paste Special xlPasteSpecialOperationAdd
This is a slow solution but may be easily understood.
It loops through the cells in column A and uses Application.Match to find the index (position) of the first occurrence. If it is not the same then it colors the row and uses PasteSpecial with xlPasteSpecialOperationAdd to add the found values to the values defined by the index.
Application.ScreenUpdating will speed up the code hiding the on-going 'worksheet dance'.
The Code
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
Try this code, please. It should be very fast, using arrays and working only in memory and does not need to color anything. The processing result, meaning only the unique values with the necessary sum per each column will be dropped on a new sheet added after the processed one:
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
This approach will allow running the code as many times you need, after eventual updates or just in case. Otherwise, it will return double dates each next time...
If you have a problem with adding the necessary reference, please run the next code before the one able to process your data:
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
Edited:
If you insist to keep all the range, and making black the interior of duplicates, you can try the next code, also very fast. It will also return in a newly created sheet, but only for testing reason. If it does what you want, the code can be easily adapted to overwrite the existing range of the active sheet:
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
I tried commenting the code lines, in a way to be easily understood. If something unclear, do not hesitate to ask for clarifications.
Please, send some feedback after testing it.

Filter out entire rows if group value is below 10

I am trying to remove rows from a spreadsheet in VBA if the sum total of value exceeds a specific amount.
For example, if I have the following data, names in A1 down and values in A2 down:
I would like to remove all rows where the total sum of the value in row A does not reach 10 or above in row B, this would leave the following results:
Thomas = 18 and John = 15 so all rows with Thomas and John are kept.
All other rows would be deleted.
Please note that I will always know that the data is in row A and B but I do not know how many rows there will be and need to execute until the first blank cell.
It worked. You can see this here:
Sub run()
Dim rowIndex, countSameRow, sumSameRow As Integer
sumSameRow = Cells(1, 2)
rowIndex = 2
countSameRow = 1
While IsEmpty(Cells(rowIndex, 1)) = False
If (Cells(rowIndex, 1) = Cells(rowIndex - 1, 1)) Then
sumSameRow = sumSameRow + Cells(rowIndex, 2)
countSameRow = countSameRow + 1
Else
If (sumSameRow < 10) Then
Rows(rowIndex - 1 & ":" & rowIndex - countSameRow).Delete
rowIndex = rowIndex - countSameRow
End If
countSameRow = 1
sumSameRow = Cells(rowIndex, 2)
End If
If IsEmpty(Cells(rowIndex + 1, 1)) Then
If (sumSameRow < 10) Then
Rows(rowIndex & ":" & rowIndex - countSameRow + 1).Delete
End If
End If
rowIndex = rowIndex + 1
Wend
End Sub
Totally agree you should write your own code first, but I couldn't help but write some starting code for you. See if the below fits your purpose:
Sub Test()
Dim lr As Long, x As Long
Dim arr As Variant, rng As Range
Dim dict1 As Object: Set dict1 = CreateObject("Scripting.Dictionary")
Dim dict2 As Object: Set dict2 = CreateObject("Scripting.Dictionary")
With Sheet1 'Change according to your sheets CodeName
'Get all of your data form A:B in memory (array)
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
arr = .Range("A1:B" & lr)
'Step through the array and fill up our two dictionaries
For x = LBound(arr) To UBound(arr)
If dict1(arr(x, 1)) <> "" Then
dict1(arr(x, 1)) = Join(Array(dict1(arr(x, 1)), x & ":" & x), ",")
Else
dict1(arr(x, 1)) = x & ":" & x
End If
dict2(arr(x, 1)) = dict2(arr(x, 1)) + arr(x, 2)
Next x
'Step through our second dictionary and check if value < 10
For Each Key In dict2.keys
If dict2(Key) < 10 Then
If Not rng Is Nothing Then
Set rng = Union(rng, .Range(dict1(Key)))
Else
Set rng = .Range(dict1(Key))
End If
End If
Next Key
'If any where below 10, this Range object has been filled, so delete it.
If Not rng Is Nothing Then
rng.Delete
End If
End With
End Sub
Here is another method that uses Autofilter and SUMIF to delete the lines.
This assumes there is a header row, if not then add a row first.
It adds a sumif in column C and filters all that is less than 10, then deletes them.
Then removes column C again.
Sub filter()
Range("C1").Value = "Sum"
Lastrow = Range("A" & Rows.Count).End(xlUp).Row
Range("C2:C" & Lastrow).Formula = "=sumif(A:A,A2,B:B)"
Range("A2").AutoFilter ' add a filter to table
ActiveSheet.Range("$A$1:$C$" & Lastrow).AutoFilter Field:=3, Criteria1:="<10", Operator:=xlAnd ' filter all below 10
ActiveSheet.Range("A2:C" & Lastrow).SpecialCells(xlCellTypeVisible).EntireRow.Delete ' delete them
Range("A1").AutoFilter ' remove filter again
Columns("C:C").EntireColumn.Delete ' remove column C
End Sub

How to create a nested loop to check if a value exists in a second list

I am trying to compare values in two lists. I want my code to compare a value in the first list and check all the entries in the second list. If there is a match then the code will print true next to the value in the first list and if not it will print false.
The problem I am having is that my code only compares values that are in the same row.
The code runs and I have tried it on a two smaller lists to make sure the data types are to same and there aren't any extra spaces or commas in the lists that would lead to a "False" output. I have also tried changing the order of the for and if statements but this doesn't work either.
Sub findvalues()
For i = 2 To 16
For j = 2 To 16
If Cells(i, 3).Value = Cells(i, 1).Value Then
Cells(i, 4).Value = "TRUE"
ElseIf Cells(i, 3).Value = Cells(j + 1, 1).Value Then
Cells(i, 4).Value = "TRUE"
Else
Cells(i, 4).Value = "FALSE"
End If
Next j
Next i
End Sub
Here are the two lists I am testing the code on
Slight mods to your code based on the data you provided in columns 1 & 3. As always, things could be improved but this should get you going ...
Sub findvalues()
Dim i As Long, j As Long, bResult As Boolean
For i = 2 To 16
strValueToLookFor = Cells(i, 1)
For j = 2 To 16
bResult = False
If strValueToLookFor = Cells(j, 3).Value Then
bResult = True
Exit For
End If
Next j
Cells(i, 6).Value = bResult
Next i
End Sub
... you may just need to flick the columns over so the first list searches on the second list or vice versa.
I don't see any need for VBA - formulas are the way to go - but to avoid two loops one could do this:
Sub findvalues()
Dim i As Long
For i = 2 To 130
Cells(i, 4).Value = IsNumeric(Application.Match(Cells(i, 1).Value, Range("C2:C130"), 0))
Next i
End Sub
Update: this does not cater for multiple matches.
There are many was to achieve that. one of them is by using IF & COUNTIF
Formula
=IF(COUNTIF($E$2:$E$6,A2)>0,"TRUE","FALSE")
Results:
VBA CODE
Option Explicit
Sub findvalues()
Dim i As Long
Dim rng As Range
With ThisWorkbook.Worksheets("Sheet1") 'Change if needed
Set rng = .Range("A2:A130") 'set rng to includes values from column A, rows 2:130
For i = 2 To 130 'Loop from row 2 to 130
'Check if the values in column C includes in the rng
If Application.WorksheetFunction.CountIf(rng, .Range("C" & i).Value) > 0 Then
.Range("D" & i).Value = "TRUE"
Else
.Range("D" & i).Value = "FALSE"
End If
Next i
End With
End Sub
VBA code to reconcile two lists.
Sub Reconciliation()
Dim endRow As Long
Dim ICount As Long
Dim Match1() As Variant
Dim Match2() As Variant
Dim ws As Worksheet
Set ws = Worksheets("Recon")
ICount = 0
endRow = ws.Cells(ws.Rows.Count, 2).End(xlUp).Row
endRow1 = ws.Cells(ws.Rows.Count, 11).End(xlUp).Row
Match1 = Sheet1.Range("b2:b" & endRow)
Match2 = Sheet1.Range("K2:K" & endRow1)
For i = LBound(Match1) To UBound(Match1)
For j = LBound(Match2) To UBound(Match2)
If Match1(i, 1) = Match2(j, 1) Then
ICount = ICount + 1
Sheet1.Range("C" & i + 1).Value = ICount
Sheet1.Range("L" & j + 1).Value = ICount
Else
End If
Next j
Next i
End Sub

Excel VBA: How to transform this kind of cells?

I am not sure if the title is correct. Please correct me if you have a better idea.
Here is my problem: Please see the picture.
This excel sheet contains only one column, let's say ColumnA. In ColumnA there are some cells repeat themselvs in the continued cells twice or three times (or even more).
I want to have the excel sheet transformed according to those repeated cells. For those items which repeat three times or more, keep only two of them.
[Shown in the right part of the picture. There are three Bs originally, target is just keep two Bs and delete the rest Bs.]
It's a very difficult task for me. To make it easier, it's no need to delete the empty rows after transformation.
Any kind of help will be highly appreciated. Thanks!
#
Update:
Please see the picture. Please dont delete the items if they show again...
EDITED - SEE BELOW Try this. Data is assumed to be in "Sheet1", and ordered data is written to "Results". I named your repeted data (A, B, C, etc) as sMarker, and values in between as sInsideTheMarker. If markers are not consecutive, the code will fail.
Private Sub ReOrderData()
Dim lLastRow As Long
Dim i As Integer
Dim a As Integer
Dim j As Integer
Dim sMarker As String
Dim sInsideTheMarker As String
'Get number of rows with data:
lLastRow = Worksheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row
j = 0
k = 1
a = 2
'Scan all rows with data:
For i = 1 To lLastRow
If (Worksheets("Sheet1").Cells(i + 1, 1).Value = Worksheets("Sheet1").Cells(i, 1).Value) Then 'If two consecutive cells holds the same value
j = j + 1
If j = 1 Then
k = k + 1
a = 2
sMarker = Worksheets("Sheet1").Cells(i, 1).Value
Worksheets("Results").Cells(k, 1).Value = sMarker
End If
Else 'If not same values in consecutive cells
sInsideTheMarker = Worksheets("Sheet1").Cells(i, 1).Value
Worksheets("Results").Cells(k, a).Value = sInsideTheMarker
a = a + 1
j = 0
End If
Next i
End Sub
EDITION: If you want results in the same sheet ("Sheet1"), and keep the empty rows for results to look exactly as your question, try the following
Private Sub ReOrderData()
Dim lLastRow As Long
Dim i As Integer
Dim a As Integer
Dim j As Integer
Dim sMarker As String
Dim sInsideTheMarker As String
'Get number of rows with data:
lLastRow = Worksheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row
j = 0
k = 1
a = 5
'Scan all rows with data:
For i = 1 To lLastRow
If (Worksheets("Sheet1").Cells(i + 1, 1).Value = Worksheets("Sheet1").Cells(i, 1).Value) Then 'If two consecutive cells holds the same value
j = j + 1
If j = 1 Then
k = i
a = 5
sMarker = Worksheets("Sheet1").Cells(i, 1).Value
Worksheets("Sheet1").Cells(k, 4).Value = sMarker
End If
Else 'If not same values in consecutive cells
sInsideTheMarker = Worksheets("Sheet1").Cells(i, 1).Value
Worksheets("Sheet1").Cells(k, a).Value = sInsideTheMarker
a = a + 1
j = 0
End If
Next i
End Sub
If you can delete the values that have more than two counts, then I suggest that this might work:
Sub count_macro()
Dim a As Integer
Dim b As Integer
a = 1
While Cells(a, 1) <> ""
b = WorksheetFunction.CountIf(Range("A1:A1000"), Cells(a, 1))
If b > 2 Then
Cells(a, 1).Delete Shift:=xlUp
End If
b = 0
a = a + 1
Wend
End Sub
This should do it. It takes input in column A starting in Row 2 until it ends, and ignores more than 2 same consecutive values. Then it copies them in sets and pastes them transposed. If your data is in a different column and row, change the sourceRange variable and the i variable accordingly.
Sub SETranspose()
Application.ScreenUpdating = False
Dim sourceRange As range
Dim copyRange As range
Dim myCell As range
Set sourceRange = range("A2", Cells(Rows.count, 1).End(xlUp))
Dim startCell As range
Set startCell = sourceRange(1, 1)
Dim i As Integer
Dim haveTwo As Boolean
haveTwo = True
For i = 3 To Cells(Rows.count, 1).End(xlUp).Row + 1
If Cells(i, 1).Value = startCell.Value Then
If haveTwo Then
range(startCell, Cells(i, 1)).Copy
startCell.Offset(0, 4).PasteSpecial Transpose:=True
Application.CutCopyMode = False
haveTwo = False
End If
End If
'if the letter changes or end of set, then copy the set over
'If LCase(Left(Cells(i, 1).Value, 1)) <> LCase(startCell.Value) Or _
'i = Cells(Rows.count, 1).End(xlUp).Row + 1 Then
If Len(Cells(i, 1).Value) > 1 Then
Set copyRange = Cells(i, 1)
copyRange.Copy
Cells(startCell.Row, Columns.count).End(xlToLeft).Offset(0, 1).PasteSpecial
Application.CutCopyMode = False
'Set startCell = sourceRange(i - 1, 1)
ElseIf Len(Cells(i, 1).Value) = 1 And Cells(i, 1).Value <> startCell.Value Then
Set startCell = sourceRange(i - 1, 1)
haveTwo = True
End If
Next i
'clear up data
Set sourceRange = Nothing
Set copyRange = Nothing
Set startCell = Nothing
Application.ScreenUpdating = True
End Sub

Architecture to grab range

My code mostly works but it's taking a while to debug so I am beginning to think my architecture may be flawed XD So how can I architect this better?
I have groups of data separated by a blank row. You can tell each group apart by the ID in column C in addition to the blank row. For each ID, I have various numbers in column B that I need to capture. Sometimes those numbers only start with 5, sometimes it starts with 7. I need to capture the 5 and the 7 separately.
With projWS
With .Range("C1:C6000")
Set f = .Find(cc, LookIn:=xlValues, lookat:=xlPart)
End With
If Not f Is Nothing Then 'first occurence found
counter = 0
i = f.Row
Do
acct = .Cells(i, 2)
If (Len(projWS.Cells(i, 3)) < 1 Or Left(acct, 1) = "7") And done = False Then
acctStart = f.Row
acctRows = i - acctStart
Set acctRng = .Range(.Cells(acctStart, 2), .Cells(i - 1, 5))
Set amountRng = .Range(.Cells(acctStart, 7), .Cells(i - 1, 8))
done = True 'set flag to show range has been filled
End If
counter = counter + 1 'increment counter
i = i + 1 'move to next row
Loop Until Len(.Cells(i, 3)) < 1 'keep looping until blank row
End If
If counter - 1 > acctRows Then 'how we determine if there's a "7"
flag = True 'so we set flag to true
Set depreRng = Range(.Cells(acctStart + acctRows, 2), .Cells(i - 1, 8))
dep = depreRng.Value2 'store range into array
End If
End With
After capture, I need to drop it into another worksheet. This worksheet already has a block of 7 built in. Hence this is the loop I am using to drop the range of 7. There is no built in block for the 5.
For r = 112 To 120
For k = 1 To UBound(dep())
If .Cells(r, 1).Value2 = Trim(dep(k, 1)) Then
Debug.Print .Cells(r, 1).Value2
.Cells(r, 6) = dep(k, 6)
.Cells(r, 7) = dep(k, 7)
Exit For
Else
.Cells(r, 6) = 0
.Cells(r, 7) = 0
End If
Next k
Next r
I have debugged several errors already. The current one is that depreRng is breaking because my math is bad. Instead of debugging each error as I stumble onto it, how can I architect this better?
Ok, my approach it's different. First i use a filter for find the range of rows with the index you are looking for and then loop inside this filtered rows for find the 5xx and the 7xx range. The code:
Sub Macro1()
Dim rng_5xx_start, rng_5xx_stop, rng_7xx_start, rng_7xx_stop As Integer
rng_5xx_start = 0
rng_5xx_stop = 0
rng_7xx_start = 0
rng_7xx_stop = 0
Dim range_5xx, range_7xx As String
'filter for the index you are looking for
'specify the maximum range, the field is the "offset" from the column B (the firts of the range), so for filter for column C you need to put 2, criteria...is the critera :)
ActiveSheet.Range("$B$1:$H$6000").AutoFilter Field:=2, Criteria1:="b"
'the filter returns only the rows with the specifyed index, now a for inside this rows for find the 5xx and the 7xx sub-ranges
For Each Row In ActiveSheet.Range("b1:b6000").SpecialCells(xlCellTypeVisible)
If Cells(Row.Row, 2).Value > 4999 And Cells(Row.Row, 2).Value < 6000 Then
'or any test for understnd if i'm in the 5xx range, if you prefer use the strings use something like left(cells(row.row,2).value,1) = "5"
If rng_5xx_start = 0 Then 'found the first row with a 5xx value
rng_5xx_start = Row.Row 'set the start of the range to this row
End If
If rng_5xx_stop < Row.Row Then 'the row where i am is in the 5xx range and is grater than the current end i noticed
rng_5xx_stop = Row.Row 'refresh the end of the range...at the end this will have the last number of row of the 5xx range
End If
End If
If Cells(Row.Row, 2).Value > 6999 And Cells(Row.Row, 2).Value < 8000 Then
'same as above but for 7xx range
If rng_7xx_start = 0 Then
rng_7xx_start = Row.Row
End If
If rng_7xx_stop < Row.Row Then
rng_7xx_stop = Row.Row
End If
End If
Next
If rng_5xx_start = 0 Then
'not found 5xx rows
range_5xx = "" 'or False, or what you prefer...
Else
range_5xx = "B" & rng_5xx_start & ":H" & rng_5xx_stop
End If
If rng_7xx_start = 0 Then
'not found 7xx rows
range_7xx = "" 'or False, or what you prefer...
Else
range_7xx = "B" & rng_7xx_start & ":H" & rng_7xx_stop
End If
End Sub
That's how i would imagine a macro for your job ;)
Edit 1:
I forgot that this will leave the sheet with the filter on...use activesheet.showalldata for show all the rows and not only the filtered ones
Edit 2:
The tests
If rng_5xx_stop < Row.Row Then
rng_5xx_stop = Row.Row
End If
and
If rng_7xx_stop < Row.Row Then
rng_7xx_stop = Row.Row
End If
are not necessary, it's enough do rng_5xx_stop = Row.Row and rng_7xx_stop = Row.Row and save the two IF statements
You are grouping cells based on the first number of the cell values in column B (I am assuming that they can never be letters). If that is the case, then you can create an array of 0 to 9 and store your ranges in there. Then go through the range.areas in order to get the groupings you're looking for (as highlighted in your screenshot).
To do this, something like this is all you need. I commented code to try to explain it more:
Sub tgr()
Dim wsData As Worksheet
Dim rColB As Range
Dim BCell As Range
Dim aRanges(0 To 9) As Range
Dim SubGroup As Range
Dim lRangeNum As Long
Dim i As Long
'Change to your actual worksheet
Set wsData = ActiveWorkbook.ActiveSheet
'Change to your actual column range, this is based off the sample data
Set rColB = wsData.Range("B1", wsData.Cells(wsData.Rows.Count, "B").End(xlUp))
'Loop through the column range
For Each BCell In rColB.Cells
'Make sure the cell is populated and the starting character is numeric
If Len(BCell.Value) > 0 And IsNumeric(Left(BCell.Value, 1)) Then
'Get the starting digit
lRangeNum = Val(Left(BCell.Value, 1))
'Check if any ranges have been assigned to that array index location
'If not, start a range at that array index
'If so, combine the ranges with Union
Select Case (aRanges(lRangeNum) Is Nothing)
Case True: Set aRanges(lRangeNum) = BCell
Case Else: Set aRanges(lRangeNum) = Union(aRanges(lRangeNum), BCell)
End Select
End If
Next BCell
'You can use any method you want to access the ranges, this just loops
'through the array indices and displays the range areas of each
For i = 0 To 9
If Not aRanges(i) Is Nothing Then
For Each SubGroup In aRanges(i).Areas
'Do what you want with it here
'This just selects the subgroup so you can see it found the groups properly
SubGroup.Select
MsgBox SubGroup.Address
Next SubGroup
End If
Next i
End Sub
I see you've allready rewritten your code, but I'd like to offer how I would do it and would like to know your thoughts about it. Would this be inefficient? I guess it could be because you have to read the first character in cells 4 times for every increment, but not shure if that is a big problem.
Dim start_row As Long
Dim end_row As Long
start_row = 1
end_row = 0
For i = 2 To Range("B" & Rows.Count).End(xlUp).Row
If Cells(i - 1, 2) = "" Then
start_row = i
ElseIf Left(Cells(i - 1, 2), 1) <> Left(Cells(i, 2), 1) Then
start_row = i
End If
If Cells(i + 1, 2) = "" Then
end_row = i
ElseIf Left(Cells(i + 1, 2), 1) <> Left(Cells(i, 2), 1) Then
end_row = i
End If
If end_row <> 0 Then
Call copy_range(start_row, end_row)
end_row = 0
End If
Next i
Another approach that lets you only read the character once could be
Dim start_row As Long
Dim end_row As Long
Dim char_above As String
Dim this_char As String
start_row = 1
end_row = 1
For i = 1 To Range("B" & Rows.Count).End(xlUp).Row
If Cells(i, 2) = "" Then
end_row = i - 1
if i <>1 then Call copy_range(start_row, end_row,char_above)
start_row = i + 1
Else
this_char = Left(Cells(i, 2), 1)
If this_char <> char_above Then
end_row = i - 1
if i<> 1 then Call copy_range(start_row, end_row,char_above)
start_row = i
End If
char_above = this_char
End If
Next i
Let me know your thoughts.

Resources