how to suppress efficiently all empty rows in selected columns in excel vba? - excel

I have found some Q/A to delete rows with empty cells in a chosen column like here. My need is a bit different, the columns are selected by the user, but this is not important.
EDIT : what is important in my use case is to delete the rows where all the cells are empty for these columns i.e. the selected columns.
The following code is working, but can only process 1,000 lines per minute on my i5. In my use case, the datasheet contains several 100k lines which means hours to process. This is not acceptable. Is there a trick to perfom it quickly please?
Sub DeleteRowsOfEmptyColumn() 'sh As Worksheet, col As String)
Application.ScreenUpdating = False
Dim sh As Excel.Worksheet: Set sh = ActiveWorkbook.ActiveSheet
Dim col As Range: Set col = Selection.EntireColumn
Dim cell
Dim area As Range: Set area = Intersect(sh.UsedRange, col)
For i = area.Rows.Count To 1 Step -1 'For Each row In area.Rows
fKeep = False
For Each cell In area.Rows(i).Cells
If Not IsEmpty(cell) Then
fKeep = True
Exit For
End If
Next cell
If Not fKeep Then
sh.Rows(i).Delete 'rowsToDelete.Add i
End If
Next i
Application.ScreenUpdating = True
End Sub
Example:
Before:
After:

Delete Empty Row Ranges
This is a basic example. Your feedback regarding the efficiency is appreciated.
Option Explicit
Sub DeleteRowsOfEmptyColumn()
Application.ScreenUpdating = False
Dim ws As Worksheet: Set ws = ActiveSheet ' improve
Dim crg As Range: Set crg = Selection.EntireColumn ' Columns Range
Dim srg As Range: Set srg = Intersect(ws.UsedRange, crg) ' Source Range
Dim drg As Range ' Delete Range
Dim arg As Range ' Area Range
Dim rrg As Range ' Row Range
For Each arg In srg.Areas
For Each rrg In arg.Rows
If Application.CountA(rrg) = 0 Then
If drg Is Nothing Then
Set drg = rrg
Else
Set drg = Union(drg, rrg)
End If
End If
Next rrg
Next arg
If Not drg Is Nothing Then drg.Delete
Application.ScreenUpdating = True
MsgBox "Rows deleted.", vbInformation
End Sub

Please, try the next way. It will process selected columns or columns having at least a selected cell. It will delete entire rows of the sheet, for the cases of all selected columns empty rows. The code only selects the rows in discussion. If they are the appropriate ones, on the last code line, Select should be replaced with Delete. It should be very fast, even for larger ranges, iterating only between blank cells range:
Sub DeleteRowsOfEmptyColumnsCells()
Dim sh As Excel.Worksheet: Set sh = ActiveSheet
Dim col As Range: Set col = Selection.EntireColumn
Dim area As Range: Set area = Intersect(sh.UsedRange, col)
Dim firstCol As Long: firstCol = area.Column: Stop
Dim areaV As Range, arr, rngDel As Range, i As Long
On Error Resume Next 'only for the case of no any empty rows existence
Set areaV = area.SpecialCells(xlCellTypeBlanks) 'a range of only empty cells
On Error GoTo 0
arr = getRows(areaV) 'extract all rows and number of columns
For i = 0 To UBound(arr(0)) 'iterate between all existing rows
If Intersect(sh.rows(arr(0)(i)), areaV).cells.count = arr(1) Then
If rngDel Is Nothing Then
Set rngDel = sh.cells(arr(0)(i), firstCol)
Else
Set rngDel = Union(rngDel, sh.cells(arr(0)(i), firstCol))
End If
End If
Next i
If Not rngDel Is Nothing Then rngDel.EntireRow.Select 'if it looks OK, Select should be replaced with Delete
End Sub
Function getRows(rng As Range) As Variant
Dim A As Range, i As Long, countC As Long
Dim arrCol, arrR, k As Long, R As Long, mtchC, mtchR
ReDim arrCol(rng.cells.count): ReDim arrR(rng.cells.count)
For Each A In rng.Areas
For i = 1 To A.Columns.count
For j = 1 To A.rows.count
mtchC = Application.match(A.cells(j, i).Column, arrCol, 0)
mtchR = Application.match(A.cells(j, i).row, arrR, 0)
If IsError(mtchC) Then
arrCol(k) = A.cells(j, i).Column: k = k + 1
End If
If IsError(mtchR) Then
arrR(R) = A.cells(j, i).row: R = R + 1
End If
Next j
Next i
Next A
ReDim Preserve arrR(R - 1)
getRows = Array(arrR, k)
End Function

I am working on similar kind of project. I have chosen to read the data into an array, and then work with the data in the array which improves run time significantly. Here is a copy of the function that I have used to delete / transform the data set:
Option Explicit
Option Base 1
Public Function RemoveRowFromArray(Arr As Variant, Element As String, Col As Long) As Variant
Dim i, j, c, count As Long
Dim TempArr() As Variant
For i = LBound(Arr, 1) To UBound(Arr, 1) ' looping through the columns to get desired value
If Arr(i, Col) = Element Then
count = count + 1 ' Counting the number of Elements in array / matrix
For j = i To (UBound(Arr, 1) - 1) ' Looping from the row where Element is found
For c = LBound(Arr, 2) To UBound(Arr, 2) ' Moving all elements in row 1 row up
Arr(j, c) = Arr(j + 1, c)
Next c
Next j
End If
Next i
' Populating TempArr to delete the last rows
ReDim TempArr((UBound(Arr, 1) - count), UBound(Arr, 2))
For i = LBound(TempArr, 1) To UBound(TempArr, 1)
For j = LBound(TempArr, 2) To UBound(TempArr, 2)
TempArr(i, j) = Arr(i, j)
Next j
Next i
RemoveRowFromArray = TempArr
End Function
I tested this and seems to work perfectly. A few important matters to keep in mind
Option Base 1 - This is important, when you declare an arr in VBA it starts at Index 0, when you read the arr from a data set in Excel [arr = sheet1.Range("A:D")] then the arr starting index is 1, Option Base 1 will ensure that all arr start at Index 1.
The function variables are :
Arr - the array / matrix
Element - the string that you wish to search for (in your case it would be blank)
Col - is the column number in which Element is.

Related

Filter "#N/A# rows to eliminate them in a short period of time

I am working with an excel which has about 500000 rows.
I have one sheet called "B" where is all the info and I only need the rows where the column Y contains text, not de #N/A from the LOOKUP.
I have to copy the rows with info, to another sheet called "A".
I used this code for the same process
On Error Resume Next
Columns("Y").SpecialCells(xlFormulas, xlErrors).EntireRow.Delete
On Error GoTo 0
But in this case, there are many rows so it takes 5 minutes(not worthy)
I only have 3000 rows with non NA, so I thought it will be easier to filter them and copy to "A" the entire row(the column A from the row in "B" it's not necessary, and the destination sheet "A" the column A has to be empty).
I don't know how to do it, i'm new in this language, thank you
Sheet B; the column Y with the header SKU contains the not found and the found ones ex:SKU1233444
Sheet A;
I have to copy from B except headers and column A, all the rows with SKU found and paste them into Sheet A leaving its headers and the column A empty because it's formulated
Arrays work faster than deleting rows one by one in VBA
Arrays need to be transposed / flipped before they're pasted into a worksheet
I ran the code below and it works.
I assumed that we're only working from column B as your attached photo above seems to suggest
Option Explicit ensures that we declare all variables we use.
$ is short hand for string; % for integer; & for long
Option Explicit
Private Sub Test()
Dim sChar$, sRange$, sRange2$
Dim iCol%, iLastUsedCol%
Dim iLastUsedRow&, iRow&
Dim r As Range
Dim aCleaned As Variant, aData As Variant
Dim WS As Worksheet, WS2 As Worksheet
Set WS = ThisWorkbook.Sheets("A")
Set WS2 = ThisWorkbook.Sheets("B")
With WS
'furthest column to right on a worksheet
sChar = ColumnChars2(Columns.Count)
'last used header column on this sheet
iLastUsedCol = .Range(sChar & 1).End(xlToLeft).Column
'last used row of data on this sheet
iLastUsedRow = .Range("A" & Rows.Count - 1).End(xlUp).Row
'cells containing data
sRange = "B2:" & ColumnChars2(iLastUsedCol) & iLastUsedRow
'transferring data to array
aData = .Range(sRange)
End With
'temporary store for row of data
ReDim aParam(iLastUsedCol - 2)
'cleaned data
ReDim aCleaned(iLastUsedCol - 2, 0)
'setting first entry of cleaned data to blank initially - needed for AddEntry subroutine called below
aCleaned(0, 0) = ""
For iRow = 1 To UBound(aData)
'if Y column cell for this row does not contain error
If Not IsError(aData(iRow, 24)) Then
'save entire row temporarily
For iCol = 0 To UBound(aParam)
aParam(iCol) = aData(iRow, iCol + 1)
Next
'transfer saved row to cleaned data array
Call AddEntry(aCleaned, aParam)
End If
Next
With WS2
iLastUsedCol = .Range(sChar & 1).End(xlToLeft).Column
iLastUsedRow = .Range("B" & Rows.Count - 1).End(xlUp).Row
'if data in B sheet
If iLastUsedRow > 1 Then
sRange2 = "B2:" & ColumnChars2(iLastUsedCol) & iLastUsedRow
'empty
.Range(sRange2).ClearContents
End If
Set r = .Range("B2")
'copy cleaned data to sheet B
r.Resize(UBound(aCleaned, 2) + 1, UBound(aCleaned, 1) + 1).Value = my_2D_Transpose(aCleaned)
End With
End Sub
The first subroutine called by the test routine above:
Public Function ColumnChars2(iCol As Variant) As String
On Error GoTo Err_Handler
'
' calculates character form of column number
'
Dim iPrePrefix As Integer, iPrefix As Integer, iSuffix As Integer
iSuffix = iCol
iPrefix = 0
Do Until iSuffix < 27
iSuffix = iSuffix - 26
iPrefix = iPrefix + 1
Loop
iPrePrefix = 0
Do Until iPrefix < 27
iPrefix = iPrefix - 26
iPrePrefix = iPrePrefix + 1
Loop
ColumnChars2 = IIf(iPrePrefix = 0, "", Chr(64 + iPrePrefix)) & IIf(iPrefix = 0, "", Chr(64 + iPrefix)) & Chr(64 + iSuffix)
Exit Function
Exit_Label:
On Error Resume Next
Application.Cursor = xlDefault
Application.ScreenUpdating = True
Application.CutCopyMode = False
Application.Calculation = xlCalculationAutomatic
Exit Function
Err_Handler:
MsgBox Err.Description, vbCritical, "ColumnChars2"
Resume Exit_Label
End Function
The second subroutine called by the test routine above:
Public Sub AddEntry(aList As Variant, aEntry As Variant)
'
' build array for later copy onto sheet
'
Dim i%
Dim aEntry2 As Variant
If VarType(aEntry) = vbString Then
aEntry2 = Array(aEntry)
Else
aEntry2 = aEntry
End If
If aList(0, 0) <> "" Then
ReDim Preserve aList(0 To UBound(aEntry2), 0 To UBound(aList, 2) + 1)
End If
For i = 0 To UBound(aEntry2)
aList(i, UBound(aList, 2)) = aEntry2(i)
Next
End Sub
The third subroutine called by the test routine above:
Function my_2D_Transpose(arr As Variant)
On Error GoTo Err_Handler
'works better than delivered Application.Transpose function
Dim a&, b&, tmp As Variant
ReDim tmp(LBound(arr, 2) To UBound(arr, 2), LBound(arr, 1) To UBound(arr, 1))
For a = LBound(arr, 1) To UBound(arr, 1)
For b = LBound(arr, 2) To UBound(arr, 2)
tmp(b, a) = arr(a, b)
Next b
Next a
my_2D_Transpose = tmp
Exit Function
Exit_Label:
On Error Resume Next
Application.Cursor = xlDefault
Application.ScreenUpdating = True
Application.CutCopyMode = False
Application.Calculation = xlCalculationAutomatic
Exit Function
Err_Handler:
MsgBox Err.Description, vbCritical, "my_2D_Transpose"
Resume Exit_Label
End Function
Copy Criteria Rows
Option Explicit
Sub CopyNoErrors()
' Define constants.
' Source
Const sName As String = "B"
Const CritColumnString As String = "Y"
' Destination
Const dName As String = "A"
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the source worksheet ('sws').
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range
Dim rCount As Long
Dim cCount As Long
' Reference the source range ('srg') excluding the first column
' and the headers.
With sws.Range("A1").CurrentRegion
rCount = .Rows.Count - 1
cCount = .Columns.Count - 1
Set srg = .Resize(rCount, cCount).Offset(1, 1)
End With
' Determine the criteria column ('CritColumn') which has to be reduced
' by one due to the shifting of the source range
' which is starting in column 'B'.
Dim CritColumn As Long
CritColumn = sws.Columns(CritColumnString).Column - 1
' Write the values from the source range to a 2D one-based array ('Data').
Dim Data() As Variant: Data = srg.Value
Dim sr As Long, sc As Long, dr As Long
' Write the rows, not containing the error value in the criteria column,
' to the top of the array.
For sr = 1 To rCount
If Not IsError(Data(sr, CritColumn)) Then
dr = dr + 1
For sc = 1 To cCount
Data(dr, sc) = Data(sr, sc)
Next sc
End If
Next sr
' Reference the destination worksheet ('dws').
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
' Reference the destination range ('drg'), a range with the same address
' as the source range.
Dim drg As Range: Set drg = dws.Range(srg.Address)
With drg
' Write the values from the top of the array to the destination range.
.Resize(dr).Value = Data
' Clear below.
.Resize(dws.Rows.Count - .Row - dr + 1).Offset(dr).ClearContents
End With
' Inform.
MsgBox "Data copied.", vbInformation
End Sub

Compare 2 cells in 2 different ranges if they match delete whole row

I am trying to delete rows where the same value occurs in both columns C and D on the same row
I am comparing between column C(BOS address 1) and D (Empower address 1)so if they have the same string remove the whole row. The code is below it executes normally but give Object 424 error after it runs.
Sub test()
Dim try As String
Dim lastrow As Long
Dim x As Variant
Dim row_count As Long
Dim lastrow_str As String
Dim lastrow_rng As String
With empower_report
' Get count of records to search through (list that will be deleted)'
lastrow_str = getColStr("Empower Address 1")
lastrow = Cells(Rows.Count, lastrow_str).End(xlUp).Row
lastrow_rng = getColRange("BOS Address 1")
' Loop through the "master" list'
For Each x In Range(lastrow_rng)
' Loop through all records in the second list.
For row_count = lastrow To 1 Step -1
' Do comparison of next record'
If x.Value = Cells(row_count, 4).Value Then
' If match is true then delete row.
Cells(row_count, 4).EntireRow.Delete
End If
Next row_count
Next
End With
End Sub
The error message ( 424 object required) because of this line. once I press end the code will run.
If x.Value = Cells(row_count, 4).Value Then
Example : input: Column C D
denver denver
denver boston
Output: Column C D
denver boston
I don't have a lot of VBA experience yet. Thank you
since you are trying to delete rows where the same value occurs in both columns C and D on the same row, you only need one loop.
Sub Demo()
Dim FirstDataColumn As Range
Dim SecondDataColumn As Range
Dim i As Long
With empower_report
' get reference to column data by any means you choose
Set FirstDataColumn = .Range( ... )
Set SecondDataColumn = .Range( ... )
If FirstDataColumn.Row <> SecondDataColumn.Row Then
' ranges are not aligned
Exit Sub
End If
If FirstDataColumn.Rows.Count <> SecondDataColumn.Rows.Count Then
' ranges are not the same size
Exit Sub
End If
' Loop the array
For i = FirstDataColumn.Rows.Count To 1 Step -1
' Detect if items on same row are equal
If FirstDataColumn.Cells(i, 1) = SecondDataColumn.Cells(i, 1) Then
' Delete row
FirstDataColumn.Rows(i).EntireRow.Delete
End If
Next
End With
End Sub
Note that this will be slower than it can be because:
Loop ranges is slow
Deleting rows one at a time is slow
If your data sets are small enough this may not be noticable.
On the other hand, if it's too slow for you, then you could
Move the data to a variant array and loop that
Collect a reference to rows to be deleted as you loop, then delete all rows in one go at the end
Remove Duplicates (Loop Column Range)
This is kind of a range study i.e. it surely can be done to be more elegant and more efficient e.g. if you could determine the whole range first, you could use RemoveDuplicates when possible data to the left and/or to the right would not be affected.
Option Explicit
Sub RemoveDupesLoop()
Const Title1 As String = "Empower Address 1"
Const Title2 As String = "BOS Address 1"
Const tRow As Long = 1 ' Title (Header) Row
Dim rg1 As Range
Dim Col2 As Long
With empower_report
Dim cIndex As Variant
cIndex = Application.Match(Title1, .Rows(tRow), 0)
If IsError(cIndex) Then Exit Sub
Dim Col1 As Long: Col1 = cIndex
cIndex = Application.Match(Title2, .Rows(tRow), 0)
If IsError(cIndex) Then Exit Sub
Col2 = cIndex
Dim lRow1 As Long: lRow1 = .Cells(.Rows.Count, Col1).End(xlUp).Row
If lRow1 <= tRow Then Exit Sub
Dim lRow2 As Long: lRow2 = .Cells(.Rows.Count, Col2).End(xlUp).Row
If lRow2 <= tRow Then Exit Sub
Dim lRow As Long
If lRow1 < lRow2 Then
lRow = lRow1
Else
lRow = lRow2
End If
Set rg1 = .Range(.Cells(tRow + 1, Col1), .Cells(lRow, Col1))
End With
Dim drg As Range
Dim cCell As Range
For Each cCell In rg1.Cells
If cCell.Value = cCell.EntireRow.Cells(Col2).Value Then
If drg Is Nothing Then
Set drg = cCell
Else
Set drg = Union(drg, cCell)
End If
End If
Next cCell
If Not drg Is Nothing Then
drg.EntireRow.Delete
End If
End Sub

Remove rows from a 2d array if value in column is empty

I have a large table of lab measurement logs, which I work with using arrays.
(Im a chemist, a lab technician and Ive started to learn VBA only last week, please bear with me.)
Im trying to figure out, how to load the table into an array and then remove rows with an empty value in the 5th column so that I can "export" the table without blanks in the 5th column via an array into a different sheet.
I first tested this with some code I found for a 1D array, where I would make 2 arrays, one placeholder array which Id loop through adding only non-blanks to a second array.
For Counter = LBound(TestArr) To UBound(TestArr)
If TestArr(Counter, 1) <> "" Then
NoBlankSize = NoBlankSize + 1
NoBlanksArr(UBound(NoBlanksArr)) = TestArr(Counter, 1)
ReDim Preserve NoBlanksArr(0 To UBound(NoBlanksArr) + 1)
End If
Next Counter
It works in 1D, but I cant seem to get it two work with 2 dimensions.
Heres the array Im using for reading and outputting the data
Sub ArrayTest()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim TestArray() As Variant
Dim Dimension1 As Long, Dimension2 As Long
Sheets("Tracker").Activate
Dimension1 = Range("A3", Range("A2").End(xlDown)).Cells.Count - 1
Dimension2 = Range("A2", Range("A2").End(xlToRight)).Cells.Count - 1
ReDim TestArray(0 To Dimension1, 0 To Dimension2)
'load into array
For Dimension1 = LBound(TestArray, 1) To UBound(TestArray, 1)
For Dimension2 = LBound(TestArray, 2) To UBound(TestArray, 2)
TestArray(Dimension1, Dimension2) = Range("A4").Offset(Dimension1, Dimension2).Value
Next Dimension2
Next Dimension1
Sheets("Output").Activate
ActiveSheet.Range("A2").Select
'read from array
For Dimension1 = LBound(TestArray, 1) To UBound(TestArray, 1)
For Dimension2 = LBound(TestArray, 2) To UBound(TestArray, 2)
ActiveCell.Offset(Dimension1, Dimension2).Value = TestArray(Dimension1, Dimension2)
Next Dimension2
Next Dimension1
Erase TestArray
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Thank you for any help in advance.
The Redim Preserve statement does not work for two-dimensional arrays if you want to change the number of records (rows).
You could load the range into an array, and then when you want to export the array to another range, loop through that array while skipping blank records.
An example:
Option Explicit
Sub ArrayTest()
Dim wb As Workbook, wsInput As Worksheet, wsOutput As Worksheet
Dim myArr As Variant
Dim i As Long, k As Long, LRow As Long
Set wb = ThisWorkbook
Set wsInput = wb.Sheets("Tracker")
Set wsOutput = wb.Sheets("Output")
LRow = wsOutput.Cells(wsOutput.Rows.Count, "A").End(xlUp).Row + 1
'Load a range into the array (example range)
myArr = wsInput.Range("A1:Z100")
'Fill another range with the array
For i = LBound(myArr) To UBound(myArr)
'Check if the first field of the current record is empty
If Not Len(myArr(i, 1)) = 0 Then
'Loop through the record and fill the row
For k = LBound(myArr, 2) To UBound(myArr, 2)
wsOutput.Cells(LRow, k) = myArr(i, k)
Next k
LRow = LRow + 1
End If
Next i
End Sub
From your code, it appears you want to
test a column of data on a worksheet to see if there are blanks.
if there are blanks in the particular column, exclude that row
copy the data with the excluded rows to a new area
You can probably do that easier (and quicker) with a filter: code below checking for blanks in column2
Option Explicit
Sub removeCol2BlankRows()
Dim wsSrc As Worksheet, wsRes As Worksheet
Dim rSrc As Range, rRes As Range
Set wsSrc = ThisWorkbook.Worksheets("sheet1")
Set rSrc = wsSrc.Cells(1, 1).CurrentRegion 'many ways to do this
Set wsRes = ThisWorkbook.Worksheets("sheet1")
Set rRes = wsRes.Cells(1, 10)
If wsSrc.AutoFilterMode = True Then wsSrc.AutoFilterMode = False
rSrc.AutoFilter field:=2, Criteria1:="<>"
rSrc.SpecialCells(xlCellTypeVisible).Copy rRes
wsRes.AutoFilterMode = False
End Sub
If you really just want to filter the VBA arrays in code, I'd store the non-blank rows in a dictionary, and then write it back to the new array:
Option Explicit
Sub removeCol2BlankRows()
Dim testArr As Variant
Dim noBlanksArr As Variant
Dim myDict As Object
Dim I As Long, J As Long, V
Dim rwData(1 To 4) As Variant
With ThisWorkbook.Worksheets("sheet1")
testArr = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=4)
End With
Set myDict = CreateObject("Scripting.Dictionary")
For I = 1 To UBound(testArr, 1)
If testArr(I, 2) <> "" Then
For J = 1 To UBound(testArr, 2)
rwData(J) = testArr(I, J)
Next J
myDict.Add Key:=I, Item:=rwData
End If
Next I
ReDim noBlanksArr(1 To myDict.Count, 1 To 4)
I = 0
For Each V In myDict.keys
I = I + 1
For J = 1 To 4
noBlanksArr(I, J) = myDict(V)(J)
Next J
Next V
End Sub

Duplicating cells with address in excel

I am having 10 columns from B to L in excel. I want to check for duplicates within this Range. But I want to know which cell is duplicating with another cell(need a reference of parent one). Please help me to arrive the solution. Here is the code which i tried to solve by getting the "comment with cell address". It is incomplete.
Please suggest best way for this problem.
Thanks in advance.
here is my code
Sub bomstruct()
Dim i As Long
Dim j As Long
Dim f As Long
Dim k As Integer
Dim w As Integer
Range("A3").Select
f = Range(Selection, Selection.End(xlDown)).Rows.Count
Dim Cval As Variant
For k = 3 To f
Cells(k, j).Activate
Cval = Cells(k, j).Value
Cadd = Cells(k, j).Address
If Cval = "" Then
Else
For j = 2 To 12
Cells(i, j).Select
g = f + 3
For i = 790 To g
If i = g Then
Cells(i - g + 3, j + 1).Select
Else
Cells(i, j).Select
If ActiveCell.Value = Cval Then
ActiveCell.Interior.ColorIndex = 6
ActiveCell.AddComment (Cadd)
End If
End If
Next i
i = i - g + 3
Next j
End If
Next k
End Sub
Following code checks for all duplicates and marks (comment and color) the duplicates. It ignores empty cells:
Sub callIt()
Dim rng As Range
' Set the range to check
With ActiveSheet
Set rng = .Range(.Range("A3"), .Range("A3").End(xlDown)).Offset(0, 1).Resize(, 11)
End With
' ===== MAYBE NEEDED ==================================
' Remove color
rng.Interior.colorIndex = 0
' Remove comment if there is one
rng.ClearComments
' ======================================================
' Call the function with the range set
colorizeAndCommentDuplicates rng
End Sub
' Colorize duplicates (same .value) in a range and add comment showing the addresses
' of all duplicates found. Ignores empty cells.
' Args:
' rng (Range): Range to check for duplicates
Sub colorizeAndCommentDuplicates(rng As Range)
Dim rngValuesArray As Variant
Dim i As Long, j As Long
Dim currentValue As Variant
Dim dict As Object, dictDuplicates As Object, rngDuplicates As Range
' Create dict to store ranges
Set dict = CreateObject("Scripting.Dictionary")
Set dictDuplicates = CreateObject("Scripting.Dictionary")
' Write range values into array
rngValuesArray = rng.value
' Loop through range array and find duplicates
For i = LBound(rngValuesArray, 1) To UBound(rngValuesArray, 1)
For j = LBound(rngValuesArray, 2) To UBound(rngValuesArray, 2)
currentValue = rngValuesArray(i, j)
' Skip empty cells
If currentValue <> vbNullString Then
' Only check for duplicates of value if we not already have
If Not dict.exists(currentValue) Then
dict(currentValue) = True
Set rngDuplicates = getDuplicatesRanges(currentValue, rngValuesArray, rng(1))
' Check if duplicates found
If Not rngDuplicates Is Nothing Then
' Add ranges of duplicates to dict
Set dictDuplicates(currentValue) = rngDuplicates
End If
End If
End If
Next
Next
' colorize and add comments
markDuplicates dictDuplicates
End Sub
' Check for duplicates in range values array and return range with duplicates
' if duplicates exist or nothing if there are no duplicates.
' Args:
' valuetoCheck (Variant): Look for duplicates of value.
' rngValuesArray (Variant): Array holding values of a range
' to look for duplicates of value in.
' rngTopLeft (Range): First (top left) range of range to look
' for duplicates in.
' Returns:
' (Range) Nothing if no duplicate found else Range (Areas) of
' duplicates found.
Function getDuplicatesRanges(ByVal valueToCheck As Variant, _
ByVal valuesArray As Variant, ByVal rngTopLeft As Range) As Range
Dim rng As Range, rngTemp As Range
Dim arrayDuplicates() As String
Dim i As Long
Dim j As Long
Dim dictDuplicates
ReDim arrayDuplicates(0)
For i = LBound(valuesArray, 1) To UBound(valuesArray, 1)
For j = LBound(valuesArray, 2) To UBound(valuesArray, 2)
' Value found
If valueToCheck = valuesArray(i, j) Then
If arrayDuplicates(0) <> "" Then
ReDim Preserve arrayDuplicates(UBound(arrayDuplicates) + 1)
End If
arrayDuplicates(UBound(arrayDuplicates)) = i & "," & j
End If
Next
Next
' Loop through array with indexes of duplicates if any found
' and convert to range
If UBound(arrayDuplicates) > 0 Then
For i = 0 To UBound(arrayDuplicates)
Set rngTemp = rngTopLeft.Offset( _
Split(arrayDuplicates(i), ",")(0) - 1, _
Split(arrayDuplicates(i), ",")(1) - 1)
If rng Is Nothing Then
Set rng = rngTemp
Else
Set rng = Application.Union(rng, rngTemp)
End If
Next
Set getDuplicatesRanges = rng
End If
End Function
' Colorize and add comment to duplicates
' Args:
' dict (Object): Scripting dictionary holding values that have
' duplicates as key and all ranges of the duplictaes as values.
Sub markDuplicates(ByRef dict As Object)
Dim key As Variant
Dim rngDict As Range
Dim rng As Range
Dim addresses As String
' Loop through duplicates
For Each key In dict.keys
Set rngDict = dict(key)
' Create string with addresses
For Each rng In rngDict
If addresses <> vbNullString Then addresses = addresses & vbCrLf
addresses = addresses & rng.Address
Next
' Colorize and add comment
For Each rng In rngDict
rng.Interior.colorIndex = 6
rng.ClearComments
rng.AddComment addresses
Next
addresses = vbNullString
Next
End Sub
Highlighting the cells that are duplicate with a conditional formatting rule is one method of 'any other ways to identify'.
with worksheets("sheet1")
with .range("B:L")
With .FormatConditions
.Delete
.Add Type:=xlExpression, Formula1:="=COUNTIF($B:$L, B1)>1"
End With
With .FormatConditions(.FormatConditions.Count)
.Interior.Color = vbRed
End With
end with
end with
Here is a macro that will add a comment to each cell listing the addresses of all the duplicates.
Read the notes in the code.
I use a dictionary to detect the duplicates, and each item in the dictionary is a collection of cell addresses where those duplicates can be found.
As written it is "sorted by rows", but you can easily change the looping to sort by columns if you prefer.
The cell with the comment is excluded from the list of duplicates.
Option Explicit
Sub foo()
Dim d1 As Object, col As Collection
Dim v As Variant, w As Variant
Dim i As Long, j As Long
Dim S As String, sComment As String
Dim R As Range, C As Range
Set d1 = CreateObject("Scripting.Dictionary")
d1.CompareMode = TextCompare
'many ways to set bounds of the region to be processed
With Cells(2, 2).CurrentRegion
.ClearComments
v = .Value2 'read values into array for faster processing
End With
'collect the addresses of each value
For i = 1 To UBound(v, 1)
For j = 1 To UBound(v, 2)
If Not d1.exists(v(i, j)) Then
Set col = New Collection
'offset from array index to cell address depends on starting point of array
col.Add Cells(i + 1, j + 1).Address
d1.Add Key:=v(i, j), Item:=col
Else
d1(v(i, j)).Add Cells(i + 1, j + 1).Address
End If
Next j
Next i
'Add the comments
Cells(2, 2).CurrentRegion.ClearComments
For Each v In d1
If d1(v).Count > 1 Then
sComment = ""
S = d1(v)(1)
Set R = Range(S)
For i = 1 To d1(v).Count
S = d1(v)(i)
Set R = Union(R, Range(S))
sComment = sComment & "," & Range(S).Address
Next i
For Each C In R
'Exclude current cell from list of duplicates
S = Mid(Replace(sComment, "," & C.Address, ""), 2)
C.AddComment "Duplicates in" & vbLf & S
Next C
End If
Next v
End Sub

Iterate over all rows and find the empty ones of active sheet VBA

I have an Excel-worksheet with different "sections" separated by an empty row. What I want to do is to simple get the row numbers to work with them. Sadly the code is not executing the For-Loop (No failure, just not entering it) but the rowNumber variable is set properly. Did I miss something on the For-Loop?
Sub Foo()
Dim currentSheet As Worksheet
Set currentSheet = activeSheet
emptyRows = FindAllEmptyRows(currentSheet)
End Sub
Function FindAllEmptyRows(sheet As Worksheet) As Variant
Dim emptyRows() As Variant
Dim i As Long, rowNumber As Long
Dim rowCounter As Integer
rowCounter = 1
rowNumber = sheet.UsedRange.Rows.Count
For i = rowNumber To 1
If Cells(i, 1).End(xlToRight).Column = 16384 And Cells(i, 1) = "" Then
emptyRows(rowCounter) = i
rowCounter = rowCounter + 1
End If
Next
FindAllEmptyRows = emptyRows
End Function
If you want to iterate from last row to first you will need to add Step -1.
emptyRows() needs to be sized to fit the data using ReDim
.Column = 16384 should be changed to .Column = sheet.Columns.Count.
I prefer If WorksheetFunction.CountA(sheet.Rows(i)) = 0 Then
Cells needs to be qualified to sheet: sheet.Cells(i, 1)
Refactored Code
Function FindAllEmptyRows(sheet As Worksheet) As Variant
Dim emptyRows() As Variant
Dim i As Long, rowNumber As Long
Dim rowCounter As Integer
rowNumber = sheet.UsedRange.Rows.Count
For i = rowNumber To 1 Step -1
If sheet.Cells(i, 1).End(xlToRight).Column = sheet.Columns.Count And Cells(i, 1) = "" Then
If rowCounter = 0 Then
ReDim emptyRows(0)
Else
ReDim Preserve emptyRows(rowCounter)
End If
emptyRows(rowCounter) = i
rowCounter = rowCounter + 1
End If
Next
FindAllEmptyRows = emptyRows
End Function
SpecialCells
Range.SpecialCells() can be used to divide a Range into areas of cells that meet certain criteria.
MSDN - Range.SpecialCells Method (Excel)
Returns a Range object that represents all the cells that match the specified type and value
OZ Grid
One of the most beneficial Methods in Excel (in my experience) is the SpecialCells Method. When used, it returns a Range Object that represents only those type of cells we specify. For example, one can use the SpecialCells Method to return a Range Object that only contains formulae. In fact, we can, if we wish, even narrow it down further to have our Range Object (containing only formulae) to return only formulae with errors.
Examining the output of this code should give you a good ideas of how to use SpecialCells.
Sub SpecialFoo()
Dim rArea As Range, rBlanks As Range, rFormulas As Range, rConstants As Range, rUnion As Range
Dim sheet As Worksheet
Set sheet = ActiveSheet
On Error Resume Next
Set rBlanks = sheet.UsedRange.SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
If Not rBlanks Is Nothing Then
For Each rArea In rBlanks.Areas
Debug.Print "rBlanks Areas: "; rArea.Address
Next
End If
On Error Resume Next
Set rFormulas = sheet.UsedRange.SpecialCells(xlCellTypeFormulas)
On Error GoTo 0
If Not rFormulas Is Nothing Then
For Each rArea In rFormulas.Areas
Debug.Print "rFormulas Areas: "; rArea.Address
Next
End If
On Error Resume Next
Set rConstants = sheet.UsedRange.SpecialCells(xlCellTypeConstants)
On Error GoTo 0
If Not rConstants Is Nothing Then
For Each rArea In rConstants.Areas
Debug.Print "rConstants Areas: "; rArea.Address
Next
End If
If Not rFormulas Is Nothing And Not rConstants Is Nothing Then
Set rFormulas = Union(rConstants, rFormulas)
For Each rArea In rFormulas.Areas
Debug.Print "rUnion Areas: "; rArea.Address
Next
End If
End Sub
you have to size emptyRows() before using it
furthermore you could use WorksheetFunction.Count() to check for any value in current row
finally
Function FindAllEmptyRows(sheet As Worksheet) As Variant
Dim emptyRows() As Variant
Dim i As Long, rowNumber As Long, rowCounter As Long
With sheet.UsedRange ' reference passed sheet UsedRange
rowNumber = .Rows.Count
ReDim emptyRows(0 To rowNumber - 1) ' dim the array to the maximum possible size
For i = rowNumber To 1 Step -1 ' step through reference range rows from the last baxkwards to the first
If WorksheetFunction.Count(.Rows(i)) = 0 Then
emptyRows(rowCounter) = i + .Rows(1).Row - 1 ' fill array in current index with current row index
rowCounter = rowCounter + 1 ' update array index
End If
Next
End With
ReDim Preserve emptyRows(0 To rowCounter) ' redim the array according to the actual number of found empty rows
FindAllEmptyRows = emptyRows
End Function
please note that:
emptyRows(rowCounter) = i + .Rows(1).Row - 1
is storing the absolute row index, i.e. the sheet row index, while
emptyRows(rowCounter) = i
would store the relative row index, i.e. the row index withing the UsedRange, which may start from a row different than row 1

Resources