Comparing cells pairs in a column for duplicates Excel VBA - excel

Hello,
Appreciate your help in advance
I have used this code below to find matching values in a column.
I am looking for help to do the following:
The code below compares all cells in the column at once for matching values starting at cell B3 and going down, then highlights all matching cells. The code works fine.
But Instead, I need to check for duplicates in pairs comparing two cells at a time instead of the whole column, and also in reverse column order starting from the bottom cell in the column and going to top.
Example of match process would be:
Compare Cell B10 = B9 (highlight both if they match, If not, Move to next pair to check, B9 = B8, B8 = B7, and so on )
Dim rg As Range
Set rg = Range("B3", Range("B3").End(xlDown))
Dim uv As UniqueValues
Set uv = rg.FormatConditions.AddUniqueValues
uv.DupeUnique = xlDuplicate
uv.Interior.Color = vbRed
Thanks

First you need a loop, to give you more control over what Excel is doing. Loop over your wanted range like this:
For Each cell In rg
Next cell
But to go backwards, it's harder. You'll have to get the highest and lowest row numbers in your range, and step-1 through them.
for a = rg_highestrow to rg_lowestRow step -1
next
The problem is, this doesn't know what column you're using. The .range object is going to make things complicated. So, write a method that accepts a param of which col you want to do, and what starting and ending row. Like this:
sub find_duplicates(byval colnumber as integer, byval startrow as integer, byval endrow as integer)
end sub
Then you can loop backwards, using step -1:
for a = endrow to startrow step -1
next
You'll need the logic to spot duplicates between the current cell, and the other cell. To do this, refer to the "previous cell". This means, you want to start your loop on the SECOND row, not the first. Like this:
for a = (endrow-1) to startrow step -1
next
This won't work if there's only one row in your range. So test if your range only has one row. If it only has one, a comparison is impossible, so quit. Putting it all together so far:
sub find_duplicates(byval colnumber as integer, byval startrow as integer, byval endrow as integer)
if endrow-startrow<1 then exit sub 'Needs at least 2 rows to function. Exit.
for row_a = (endrow-1) to startrow step -1 'Loop backwards using step-1, but stop short of the very last item.
'Do the comparison of row_a and row_a+1
if Cells(row_a,colnumber).Value = Cells(row_a+1,colnumber).Value then
'They match. Do whatever you need to do
end if
next
end sub
You can call it like this:
find_duplicates(2,10,20)
This will search the indicated column from row 20 to row 10, comparing pairs of rows' cells for duplicate values.

This code below has worked perfectly.
the -1 in the offset formula has helped me compare current cell to previous one (as in reverse order)
Dim rngMyCell As Range
Dim wsMySheet As Worksheet
Application.ScreenUpdating = False
Set wsMySheet = ActiveSheet
For Each rngMyCell In wsMySheet.Range("F3:F" & wsMySheet.Range("F" & Rows.Count).End(xlUp).Row)
If Val(rngMyCell.Offset(-1, 0)) = Val(rngMyCell) Then
wsMySheet.Range("F" & rngMyCell.Row & ":F" & rngMyCell.Row).Interior.Color = RGB(255, 255, 0)
Else
wsMySheet.Range("F" & rngMyCell.Row & ":F" & rngMyCell.Row).Interior.Color = xlNone
End If
Next rngMyCell
Set wsMySheet = Nothing
Application.ScreenUpdating = True

Related

Delete rows IF all cells in selected ROW is BLANK

I have financial data where some rows are blank and id like to be able to delete the entire row IF entire rows in a selected range are blank (its important for it to be in selected range as I might have "Revenues" in column A but then I have column B-D be blank data (no numbers basically)).
I'd like for it to apply to a selected range, instead of having a predetermined range in the code (for the code to be flexible).
I am trying to use this format but it doesnt seem to be working:
Sub deleteBlankRows()
Dim c As Range
On Error Resume Next
For Each c In Selection.Row.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Next
End Sub
Any thoughts would be welcome.
Loop trough each complete row of selection and check if the count of blank cells matchs the count of all cells in row:
My code:
Dim rng As Range
For Each rng In Selection.Rows
If Application.WorksheetFunction.CountBlank(rng) = rng.Cells.Count Then rng.EntireRow.Delete
Next rng
After executing code:
The emtpy row is gone
UPDATE:
#VasilyIvoyzha is absolutely right. For each won't work properly on this situation. A better approach would be:
Dim i&, x&, lastRow&
lastRow = Range(Split(Selection.Address, ":")(1)).Row
x = Selection.Rows.Count
For i = lastRow To Selection.Cells(1).Row Step -1
If WorksheetFunction.Concat(Selection.Rows(x)) = "" Then Rows(i).Delete
x = x - 1
Next i
This way will delete empty rows on selection, even if they are consecutive.

Last Row Returns 1 - incorrect value

situation is following:
I have 32 columns with data (various number of rows in columns) and need to delete cells with .value "downloaded" (always last cell in a column).
I have a code looping from column 32 to 1 and searching last_row for "downloaded" value. For 30 columns code seems to be working flawlessly but 2 columns return last_row value 1 even though there are multiple values (in fact hundreds of them) but they are non existent for VBA code.
Code:
Last_Col = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
last_row = ws.Cells(Rows.Count & Last_Col).End(xlUp).Row
For R = Last_Col To 1 Step -1
With ws
Last_Col = R
last_row = ws.Cells(.Rows.Count & Last_Col).End(xlUp).Row
If Cells(last_row, Last_Col).Value Like "*Downloaded*" Then
Cells(last_row, Last_Col).ClearContents
End If
End With
Next R
Data is being drained from another worksheets. For 2 columns where I experience an error, I manually deleted values and inserted another, random batch of values and code worked as intended.
Checked columns formatting, worksheets from which data is taken but I struggle to find a solution.
Thank you for your help.
Clear Last Cell If Criteria Is Met
The main mistake was using Cells(.Rows.Count & Last_Col), where .Rows.Count & Last_Col would have resulted in a 8 or 9-digit string, while it should have been ws.Cells(ws.Rows.Count, Last_Col).End(xlUp).Row which was pointed out by chris neilsen in the comments.
Another important issue is using ws. in front of .cells, .rows, .columns, .range, aka qualifying objects. If you don't do it and e.g. the wrong worksheet is active, you may get unexpected results.
There is no need for looping backwards unless you are deleting.
Although it allows wild characters (*, ?), the Like operator is case-sensitive (a<>A) unless you use Option Compare Text.
The first solution, using the End property, will fail if a number of last columns is hidden or if you insert a new first row e.g. for a title.
The second solution, using the Find method (and the first solution), may fail if the data is filtered.
The Code
Option Explicit
Sub clearLastEnd()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastCol As Long
LastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Dim LastRow As Long
Dim c As Long
For c = 1 To LastCol
LastRow = ws.Cells(ws.Rows.Count, c).End(xlUp).Row
With ws.Cells(LastRow, c)
If InStr(1, .Value, "Downloaded", vbTextCompare) > 0 Then
.ClearContents
End If
End With
Next c
End Sub
Sub clearLastFind()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim cel As Range
Set cel = ws.Cells.Find(What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
If Not cel Is Nothing Then
Dim c As Long
For c = 1 To cel.Column
Set cel = Nothing
Set cel = ws.Columns(c).Find(What:="*", _
SearchDirection:=xlPrevious)
If Not cel Is Nothing Then
If InStr(1, cel.Value, "Downloaded", vbTextCompare) > 0 Then
cel.ClearContents
Else
' The current last non-empty cell does not contain criteria.
End If
Else
' Column is empty.
End If
Next c
Else
' Worksheet is empty.
End If
End Sub
EDIT:
So you are curious why it worked at all. The following should shed a light on it:
Sub test()
Dim i As Long
Debug.Print "Right", "Wrong", "Rows.Count & i"
For i = 1 To 32
Debug.Print Cells(Rows.Count, i).Address, _
Cells(Rows.Count & i).Address, Rows.Count & i
Next i
End Sub
In a nutshell, Cells can have 1 or 2 arguments. When 1 argument is used, it refers to the n-th cell of a range, and it 'counts' by row. The more common usage is with 2 arguments: rows, columns. For example:
Cells(5, 10) ' refers to cell `J5`.
Using one argument is inconvenient here:
Cells(16384 * (5-1) + 10)
i.e.
Cells(65546)
It may be convenient when processing a one-column or a one-row range.
Well , let me see if i understand you have a table in worksheet table have 32 columns and X rows (because you only put WS and i can know if is WS=worksheet or WS= Table-range)
for this i am going to say is selection (if you put worksheet only hace to change for it)
in your code put:
Last_Col = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
but in this you always wil obtein 1st cell so i dont understand why exist?
WS.columns.count
return number of columns you selection have
.End(xlToLeft)
return last cell if star to move to left (like Ctrl - left key)
so
Last_Col ---first go to cells (1,ws.Columns.Count) then go to left (End(xlToLeft)) and the end return number of column where finish (.Column) in this case you always get cell (1,"first column of your table")
NOTE: because you wrote that you have allways value in your cells (I have 32 columns with data (various number of rows in columns)
And for Row you have same question
Then you Wrote you want "Delete" but in your code you put Erase value (.ClearContents) so what do you want? because both are no equal
BUT if you have a table and want to search in any cells that have "Download" and only want to "clear content" you just may to use ".find" instead; or if you want to do all at same time you can use .replace (need to check before if .find return "nothing" or no , because if return nothing you get error)
If you have a table with 32 columns and each row have one cell where you put "Donloaded" and want to "delete" all row your code only need select column where appear "downloaded" (example Column "status").
If you have a table where any cell can take value "downloaded" and want to "delete" that cell you need to take care to resize your table and "move to" (when you delete cells you need to say where you want to move yor data remain "letf, "rigth", "up", down).
However if you say that "Downloaded" always appear in last row you can use For to change for all columns and use .end(xlDown)
For i=1 to 32
if cells(1,i).end(xlDown).value="downloaded" then cells(1,i).end(xlDown).ClearContents
next
BUT you need put more information because if you cant garantize that all cells have values and exist cells with "nothing" you will need

Creating a data series linear trend to fill between cells in the same column

I am currently working with a modified collection of data with gaps in each recorded value. I would like to use Excel's fill utility under the Home tab (editing(group)->fill->series. with Rows,linear,trend) to generate values with a linear trend. I am working with a range from A2:A6569 so manually doing this is not practical. I have generated this data using an Excel function if statement, where if its not calling a value, its placing ("") in each cell. Everything that I've found and tried is not working.
Data Sample: (where x is a blank cell)
"title"
0.004
x
x
x
5.214
x
x
7.01
x
x
x
6.97
Pseudo code of what I have tried:
Dim rStoA, rStoB, rStoC As Range
With ActiveSheet.Range("A2:A6569")
for each value in range
if IsNumeric(value) And IsEmpty(rStoA) Then
rStoA = value
else if IsNumeric(value) and IsNumeric(rStoA) Then
rStoB = value
Set rStoC = .Range(rStoA, rStoB)
rStoC.DataSeries Rowcol:=xlColumns, Type:=xlLinear,
Date:=xlDay, Trend:=True
rStoA = rStoB
end if
next
End With
From recording a macro, I have found the function I need from Excel's fill utility:
"selected_range".DataSeries Rowcol:=xlColumns, Type:=xlLinear, Date:=xlDay, Trend:=True
I believe that my problem is my use of Range's but I am stuck.
Also, would it be better if I were to use Range.Find? If so, could someone steer me in the right direction with that?
I appreciate any advice.
Thank you!
If I get what you are trying to do right, you could do something like the following:
Option Explicit
Sub fillSeries()
Dim sht As Worksheet
Dim firstCell As Range, lastCell As Range
Dim fillRange As Range, cell As Range
Dim lastRow As Long, firstRow As Long
Dim fillCol As String
Set sht = ThisWorkbook.Worksheets("Name of your worksheet")
lastRow = 13 'the row of the last cell you want to fill
firstRow = 2 'the row where your data begins
fillCol = "A" 'the column to be filled
For Each cell In Range(sht.Cells(firstRow, fillCol), sht.Cells(lastRow, fillCol))
If cell.Value = "" Then
cell.ClearContents
End If
Next cell
Set firstCell = sht.Range(fillCol & firstRow)
While firstCell.Row <> lastRow
If firstCell.End(xlDown).Row > lastRow Then
Set lastCell = sht.Range(fillCol & lastRow)
Else
Set lastCell = firstCell.End(xlDown)
End If
sht.Range(firstCell, lastCell).DataSeries Type:=xlLinear, Trend:=True
Set firstCell = lastCell
Wend
End Sub
Basically, the code finds pairs of non blank cells, that are separated by blank cells and fills those blanks in between. The last cell of the previously used pair becomes the first of the next one and so on until the last cell is reached.
I am not sure if the last cell of the range is already filled or not so I built the code in a way so you can define manually which cell is the last one.
So, for the data sample you've provided, assuming that the filling should stop at row 13 (where 6,97 is), the result would be the following:
If you would like to continue the filling until say row 20, the result would be:

Counting number of rows including blank rows until 2 blanks encountered

Currently have a macro which counts the number of rows to use as a variable. Due to new data source which has blank rows this no longer functions.
I need it to continue counting until it hits two blanks which is the end of the data source but also include the blank rows in the count.
I have a macro that counts the number of rows to provide a variable for a separate macro which uses that number for a loop function. Everything was working fine except the new data to count has blank row in between data (which must remain and included in the total row count).
I can figure out how to count non-blanks and full cells separately but can't figure out how to do it together. Any suggestions?
Sub num_rows(nrows As Variant)
Dim numrows
Dim ra As Range
Dim i As Integer
'get number of rows between blank cells
Sheets("4 Gantt Overview").Activate
Set ra = Range("b7")
numrows = Range(ra.Address,Range(ra.Address).End(xlDown)).rows.Count
Range(ra.Address).Select
'establish counting loop
For i = 1 To numrows
ActiveCell.Offset(1, 0).Select
Next
nrows = numrows
Range("b7").Select
End Sub
For a data set of 130 rows and 2 blanks its counting only to 30 rows (the first blank position).
Imagine the following data:
If you want to find the first 2 blanks, you can use .SpecialCells(xlCellTypeBlanks) to fund all blanks in your range (here column A). It will turn something like the selected cells in the image. There are 6 selected areas that you can access with .SpecialCells(xlCellTypeBlanks).Areas.
So if we loop through all these areas For Each Area In .Areas and check their row count If Area.Rows.Count >= 2, we can easily find the area with 2 rows (or at least 2 rows).
The amount of rows (empty or not) is then Area.Row - AnalyzeRange.Row
So we end up with:
Option Explicit
Sub TestCount()
MsgBox CountRowsUntilTwoBlanks(Worksheets("Sheet1").Range("A:A"))
End Sub
Function CountRowsUntilTwoBlanks(AnalyzeRange As Range) As Long
Dim Area As Range
For Each Area In AnalyzeRange.SpecialCells(xlCellTypeBlanks).Areas
If Area.Rows.Count >= 2 Then 'if 2 or more then use >=2, if exactly 2 use =2
CountRowsUntilTwoBlanks = Area.Row - AnalyzeRange.Row
Exit For
End If
Next Area
End Function
So for this example it will return 16 rows.
Note that if your goal is to find the last used row, which in this example would be row 20 then you could just use …
Dim LastRow As Long
With Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
… to find the last used row in column A. Here LastRow returns 20.
This this macro. It will find first cell that is blank with a following cell blank as well.
Sub stopAtDoubleBlank()
Dim i As Long
i = 2
Do While Range("A" & i).Value <> "" Or Range("A" & i + 1) <> ""
i = i + 1
Loop
MsgBox i
End Sub
You can try something like this too if you want:
Sub lastrow()
Dim lr As Long
lr = ActiveSheet.Rows.Count
Cells(1, lr).Select
Selection.End(xlUp).Select
lr = ActiveCell.Row
End Sub
(go down to the very bottom and jump up to the last not empty row in A cloumn(that can be changed) also you can add something like +1 if you want an empty row at the end)

Summing rows above blank cell until previous blank cell

Hello!
I am trying to build a macro, that finds a blank cell in a range in a single column and sums all cells between this blank cell and the previous blank cell.
I've searched the web a lot, and while others have asked this question, i do not find the answers to them particularly helpful in my situation, as i need this to work through 3500+ rows.
E.g:
4
3
4
BLANK 1
2
5
7
1
BLANK 2
1
4
BLANK 3
In this case the cell called "BLANK 1" would be the sum of the 3 previous rows: 4+3+4=11
"Blank 2" would be 15 and so forth.
The range is "G8:G3561".
Edit
For the quick answer see Mr_Nitrogen's answer. It works beautifully!
However, as I am new to VBA and coding in general I do not know how or why the code works.
I am very eager to obtain a better understanding of VBA, which is why I'm continuing this thread (if allowed to).
Furthermore, I would like to provide evidence for the commenters that I have indeed worked on this myself and that I prefer to build my own code.
It's important for me to understand why my code works or doesn't work, which is why i hope that you still want to help me develop my own code.
I finally found an approach that is logical to me. I know that this is not the simplest way to do it, but I would like to know if it could work.
I've written the following code.
Sub Sum_storage()
Dim rng As Range
Dim cell As Range
Dim cell2 As Range
Dim cell3 As Range
Range("G8").End(xlDown).Offset(1, 0).Select
Set cell = Selection
cell.Value = "temp" 'Finds the first blank cell in column G _
and creates a temporary value in order _
to find the second blank cell
Range("G8").End(xlDown).Offset(1, 0).Select
Set cell2 = Selection
cell.Offset(1, 0).Select
Set cell3 = Selection 'The range i need to sum can _
now be described as "cell3:cell2"
Set rng = Range(Range("cell3"), Range("cell2")) 'The code works until this point
cell2.Value = WorksheetFunction.Sum(rng)
The idea is to define the range i want to sum with multiple variables.
My problem is trying to refer to these variables (and setting them in an easier way than using .Offset).
Is it simply not possible to set a range (rng) based on two previously set ranges?
If this is possible the next step for me is creating some kind of loop that could make this work for all 3500+ rows.
You were on the right track with using End(xlDown).
This one should be a way faster than looping through all cells, because this jumps to the next empty cell and sums via WorksheetFunction.Sum.
Option Explicit
Public Sub DoMyStuff()
Dim ws As Worksheet
Set ws = Worksheets("Tabelle8") 'define your worksheet here
Dim FirstCell As Range
Set FirstCell = ws.Range("G8")
Dim VeryLastCell As Range 'get very last cell as stop criteria
Set VeryLastCell = ws.Cells(ws.Rows.Count, "G").End(xlUp)
Do
Dim LastCell As Range
If FirstCell.Offset(1) = vbNullString Then 'test if there is only one cell to sum
Set LastCell = FirstCell
Else
Set LastCell = FirstCell.End(xlDown)
End If
With LastCell.Offset(1, 0) 'this is the cell we want to write the sum
.Value = Application.WorksheetFunction.Sum(ws.Range(FirstCell, LastCell))
.Interior.Color = RGB(255, 0, 0)
End With
Set FirstCell = LastCell.Offset(2, 0)
Loop While FirstCell.Row < VeryLastCell.Row
End Sub
This isnt that complicated to do with for loop, maybe something like
lastrow = Cells(Rows.Count, "G").End(xlUp).Row
firstrow = 8
TempTotal = 0
for x = firstrow to lastrow + 1
If Cells(x, "G") <> "" Then
TempTotal = TempTotal + Cells(x, "G")
Else: Cells(x, "G") = TempTotal
Cells(x, "G").Interior.ColorIndex = 4
TempTotal = 0
End if
Next x
Ive made an edit to make it a bit simpler
The Logic of the Code:
Define the last row with data in column "G"
Move down cell by cell until that row
If the cell has a value in it, add it to the temporary total.
If it is blank, inset the temporary total and reset the tempTotal to zero
This is the first line, in which we select the very last last cell in column "G", use use End(xlup) on it to get to the last cell with data and use .row to get the row number of that cell
Set up a For loop, which runs the code between "for" and "next x" lines repeatedly while incrementing the value of x from "firstrow" to "lastrow + 1" each time it repeats ( so if firsrow is 1 and lastrow is 100) then it will run the code 100 times with x = 1,2,3,4,5 etc.)
this is the "if" statement, "<>" means does not equal, so we are saying if the cell on row x, col "G" is not equal to "" which is an empty string (or nothing) then we do the next line (add its value to tempTotal)
if the "If" statement isnt true (if the cell is blank) then we do what is under the "Else" and make that cell equal to TempTotal, change its color to green (4 is a colorcode, they go between 1 and 50), and reset the temptotal to 0.

Resources