Remove duplicate values within dynamic ranges identified by text strings - excel

Text “endofdata” in col B identifies the boundaries of multiple ranges on a single sheet. I’m trying to step through each range and remove duplicate values in columns E and F within each range. I also call a routine that deletes blank rows that are generated when duplicates are removed. The bottom row with “endofdata” is always removed when .removeduplicates is executed.
I’ve tried the Do loop but it’s failing. (It works for the first range but fails for the next range) Please suggest how to make this work. What kind of loop should I use? How should I search for “endofdata” string? Thank you very much in advance.
Sub RemoveDupsinRange()
Dim LastRow As Long, i As Long, startRow, EndRow
Call setSheets
LastRow = wsQC.Cells(wsQC.Rows.Count, "A").End(xlUp).Row
Debug.Print LastRow
For i = LastRow To 1 Step -1
Do
If wsQC.Cells(i, 2).Value = "endofdata" Then
startRow = i
End If
i = i - 1
Loop Until wsQC.Cells(i, 2).Value = "endofdata"
EndRow = i
i = i - 1
Range(startRow & ":" & EndRow).Select
Selection.removeduplicates Columns:=Array(5, 6), _
Header:=xlNo
Call DeleteBlanks
Next i
End Sub

I just tested this loop and it worked.
Sub RemoveDupsinRange()
Dim LastRow As Long, i As Long, rStart As Range, rEnd As Range
Call setSheets
LastRow = wsQC.Cells(wsQC.Rows.Count, "A").End(xlUp).Row
Debug.Print LastRow
Set rEnd = wsQC.Cells(LastRow, 2)
For i = LastRow To 2 Step -1
Do
i = i - 1
If wsQC.Cells(i, 2).Value = "endofdata" Then
Set rStart = wsQC.Cells(i, 2)
End If
Loop Until wsQC.Cells(i, 2).Value = "endofdata"
wsQC.Range(rStart.Offset(, -1), rEnd.Offset(, 4)).RemoveDuplicates Columns:=Array(5, 6), Header:=xlNo
Set rEnd = rStart
Call DeleteBlanks
Next i
End Sub

Related

Loop through column that has blanks - excel

I want to know how to loop this a column that blanks inside the column.
I am trying to run a script where if there is a group of a data together, it will make a new column. I got it from here: https://stackoverflow.com/a/15418263/15730901
The problem is that only works for the first column, if I try it a second time on a different column it will stop at the blank because of the loop condition. Is there anyway to change the loop condition to check for the whole column instead of stopping on a blank cell?
Code
sub AddBlankRows()
'
dim iRow as integer, iCol as integer
dim oRng as range
set oRng=range("a1")
irow=oRng.row
icol=oRng.column
do
'
if cells(irow+1, iCol)<>cells(irow,iCol) then
cells(irow+1,iCol).entirerow.insert shift:=xldown
irow=irow+2
else
irow=irow+1
end if
'
loop while not cells (irow,iCol).text=""
'
end sub
Thank you for your time,
Use Range.Find to find the last non-blank cell in the column
lastRow = Columns(iCol).Find("*", SearchOrder:=xlByRows, SearchDirections:=xlPrevious).Row
The your loop becomes
for iRow = lastRow - 1 to firstRow Step -1
if cells(irow + 1, iCol) <> cells(irow,iCol) then
cells(irow + 1,iCol).entirerow.insert shift:=xldown
end if
next iRow
Inserting a Row After a Group of Data
Here's a link to an answer that I posted where the OP was using the same code but wanted it to work for multiple columns. The question has been deleted by the author, so you may not have enough reputation to see it.
A Quick Fix
Option Explicit
Sub AddBlankRows()
Dim rg As Range: Set rg = Range("A1")
Dim r As Long: r = rg.Row
Dim c As Long: c = rg.Column
Dim lRow As Long: lRow = Range("A" & Rows.Count).End(xlUp).Row
Do Until r > lRow
If Len(Cells(r + 1, c).Value) > 0 And Len(Cells(r, c).Value) > 0 _
And Cells(r + 1, c).Value <> Cells(r, c).Value Then
Cells(r + 1, c).EntireRow.Insert Shift:=xlDown
r = r + 2
Else
r = r + 1
End If
Loop
'
End Sub

Comparing two lists with different lengths

I want to compare two ID lists with different lengths. The first list is longer and has Values, while the second has no Values.
When the ID's match, it should paste the Value in the first list to the appropriate place beside list 2.
Sub compareList()
Dim v1, v2, v4, v3()
Dim i As Long
Dim j As Long
v1 = Range("A2", Range("A" & Rows.Count).End(xlUp)).Value
v2 = Range("B2", Range("B" & Rows.Count).End(xlUp)).Value
v4 = Range("D2", Range("D" & Rows.Count).End(xlUp)).Value
ReDim v3(1 To 4)
For i = LBound(v1) To UBound(v1)
If IsError(Application.Match(v1(i, 1), v4, 0)) Then
j = j + 1
Else
v3(j) = v2(i, 1)
End If
Next i
Range("E2").Resize(i) = Application.Transpose(v3)
End Sub
It gives me an out of index error, or pastes the value in the order it reads it (without paying attention to the match).
If you do not like Vlookup and need some VBA code, please test the next code:
Sub compareList()
Dim sh As Worksheet, lastR As Long, lastR2 As Long, i As Long, j As Long, arr, arrFin
Set sh = ActiveSheet
lastR = sh.Range("A" & rows.count).End(xlUp).row
lastR2 = sh.Range("D" & rows.count).End(xlUp).row
arr = sh.Range("A2:B" & lastR).Value
arrFin = sh.Range("D2:E" & lastR2).Value
For i = 1 To UBound(arrFin)
For j = 1 To UBound(arr)
If arrFin(i, 1) = arr(j, 1) Then arrFin(i, 2) = arr(j, 2): Exit For
Next j
Next i
sh.Range("D2:E" & lastR2).Value = arrFin
End Sub
Just continuing on and referring to #FaneDuru stating
If you don't like Vlookup and need some VBA code:
1) Example code using Match()
Sub compareListTM()
'define arrays using help function getRange()
Dim arr: arr = getRange(Sheet1.Range("A:A")).Value
Dim data: data = getRange(Sheet1.Range("B:B")).Value
Dim arrFin: arrFin = getRange(Sheet1.Range("D:D")).Value
Dim ret: ret = Application.Match(arrFin, arr, 0) ' Match() items all at once :-)
Dim i As Long
For i = 1 To UBound(ret)
If Not IsError(ret(i, 1)) Then
ret(i, 1) = data(ret(i, 1), 1)
Else
ret(i, 1) = vbNullString
End If
Next i
Sheet1.Range("E2").Resize(UBound(ret), 1).Value = ret
End Sub
If, however you could give VLookUp a try:
2) Example code using worksheetfunction
Sub compareList2()
Dim results
results = WorksheetFunction.VLookup( _
getRange(Sheet1.Range("D:D")), _
getRange(Sheet1.Range("A:B")), _
2, False)
'write results
Sheet1.Range("E2").Resize(UBound(results), 1).Value = results
End Sub
Help function getRange() used in both examples
A way to avoid repeated lastRow, Range definitions in main code.
I don't pretend this function to be perfect in any way, it just meets the necessary requirements for above procedures kept as short as possible.
Function getRange(ColRange As Range, _
Optional ByVal SearchColumn As Variant = 1, _
Optional ByVal StartRow As Long = 2) As Range
'Author : https://stackoverflow.com/users/6460297/t-m
'Purpose: calculate lastrow of a given search column (default: 1st column of ColRange) and
' return ColRange resized to calculated lastrow (considering optional StartRow argument)
'Par. 1 : assumes that ColRange is passed as ENTIRE COLUMN(S) range object, e.g. Range("X:Y")
'Par. 2 : a) a numeric SearchColumn argument refers to the ColRange's column index
' (even outside ColRange, can be negative or higher than columns count in ColRange!)
' b) a literal SearchColumn argument refers to the worksheet column as indicated (e.g. "B")
'Example: getRange(Sheet1.Range("X:Y")) ... calculates lastrow of 1st column in colRange (i.e. in X)
' getRange(Sheet1.Range("X:Y"), "B") ... calculates lastrow of column B in worksheet
'~~~~~~
'1) get columns in ColRange
Dim StartColumn As Long: StartColumn = ColRange.Columns(1).Column
Dim LastColumn As Long: LastColumn = ColRange.Columns(ColRange.Columns.Count).Column
With ColRange.Parent ' i.e. the worksheet
'2) change numeric search column number to letter(s)
If IsNumeric(SearchColumn) Then
If SearchColumn + StartColumn - 1 < 1 Then ' cols left of StartColumn must be at least "A"
SearchColumn = "A"
Else ' get literal column name, e.g. column "D"
SearchColumn = Split((.Columns(SearchColumn + StartColumn - 1).Address(, 0)), ":")(0)
End If
End If
'3) get last row of SearchColumn
Dim lastRow As Long: lastRow = .Range(SearchColumn & .Rows.Count).End(xlUp).Row
If lastRow < StartRow Then lastRow = StartRow ' avoid findings lower than start row
'4) return data range as function result
Set getRange = .Range(.Cells(StartRow, StartColumn), .Cells(lastRow, LastColumn))
End With
End Function

VBA - Remove cell that contains word from same column

I've seen similar posts out there but not quite the same and seem to be confused on the results I'm getting...
I essentially need to de-dupe a column on LIKE words, so it's somewhat straightforward but apparently not as easy as I thought.
I have a dataset like soo...
When I run my macro it removes rows (as I intended), but doesn't seem to remove all the rows or the wrong rows...
It actually removes the highlighted/yellow rows
I was thinking it should actually remove something like the bottom rows.. where it would keep "aerospace" but remove "aerospace 2019", since the 2019 is kinda redundant and not applicable to me.
My macro is simple, but I thought it would do the trick... what am I doing wrong?
Sub container()
Dim ws As Worksheet, rw As Long, col As Long, i As Long
Set ws = ActiveSheet 'or whatever
i = 2
'For col = 2 To 5 'placeholder in case multiple columns are needed - remove Set col above
For rw = 2 To ws.Cells(Rows.Count, 1).End(xlUp).Row 'from row 1 til last non-empty row
v = ws.Cells(rw, 2).Value 'set range
If Cells(i, 2).Value Like v Then 'determine if the cell contains the value of the word
Cells(i, 2).EntireRow.Delete 'delete
i = i + 1
End If
Next rw
'Next col
End Sub
After Ron's post I was able to create the below, but appears I'm still stuck. I think I've just been looking at this too long.
Sub container()
Dim ws As Worksheet, rng As Range, i As Long, rw As Long
Set ws = ActiveSheet 'or whatever
Set rng = ws.Range("B2:B" & ws.Cells(ws.Rows.Count, "B").End(xlUp).Row) 'set array range
i = Range("B" & Rows.Count).End(xlUp).Row
For rw = ws.Cells(Rows.Count, 1).End(xlDown).Row To 2
v = ws.Cells(rw, 2).Value
If InStr(1, v, rng) > 0 Then
cell.EntireRow.Delete
i = i - 1
End If
Next rw
End Sub

Function.Match in a Loop

I am trying to match a value from a cell (grid_2.range "A1") and grid_2.range("B1") with a column P on a sheet named grid_2 ("Grid2") to copy all the row where there value is located. Therefore, I will need to check on my data and copy/paste the entire row to another sheet maned grid. But for some reason my code loops but only find the match and copy and paste once.
Sub new_copyPaste()
Dim targetSh As Worksheet
Dim i As Variant
Dim lastRow As Long
lastRow = grid.Cells(Rows.Count, "C").End(xlUp).Row + 1
For i = 3 To grid_2.Cells(Rows.Count, "P").End(xlUp).Row
Position = WorksheetFunction.Match(grid_2.Range("A1"), Worksheets("Grid2").Columns(16), 0)
If grid_2.Cells(i, 16).Value = grid_2.Range("A1") Then
Worksheets("Grid2").Rows(Position).Copy
grid.Range("A" & lastRow).PasteSpecial
End If
Next i
For i = 3 To grid_2.Cells(Rows.Count, "P").End(xlUp).Row
Position = WorksheetFunction.Match(grid_2.Range("B1"), Worksheets("Grid2").Columns(16), 0)
If grid_2.Cells(i, 16).Value = grid_2.Range("B1") Then
Worksheets("Grid2").Rows(Position).Copy
grid.Range("A" & lastRow).PasteSpecial
End If
Next i
End Sub
Maybe do you know what I am doing wrong?
I thought about using VLookup, but after researching, it seems that function match would be more appropriate.
I am open for suggestions :)
Match only returns the first match and is not needed here:
Sub new_copyPaste()
Dim lastRow As Long
Dim i As Long
For i = 3 To grid_2.Cells(Rows.Count, "P").End(xlUp).Row
If grid_2.Cells(i, 16).Value = grid_2.Range("A1") Then
Worksheets("Grid2").Rows(i).Copy
lastRow = grid.Cells(Rows.Count, "C").End(xlUp).Row + 1
grid.Range("A" & lastRow).PasteSpecial
End If
Next i
For i = 3 To grid_2.Cells(Rows.Count, "P").End(xlUp).Row
If grid_2.Cells(i, 16).Value = grid_2.Range("B1") Then
Worksheets("Grid2").Rows(i).Copy
lastRow = grid.Cells(Rows.Count, "C").End(xlUp).Row + 1
grid.Range("A" & lastRow).PasteSpecial
End If
Next i
End Sub

How can I compare cells in different rows and insert-right if lower cell if not the same?

How can I compare cells in two rows and and match the lower cell value to the upper cell value, if the two are different? Here is some the same data I am working with and what I hope to see after the code runs.
Before:
[ID_GLOBAL], [ID_UNIQUE], [ID_REAL], [ID_1], [ID_2], [GAS_ADJ], [OBJ_ADJ]
[ID_GLOBAL], [ID_UNIQUE], [ID_REAL], [GAS_ADJ], [OBJ_ADJ]
After:
[ID_GLOBAL], [ID_UNIQUE], [ID_REAL], [ID_1], [ID_2], [GAS_ADJ], [OBJ_ADJ]
[ID_GLOBAL], [ID_UNIQUE], [ID_REAL], NULL AS [ID_1], NULL AS [ID_2], [GAS_ADJ], [OBJ_ADJ]
I think the code will basically look like this, but I haven't gotten the insert-right working properly.
Sub CompareCellsDiffRows()
Dim bothrows As Range, i As Integer
Set bothrows = Selection
With bothrows
For i = 1 To .Columns.Count
If Not StrComp(.Cells(1, i), .Cells(2, i), vbBinaryCompare) = 0 Then
' magic happens here
End If
Next i
End With
End Sub
As you may have guessed, I'm dealing with hundreds of fields in several tables and trying to Union everything together, so all these field names have to match up in the correct order.
Thanks.
I am assuming, as per your example, that the first row is the one that will be always complete.
Sub CompareRowDifferences()
Dim sht As Worksheet
Dim i, LastColumn As Long
Set sht = ThisWorkbook.Worksheets("Transposed Fields")
LastColumn = sht.Cells.SpecialCells(xlLastCell).Column
With sht
For i = 1 To LastColumn
If StrComp(.Cells(1, i), .Cells(2, i), vbBinaryCompare) <> 0 Then
.Cells(2, i).Insert Shift:=xlToRight
.Cells(2, i).Value2 = "NULL AS " & .Cells(1, i).Value2
End If
Next i
End With
End Sub
Hope it helps
Regarding your second question (if they are not ordered) and assuming always that the first line is the ones that rules...
Sub CompareRowDifferences()
Dim sht As Worksheet
Dim i, j, LastColumn As Long
Set sht = ThisWorkbook.Worksheets("Transposed Fields")
LastColumn = sht.Cells.SpecialCells(xlLastCell).Column
j = 0
With sht
For i = 1 To LastColumn
Test = Application.WorksheetFunction.CountIf(Range _
(Cells(2, i), Cells(2, LastColumn + j)), .Cells(1, i).Value2)
If Test >= 1 Then
.Cells(2, i).Insert Shift:=xlToRight
.Cells(2, i).Value2 = .Cells(1, i).Value2
Else
.Cells(2, i).Insert Shift:=xlToRight
.Cells(2, i).Value2 = "NULL AS " & .Cells(1, i).Value2
End If
j = j + 1
Next i
Range(Cells(2, LastColumn), Cells(2, LastColumn + j)).ClearContents
End With
End Sub
This procedure identifies and uses the row with a higher number of fields (i.e. no-empty cells), and uses it as "model" to update the other row regardless of the position of the fields in the other row.
Sub Headers_Comparison(rInput As Range)
Dim aOut As Variant, aSrc As Variant, aTrg As Variant
Dim bMatch As Byte, bRow As Byte, b As Byte
With WorksheetFunction
Rem Validate Fields in Rows
If .CountA(rInput.Rows(1)) > .CountA(rInput.Rows(2)) Then
bRow = 2
aSrc = .Transpose(.Transpose(rInput.Rows(1).Value2))
aTrg = .Transpose(.Transpose(rInput.Rows(2).Value2))
Else
bRow = 1
aSrc = .Transpose(.Transpose(rInput.Rows(2).Value2))
aTrg = .Transpose(.Transpose(rInput.Rows(1).Value2))
End If
aOut = aTrg
For b = 1 To UBound(aSrc)
bMatch = 0
On Error Resume Next
bMatch = .Match(aSrc(b), aTrg, 0)
On Error GoTo 0
aOut(b) = IIf(bMatch > 0, vbNullString, "NULL AS ") & aSrc(b)
Next: End With
rInput.Rows(bRow).Value = aOut
End Sub
It should be called in this manner:
Call Headers_Comparison(rSel) 'update with required range
I think I just figured it out!
Sub CompareRowDifferences()
Dim i As Integer
Dim sht As Worksheet
Dim LastColumn As Long
Set sht = ThisWorkbook.Worksheets("Transposed Fields")
LastColumn = sht.UsedRange.Columns(sht.UsedRange.Columns.Count).Column
For i = 1 To LastColumn
If Not StrComp(sht.Cells(1, i), sht.Cells(2, i), vbBinaryCompare) = 0 Then
' magic happens here
Set Rng = sht.Cells(2, i)
Rng.Insert Shift:=xlToRight
sht.Cells(2, i).Value = "NULL AS " & sht.Cells(1, i).Value
End If
Next i
End Sub
This seems to work. Although, this is a pretty simple solution. I understand it would be much more complex if the order of names the lower row changed. This works ONLY because the names in row 2 match the names in row 1, there are just fewer names. I would love to see what the code would look like if the order of the row 2 names was switch around, compared to the row 1 names.

Resources