Bascially I have found the below formula which is perfect except it only filters duplicates out based on column A, whereas I only want the rows deleted if Col A, B and C are all duplicated.
Sub removeDupes()
Dim i As Long
Dim ws As Worksheet
Set ws = ActiveSheet 'This can be changed to a specific sheet: Worksheets("sheetName")
With ws
For i = .Cells(.Rows.Count, 1).End(xlUp).Row To 3 Step -1
If .Cells(i, 1).Value = .Cells(i + 1, 1).Value Then
.Rows(i).Delete
End If
Next i
End With
End Sub
How can I edit this code so it applies to 3 columns?
Remove Duplicates
Before
After
Option Explicit
Sub RemoveDupesShort()
With ActiveSheet.UsedRange
.Range("A3", .Cells(.Rows.Count, .Columns.Count)) _
.RemoveDuplicates (VBA.Array(1, 2, 3))
End With
MsgBox "Duplicates removed.", vbInformation
End Sub
Sub RemoveDupes()
' Define constants.
Const FirstCellAddress As String = "A3"
Dim DupeCols() As Variant: DupeCols = VBA.Array("A", "B", "C")
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
' For worksheet 'Sheet1' in the workbook containing this code, instead use:
'Set ws = ThisWorkbook.Worksheets("Sheet1")
' Reference the range ('rg').
Dim rg As Range
With ws.UsedRange
Set rg = ws.Range(FirstCellAddress, .Cells(.Rows.Count, .Columns.Count))
End With
' Write the column numbers to a zero-based variant array ('Cols').
Dim cUpper As Long: cUpper = UBound(DupeCols)
Dim Cols() As Variant: ReDim Cols(0 To cUpper)
Dim c As Long
For c = 0 To cUpper
Cols(c) = ws.Columns(DupeCols(c)).Column
Next c
' Remove duplicates.
rg.RemoveDuplicates (Cols)
' Note that the array of column numbers also needs to be evaluated:
' '(Cols)' which is short for 'Evaluate(Cols)'
' Inform.
MsgBox "Duplicates removed.", vbInformation
End Sub
Related
I am trying to copy, filter the values of column D = India and France and Column C > 1/06/2020 and paste those unique filtered values in the another worksheet, Could you please help me?
I tried to do it but i couldn't manage to create multiple filters and copy only the unique values
Public Sub ConditionalRowCopy()
' Declare object variables
Dim sourceSheet As Worksheet
Dim targetSheet As Worksheet
Dim cell As Range
' Declare other variables
Dim sourceLastRow As Long
Dim targetLastRow As Long
' Set a reference to the sheets so you can access them later
Set sourceSheet = Workbooks("Bookcopy.xlsm").Worksheets("copy")
Set targetSheet = Workbooks("Bookpaste.xlsm").Worksheets("paste")
' Find last row in source sheet based on column "R"
sourceLastRow = sourceSheet.Cells(sourceSheet.Rows.Count, "D").End(xlUp).Row
' Find cell with word "Emetteurs", search in column R)
For Each cell In sourceSheet.Range("D1:D" & sourceLastRow).Cells
' If match
If cell.Value = "India" Then
' Find last row in target sheet based on column "A"
targetLastRow = targetSheet.Cells(targetSheet.Rows.Count, "A").End(xlUp).Row
' Copy entire row to next empty row in target sheet
cell.EntireRow.Copy Destination:=targetSheet.Range("A" & targetLastRow).Offset(RowOffset:=1)
End If
Next cell
End Sub
Copy Unique Values (2 columns)
Option Explicit
Sub CopyUniqueValues()
' Write the values from the source to an array.
Dim sws As Worksheet: Set sws = Workbooks("Bookcopy.xlsm").Worksheets("copy")
Dim Data(), srCount As Long, cCount As Long
With sws.Range("A1").CurrentRegion
srCount = .Rows.Count - 1
If srCount = 0 Then Exit Sub ' no data
cCount = .Columns.Count
Data = .Resize(srCount).Offset(1).Value
End With
' Write the unique values from the array to the 'keys' of a dictionary
' and their rows of the values' first occurrences to the 'items'.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim sr As Long, sString As String
For sr = 1 To srCount
sString = Data(sr, 4) & "#" & Int(Data(sr, 3))
If Not dict.Exists(sString) Then dict(sString) = sr
Next sr
' Using the rows from the 'items' of the dictionary, write the unique rows
' to the top of the array.
Dim sKey, tr As Long, c As Long
For Each sKey In dict.Keys
sr = dict(sKey)
tr = tr + 1
For c = 1 To cCount
Data(tr, c) = Data(sr, c)
Next c
Next sKey
' Reference the target range.
Dim tws As Worksheet: Set tws = Workbooks("Bookpaste.xlsm").Worksheets("paste")
Dim tCell As Range: Set tCell = tws.Cells(tws.Rows.Count, "A").End(xlUp).Offset(1)
Dim trg As Range: Set trg = tCell.Resize(tr, cCount)
' Write the top rows from the array to the target range.
trg.Value = Data
' Clear below.
trg.Resize(tws.Rows.Count - trg.Row - tr + 1).Offset(tr).ClearContents
' Inform.
MsgBox "Unique values copied.", vbInformation
End Sub
you could use Excel built in AutoFilter() and RemoveDuplicates() functionalities
Sub ConditionalRowCopy()
With Workbooks("Bookcopy.xlsm").Worksheets("copy")
With .Range("D1", .Cells(.Rows.Count, 1).End(xlUp))
.AutoFilter Field:=3, Criteria1:=">06/01/2020"
If Application.Subtotal(103, .Resize(, 1)) > 1 Then
.SpecialCells(xlCellTypeVisible).Copy Destination:= Workbooks("Bookpaste.xlsm").Worksheets("paste").Range("A1")
With Workbooks("Bookpaste.xlsm").Worksheets("paste")
With .Range("D1", .Cells(.Rows.Count, 1).End(xlUp))
.RemoveDuplicates Columns:=Array(3, 4), Header:=xlNo
End With
End With
End If
End With
.AutoFilterMode = False
End With
End Sub
I have a file with a few sheets, I need to extract values from each not empty cell into a column on another sheet.
Would be awesome if while doing that duplicates can be removed as well.
The following code infinitely loops. I don't see how to break the loop since all the events are being used in the body of the code.
Range where the cells are being looked for on both sheets are different, that is why I used .End(xlUp) to define the last row with values in cells.
I cannot use empty cells as a trigger for stopping the loop because there are empty cells between cells with values.
Sub updt()
Dim ws As Worksheet, currWs As Worksheet, Lng As Integer, rng As Range
Set ws = wb.Worksheets("Sheet1") 'the source sheet
Set currWs = Sheets("Sheet2") 'the destination sheet
Lng = ws.Cells(Rows.Count, 1).End(xlUp).Row
Set rng = ws.Range("A2:A" & Lng)
For Each c Lng rng
If WorksheetFunction.CountIf(currWs.Range("A:A"), c.Value) = 0 Then
currWs.Range("A" & currWs.Cells(Rows.Count, 1).End(xlUp).Row)(2) = c.Value
End If
Next
End Sub
Update Column With Unique Non-Existing Values From a Column of Another Worksheet Using a Dictionary
To avoid further complications, no arrays are used.
Option Explicit
Sub UpdateWorksheet()
' Reference the workbook.
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the source worksheet, calculate the last row
' and reference the source column range.
Dim sws As Worksheet: Set sws = wb.Worksheets("Sheet1")
Dim slRow As Long: slRow = sws.Cells(sws.Rows.Count, "A").End(xlUp).Row
Dim srg As Range: Set srg = sws.Range("A2:A" & slRow)
' Reference the destination worksheet and calculate the last row.
Dim dws As Worksheet: Set dws = wb.Worksheets("Sheet2")
Dim dlRow As Long: dlRow = dws.Cells(dws.Rows.Count, "A").End(xlUp).Row
' Define a dictionary (object).
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' case-insensitive
' Declare variables.
Dim cCell As Range
Dim cKey As Variant
' Write the unique values from the destination column range
' to the dictionary.
If dlRow > 1 Then ' 1 means 'first row - 1' i.e. '2 - 1'
Dim drg As Range: Set drg = dws.Range("A2:A" & dlRow)
For Each cCell In drg.Cells
cKey = cCell.Value
If Not IsError(cKey) Then ' exclude error values
If Len(cKey) > 0 Then ' exclude blanks
dict(cKey) = Empty
End If
End If
Next cCell
End If
' Add the unique values from the source column range
' to the dictionary.
For Each cCell In srg.Cells
cKey = cCell.Value
If Not IsError(cKey) Then ' exclude error values
If Len(cKey) > 0 Then ' exclude blanks
dict(cKey) = Empty
End If
End If
Next cCell
' Check if the dictionary is empty.
If dict.Count = 0 Then
MsgBox "No valid values found.", vbCritical
Exit Sub
End If
' Clear the previous values from the destination first cell to the bottom
' of the worksheet.
Dim dCell As Range: Set dCell = dws.Range("A2")
With dCell
.Resize(dws.Rows.Count - .Row + 1).ClearContents
End With
' Write the unique values from the dictionary to the destination worksheet.
For Each cKey In dict.Keys
dCell.Value = cKey ' write
Set dCell = dCell.Offset(1) ' reference the cell below
Next cKey
' Inform.
MsgBox "Worksheet updated.", vbInformation
End Sub
You might want to use AdvancedFilter:
Option Explicit
Sub Copy_Advanced()
Dim ws As Worksheet, currWs As Worksheet, Lng As Integer, rng As Range
Set ws = Worksheets("Sheet1") 'the source sheet
Set currWs = Sheets("Sheet2") 'the destination sheet
Lng = ws.Cells(Rows.Count, 1).End(xlUp).Row
Set rng = ws.Range("A1:A" & Lng)
ws.Range("D1").Value = ws.Range("A1").Value
ws.Range("D2") = ">0"
ws.Range(rng.Address).AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=ws.Range("D1:D2"), _
CopyToRange:=currWs.Range("A1"), _
Unique:=True
End Sub
So I have a worksheet called "gar_nv" containing in its first row some strings that I'd like to define as names for my columns. For instance, first cell of column A is "Number", I'd like to refer to the column A(starting from the second cell) as "Number" instead of column "A".
Sub NameCol()
Dim LastRow As Long
Dim x As Long, Rng As Range
With gar_nv
For x = 1 To .UsedRange.Columns.Count
LastRow = Cells(Cells.Rows.Count, x).End(xlUp).Row
Set Rng = Cells(2, x).Resize(LastRow)
.Names.Add Name:=Cells(1, x), RefersTo:=Rng
Set Rng = Nothing
Next
End With
End Sub
When I test my code like this, it throws a 91 error, what am I doing wrong?
Sub test()
With gar_nv
For Each Rng In .Range("Number")
MsgBox (Rng.Value)
Next
End With
End Sub
Create Names for Columns of Data
gar_nv is the code name of a worksheet in the workbook containing this code.
Option Explicit
Sub NameColumnsData()
' Delete all previous names in the worksheet.
'DeleteAllWorksheetNames gar_nv
Dim hrg As Range ' header range
Dim drg As Range ' data range
Dim cCount As Long ' number of columns
With gar_nv.UsedRange
Set hrg = .Rows(1)
Set drg = .Resize(.Rows.Count - 1).Offset(1)
cCount = .Columns.Count
End With
Dim crg As Range
Dim c As Long
Dim cTitle As String
For c = 1 To cCount
cTitle = hrg.Cells(c).Value
Set crg = drg.Columns(c)
gar_nv.Names.Add cTitle, crg
' Of course, you can lose the variables and just do:
'gar_nv.Names.Add hrg.Cells(c).Value, drg.Columns(c)
Next c
MsgBox "Column data names created.", vbInformation
End Sub
Sub NameColumnsDataTEST()
Dim cCell As Range
With gar_nv
For Each cCell In .Range("Number").Cells
' Caution! If there are many cells it may take 'forever'.
'MsgBox cCell.Address(0, 0) & ": " & cCell.Value
' Rather print to the Immediate window (Ctrl+G):
Debug.Print cCell.Address(0, 0) & ": " & cCell.Value
Next
End With
End Sub
Sub DeleteAllWorksheetNames(ByVal ws As Worksheet)
Dim nm As Name
For Each nm In ws.Names
Debug.Print nm.Name, nm.RefersTo, "Deleted!"
nm.Delete
Next nm
End Sub
How do I copy a formula result?
I select which rows to keep in the worksheet "UI", by marking the rows with the value 1 in column B.
I assigned the following macro to a command button, which copies the selected rows to the worksheet "Output":
Private Sub CommandButton1_Click()
Dim i As Integer
Dim ws1 As Worksheet: Set ws1 = ThisWorkbook.Sheets("UI")
Dim ws2 As Worksheet: Set ws2 = ThisWorkbook.Sheets("Output")
For i = 2 To ws1.Range("B999").End(xlUp).Row
If ws1.Cells(i, 2) = "1" Then ws1.Rows(i).Copy ws2.Rows(ws2.Cells(ws2.Rows.Count, 2).End(xlUp).Row + 1)
Next i
End Sub
As the values in the rows are the results of formulas, the results pasted in "Output" come back as invalid cell references.
Is there a way of copy-pasting as text?
You should use "xlPasteValues" property to avoid invalid cell references when values in the rows are the results of formulas. You can try to modify your code as follows:
Private Sub CommandButton1_Click()
Dim i As Integer
Dim ws1 As Worksheet: Set ws1 = Sheets("UI")
Dim ws2 As Worksheet: Set ws2 = Sheets("Output")
For i = 2 To ws1.Range("B999").End(xlUp).Row
If ws1.Cells(i, 2) = "1" Then
ws1.Rows(i).Copy
ws2.Rows(ws2.Cells(ws2.Rows.Count, 2).End(xlUp).Row + 1).PasteSpecial Paste:=xlPasteValues
End If
Next i
End Sub
Copy Values of Rows with Criteria
It's not quite nifty, but efficient.
Adjust the values in the constants section.
The Code
Option Explicit
Private Sub CommandButton1_Click()
' Source
Const sName As String = "UI"
Const sFirstRow As Long = 2
Const Criteria As String = "1" ' 'Const Criteria as long = 1'?
' Destination
Const dName As String = "Output"
Const dCell As String = "A2"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook
' Define Source Range (assuming 'UsedRange' starts in cell 'A1').
Dim rg As Range: Set rg = wb.Worksheets(sName).UsedRange
' Write values from Source Range to Data Array.
Dim Data As Variant: Data = rg.Value ' assuming 'rg' has at least two cells
Dim cCount As Long: cCount = UBound(Data, 2)
' Declare additional variables.
Dim cValue As Variant
Dim i As Long, j As Long, k As Long
' Loop and write matching values to the beginning of Data Array.
For i = sFirstRow To UBound(Data, 1)
cValue = Data(i, 2)
If Not IsError(cValue) Then
If cValue = Criteria Then
k = k + 1
For j = 1 To cCount
Data(k, j) = Data(i, j)
Next j
End If
End If
Next i
' Write matching values from Data Array to Destination Range.
If k > 0 Then
With wb.Worksheets(dName).Range(dCell)
.Resize(.Worksheet.Rows.Count - .Row + 1, _
.Worksheet.Columns.Count - .Column + 1).ClearContents
.Resize(k, cCount).Value = Data
End With
MsgBox "Data transferred.", vbInformation, "Success"
Else
MsgBox "No matches found.", vbExclamation, "Fail?"
End If
End Sub
I'm trying to compare sheet1 "A" column values to sheet2 "E:E" column values and copy/paste the whole line of every match to sheet3. Please help me to complete this task. I'm very new to VBA.
Thank you very much in advance!
Sub DelDups_TwoLists()
Dim iListCount As Integer
Dim iCtr As Integer
' Turn off screen updating to speed up macro.
Application.ScreenUpdating = False
' Get count of records to search through (list that will be deleted).
iListCount = Sheets("sheet1").Cells(Rows.Count, "A").End(xlUp).Row
' Loop through the "master" list.
For Each x In Sheets("Sheet2").Range("E:E" & Sheets("Sheet1").Cells(Rows.Count, "A").End(xlUp).Row)
' Loop through all records in the second list.
For iCtr = iListCount To 1 Step -1
' Do comparison of next record.
' To specify a different column, change 1 to the column number.
If x.Value = Sheets("Sheet1").Cells(iCtr, 1).Value Then
' If match is true then delete row.
Sheets("Sheet1").Cells(iCtr, 1).EntireRow.Copy
Sheets("Sheet3").Select.Paste
End If
Next iCtr
Next
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
Sub DelDupsTwoLists()
Dim lastRowWs1 As Long, lastRowWs2 As Long
Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
Set ws1 = Worksheets(1)
Set ws2 = Worksheets(2)
Set ws3 = Worksheets(3)
lastRowWs1 = LastRow(ws1.Name, 1)
lastRowWs2 = LastRow(ws2.Name, 5) 'E = 5
Dim myCell1 As Range, myCell2 As Range
Dim ws1Range As Range, ws2Range As Range
Set ws1Range = ws1.Range(ws1.Cells(1, "A"), ws1.Cells(lastRowWs1, 1))
Set ws2Range = ws2.Range(ws2.Cells(1, "E"), ws2.Cells(lastRowWs2, 1))
Dim rangeToDelete As Range
For Each myCell1 In ws1Range
For Each myCell2 In ws2Range
If myCell1.Value = myCell2.Value Then
Dim lastRowWs3: lastRowWs3 = LastRow(ws3.Name, 1) + 1
myCell2.EntireRow.Copy Destination:=ws3.Cells(lastRowWs3, 1)
If Not rangeToDelete Is Nothing Then
Set rangeToDelete = Union(rangeToDelete, myCell2.EntireRow)
Else
Set rangeToDelete = myCell2.EntireRow
End If
End If
Next
Next
If Not rangeToDelete Is Nothing Then
Debug.Print "Deleting rangeToDelete - "; rangeToDelete.Address
rangeToDelete.Delete
End If
Debug.Print "Done!"
End Sub
Public Function LastRow(wsName As String, Optional columnToCheck As Long = 1) As Long
Dim ws As Worksheet
Set ws = Worksheets(wsName)
LastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row
End Function
Pretty much I rewrote the whole code from scratch. It pretty much uses the initial n2 complexity, but is rather faster than that, because the deletion of the rows in WorkSheet(2) is done in a single last step rangeToDelete.Delete, which saves a lot of time.
Pretty much, the code defines 2 ranges with which is works - ws1Range and ws2Range, using the LastRow function. Once it defines them, it starts looping through them and comparing them. Hence the n2 complexity. In case of equal values, the row is copied and the cell is added to the rangeToDelete.
Note - it will probably not work as "out of the box solution", but try to debug further with F8 and see what happens.
Additionally:
Using Integer is not a great idea in VBA.
"_" in the Sub name is used for Events in VBA, thus it is not a great idea to use it. (although it works)
How to avoid using Select in Excel VBA
Give this a try (see comments in code for more details):
Sub DelDups_TwoLists()
' Turn off screen updating to speed up macro.
Application.ScreenUpdating = False
With ActiveWorkbook
Dim wsSrc As Worksheet: Set wsSrc = .Sheets("Sheet1") 'declare and set the source worksheet
Dim wsDst As Worksheet: Set wsDst = .Sheets("Sheet3") 'declare and set the destination worksheet
Dim R1 As Long, R2 As Long, C As Long, lRow As Long, lCol As Long 'declare variables to use
With wsSrc
lCol = .Cells(1, Columns.Count).End(xlToLeft).Column 'get the last column value in the source sheet, at row 1, will reuse this laster
Dim arrData_1 As Variant: arrData_1 = .Range(.Cells(1, 1), .Cells(.Cells(Rows.Count, 1).End(xlUp).Row, 1)) 'declare and allocate the source data to an array
End With
With .Sheets("Sheet2")
Dim arrData_2 As Variant: arrData_2 = .Range("E1:E" & .Cells(Rows.Count, 1).End(xlUp).Row) 'declare and allocate the compare data to an array
End With
End With
With wsDst
For R1 = LBound(arrData_1) To UBound(arrData_1) 'for each row in the source data
For R2 = LBound(arrData_2) To UBound(arrData_2) 'for each row in the compare data
If arrData_1(R1, 2) = arrData_2(R2, 1) Then 'if there is a match
lRow = .Cells(Rows.Count, 1).End(xlUp).Row + 1 'get the last row in the destination sheet
.Range(.Cells(lRow, 1), .Cells(lRow, lCol)).Value = _
wsSrc.Range(wsSrc.Cells(R1, 1), wsSrc.Cells(R1, lCol)).Value 'allocate the matching values
Exit For 'exit early here if there is a match, go to next row to check
End If
Next R2
Next R1
End With
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub