Vba find duplicates and copy if none found - vba

I'm trying to add a vba code that looks in a column on sheet YTDFigures and sees if there is a duplicate in sheet EeeDetails. If there isn't then I want to copy the YTDFigures data and paste in a new sheet.
The code I've tried gets an error run time error 91 on the line FinName = Worksheets("EeeDetails").Range("A:A").Find(What:=SearchName, LookIn:=xlValues) I thought this would work as if a match isn't found the .Find function returns nothing.
Sub CheckMatch()
Application.ScreenUpdating = False
Dim SearchName As Range, SearchNames As Range
Dim Usdrws As Long
Dim row As Integer
Usdrws = Worksheets("YTDFigures").Range("A" & Rows.Count).End(xlUp).row
Set SearchNames = Worksheets("YTDFigures").Range("A2:A" & Usdrws)
For Each SearchName In SearchNames
row = Split(SearchName.Address, "$")(2)
FinName = Worksheets("EeeDetails").Range("A:A").Find(What:=SearchName, LookIn:=xlValues)
If FinName Is Nothing Then
Range("A" & row & ":S" & row).Copy
LastRow = Worksheets("Errors").Range("AA" & Rows.Count).End(xlUp).row + 1
Worksheets("Errors").Activate
Range("A" & LastRow).Select
Selection.PasteSpecial
Worksheets("EeeDetails").Activate
End If
Next
Application.ScreenUpdating = True
End Sub

You can place the raw data into an array, place the array on a temporary sheet, remove the duplicates, copy the data, then delete the temp sheet.
See below:
Sub CheckMatch()
Application.ScreenUpdating = False
Dim ws As Worksheet, tRows As Long
Set ws = ThisWorkbook.Worksheets(1)
Set RngA = ws.UsedRange.Columns("A")
tRows = ws.Rows(ws.Rows.Count).End(xlUp).row
Dim valA As Variant
valA = ws.Range(ws.Cells(1, 1), ws.Cells(tRows, 1)).Value
Dim tempWs As Worksheet
Set tempWs = ThisWorkbook.Worksheets.Add
tempWs.Name = "Temp1"
With tempWs
.Range(.Cells(1, 1), .Cells(tRows, 1)) = valA
With .UsedRange.Columns("A")
.RemoveDuplicates Columns:=1, Header:=xlYes
.Copy
End With
End With
' Do what you need to do with your copied data
Application.DisplayAlerts = False
tempWs.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
Edit:
I just tested this with sample data of over 10k rows, and it works in less than a half a second. It's very fast.

Related

Vba assistance for filtered randomizer

Hello guys I have the following code made to be a randomizer, however is not giving me the proper results that I expected, here are the results needed 
1- It doesn't matter which manager name do you put it always give you the same data no matter how many rows you have on the raw data
2- Also I need the randomizer to look for the current week number and if there's no data to go back to the previous week, this I don't know how to do it so any assistance will be amazing
At the end there are some images for more insight on the issue
Private Sub CommandButton1_Click()
Dim lr As Long, wks As Worksheet
Application.ScreenUpdating = False
Set wks = ActiveSheet
Sheets("Sheet2").Copy After:=Sheets(Sheets.Count)
With ActiveSheet
lr = .Cells(.Rows.Count, "A").End(xlUp).Row
.Range("A1:H" & lr).Sort key1:=.Range("G1"), Header:=xlYes
.Range("A1:H" & lr).AutoFilter Field:=7, Criteria1:="<>" &  
Sheets("Sheet1").Range("A2").Value
.Rows("1:" & lr).Delete Shift:=xlUp
lr = .Cells(.Rows.Count, "A").End(xlUp)
If lr > 5 Then
.Range("I2:I" & lr).Formula = "=RAND()"
.Calculate
.Range("I2:I" & lr).Value = .Range("I2:I" & lr).Value
wks.Range("A5:H9").Value = .Range("A1:H5").Value
Else
MsgBox "Please enter a valid login in order to continue"
End If
Application.DisplayAlerts = False
.Delete
Application.DisplayAlerts = True
End With
wks.Activate
wks.Range("A5:H9").Sort key1:=wks.Range("A5"), Header:=xlNo
Set wks = Nothing
Application.ScreenUpdating = True
End Sub
  [1]: https://i.stack.imgur.com/TvrgJ.png
  [2]: https://i.stack.imgur.com/msRXy.png
If I understand correctly, you are trying to pick 5 random records from the "Data" sheet and populate them in the table on the "Audit" sheet. You did not provide the sheet names so I've made some assumptions in my code, you can modify to fit.
This is just the Random Record Picker. You can filter the set you are picking from however you want before this runs.
Sub getRandomRecords()
Dim lastRow As Integer
Dim shAudit As Worksheet
Dim shData As Worksheet
Dim r As Integer
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim key As Variant
Set shAudit = ThisWorkbook.Sheets("Sheet1")
Set shData = ThisWorkbook.Sheets("Sheet2")
lastRow = shData.Range("A" & shData.Rows.Count).End(xlUp).Row
'Pick 5 random records with no repeats
Do Until dict.Count = 5
r = Application.WorksheetFunction.RandBetween(2, lastRow)
If Not dict.Exists(r) Then
dict.Add r, r
End If
Loop
r = 0
'Copy the randomly selected records to the audit tab
For Each key In dict.Keys
shData.Range("A1:H1").Offset(key - 1, 0).Copy shAudit.Range("A5:H5").Offset(r, 0)
r = r + 1
Next key
End Sub

Copy columns from multiple spreadsheets. Data moving up when column empty on spreadsheet

I have the below code. The code will go into each of the 17 workbooks and extract certain columns based on the columns headers name. This will repeat and add to the bottom of the master workbook, until the last one has been extracted.
Unfortunately, if there is nothing in one of the columns on one of the individual 17 workbooks, the data from the next workbook gets moved up in the cells. Is there anyway to sort this. I have added the code below.
Option Explicit
Sub CopyColumns()
Dim CopyFromPath As String, FileName As String
Dim CopyToWb As Workbook, wb As Workbook, CopyToWs As Worksheet
Dim lastRow As Long, NextRow As Long, lcol As Long, c As Long, sv As Integer
Dim ws As Worksheet
Dim myCol As Long
Dim myHeader As Range
r\"
Set CopyToWb = ActiveWorkbook
Set c).End(xlUp).Row
If lastRow = 1 Then GoTo nxt
Range(Cells(2, c), Cells(lastRow, c)).Copy
CopyToWs.Activate
Set myHeader = CopyToWs.Rows(1).Find(What:=.Cells(1, c).Value, Lookat:=xlWhole)
With CopyToWs
If Not myHeader Is Nothing Then
myCol = myHeader.Column
NextRow = .Cells(Rows.Count, myCol).End(xlUp).Row + 1
.Cells(NextRow, myCol).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Set myHeader = Nothing
End If
nxt:
End With
End If
Next c
wb.Close saveChanges:=False
End With
FileName = Dir
Loop
Application.ScreenUpdating = True
End Sub
Thank you in advance
Calculate NextRow only once per workbook, and then use it for all columns:
Do While Len(FileName) > 0
'Calculate the next row to be populated for all columns, based on the last
'used cell in column A
'(I used column A, but pick whatever destination column will always be
'populated in every workbook.)
With CopyToWs
NextRow = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
End With
'Process this workbook
Set wb = Workbooks.Open(CopyFromPath & FileName)
With wb.Sheets("Open Issue Actions")
lcol = .Cells(1, .Columns.Count).End(xlToLeft).Column
For c = 1 To lcol
'...
With CopyToWs
If Not myHeader Is Nothing Then
myCol = myHeader.Column
'NextRow = .Cells(Rows.Count, myCol).End(xlUp).Row + 1
.Cells(NextRow, myCol).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Set myHeader = Nothing
End If
End With
nxt:
'...
Actually you want one row per sheet. Nothing else. Nothing more. You do not even need to calculate it. You need to increment it lngRow = lngRow+1.
Try to use the following into your code:
Option Explicit
Sub CopyColumns()
Dim lngRow As Long: lngRow = 1
Do While Len(FileName) > 0
Set wb = Workbooks.Open(CopyFromPath & FileName)
With wb.Sheets("Open Issue Actions")
lngRow = lngRow + 1
With CopyToWs
If Not myHeader Is Nothing Then
myCol = myHeader.Column
.Cells(lngRow, myCol).PasteSpecial xlPasteValues
Set myHeader = Nothing
End If
End With
End With
wb.Close saveChanges:=False
Loop
Application.ScreenUpdating = True
End Sub
In the code you add/edit three things:
The line Dim lngRow as Long: lngRow=1 on top with the other Dim
lngRow = lngRow + 1 after the With wb.Sheets("Open Issue Actions")
the paste values should be like this .Cells(lngRow, myCol).PasteSpecial xlPasteValues
The whole code is here: https://pastebin.com/kXdzkGZ1
The idea is to have lngRow and to increment it for every WorkSheet that you open. And do not do anything else with it.
In general, your code can be optimized in some ways, if it works ok after the change, put it here for further ideas: https://codereview.stackexchange.com/

Filter data and copy values VBA

My code below is supposed to filter data in the wsData and then copy it into the wsTest worksheet after each other in column A. The code works except that it copies the values over each on the destination sheet rather then after each other. Any idea why?
Sub PrintReport()
Dim wbFeeReport As Workbook
Dim wsData As Worksheet
Dim wsForm As Worksheet
Dim wsTest As Worksheet
Dim FrRngCount As Range
Dim i As Integer
Dim k As Integer
Dim t As Integer
Dim s As Integer
Set wbFeeReport = Workbooks("FeExcForm.xlsm")
Set wsData = wbFeeReport.Worksheets("Data")
Set wsTest = wbFeeReport.Worksheets("Test")
wsTest.Cells.Clear
wsData.Activate
i = 1
For k = 1 To 2
With ActiveSheet
.AutoFilterMode = False
With Range("A1", Range("A" & Rows.Count).End(xlUp))
.AutoFilter 1, k
On Error Resume Next
.SpecialCells(xlCellTypeVisible).Copy Destination:=wsTest.Range("A" & i)
End With
i = wsTest.Range("A" & Rows.Count).End(xlUp)
.AutoFilterMode = False
End With
Next k
End Sub
As first point: if using a range with AutoFilter the copy will always exclude the hidden cells
With Range("A1", Range("A" & Rows.Count).End(xlUp))
.AutoFilter 1, k
.Copy wsTest.Range("A" & i)
End With
is all you need here.
Regarding your error: On Error Resume Next hides the error of i = wsTest.Range("A" & Rows.Count).End(xlUp) which would return a range rather than a numerical value.
i = wsTest.Range("A" & Rows.Count).End(xlUp).Row + 1
is your friend here :)
Everything together should look something like that:
Sub PrintReport()
Dim wbFeeReport As Workbook
Dim wsData As Worksheet
Dim wsForm As Worksheet
Dim wsTest As Worksheet
Dim FrRngCount As Range
Dim i As Integer
Dim k As Integer
Dim t As Integer
Dim s As Integer
Set wbFeeReport = Workbooks("FeExcForm.xlsm")
Set wsData = wbFeeReport.Worksheets("Data")
Set wsTest = wbFeeReport.Worksheets("Test")
wsTest.Cells.Clear
wsData.Activate
i = 1
For k = 1 To 2
With wsData
.AutoFilterMode = False
With .Range("A1", Range("A" & Rows.Count).End(xlUp))
.AutoFilter 1, k
.Copy wsTest.Range("A" & i)
End With
i = wsTest.Range("A" & Rows.Count).End(xlUp).Row + 1
.AutoFilterMode = False
End With
Next k
End Sub
EDIT: For excluding headers just change:
.Copy wsTest.Range("A" & i)
to:
If i = 1 Then .Copy wsTest.Range("A" & i) Else .Offset(1, 0).Copy wsTest.Range("A" & i)
and if you do not want any headers at all then directly use:
.Offset(1, 0).Copy wsTest.Range("A" & i)
But I havent tested it. Just tell me if you get any problems ;)

Data overlaps when merging multiple sheets

I have an Excel workbook which contains n sheets. I want to merge the data from each sheet to one single sheet. The header and data from the first sheet should be on top, the data from second sheet should be below it and so on. All the sheets have the same columns and headers structure. So, the header should appear only once i.e take header and data from first sheet and only data from remaining sheets. I have the following code:
Sub Combine()
'This macro will copy all rows from the first sheet
'(including headers)
'and on the next sheets will copy only the data
'(starting on row 2)
Dim i As Integer
Dim j As Long
Dim SheetCnt As Integer
Dim lstRow1 As Long
Dim lstRow2 As Long
Dim lstCol As Integer
Dim ws1 As Worksheet
With Application
.DisplayAlerts = False
.EnableEvents = False
.ScreenUpdating = False
End With
On Error Resume Next
'Delete the Target Sheet on the document (in case it exists)
Sheets("Target").Delete
'Count the number of sheets on the Workbook
SheetCnt = Worksheets.Count
'Add the Target Sheet
Sheets.Add after:=Worksheets(SheetCnt)
ActiveSheet.Name = "Target"
Set ws1 = Sheets("Target")
lstRow2 = 1
'Define the row where to start copying
'(first sheet will be row 1 to include headers)
j = 1
'Combine the sheets
For i = 1 To SheetCnt
Worksheets(i).Select
'check what is the last column with data
lstCol = ActiveSheet.Cells(1, ActiveSheet.Columns.Count).End(xlToLeft).Column
'check what is the last row with data
lstRow1 = ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlUp).Row
'Define the range to copy
Range("A2:G2" & j, Cells(lstRow1, lstCol)).Select
'Copy the data
Selection.Copy
ws1.Range("A2:G2" & lstRow2).PasteSpecial
Application.CutCopyMode = False
Selection.Offset(1, 0).Resize(Selection.Rows.Count - 1).Select
'Define the new last row on the Target sheet
lstRow2 = ws1.Cells(65535, "A").End(xlUp).Row + 1
'Define the row where to start copying
'(2nd sheet onwards will be row 2 to only get data)
j = 3
Next
With Application
.DisplayAlerts = True
.EnableEvents = True
.ScreenUpdating = True
End With
Sheets("Target").Select
Cells.EntireColumn.AutoFit
Range("A1").Select
End Sub
With this code, my data from all sheets is getting overlapped. I want the data to be one below the other.
It's overlapping because you don't increment the paste area on the Target sheet
To fix the problem offset the paste area correspondingly:
Sheet 1: copy 10 rows-paste -> increment paste start & end area by 10
Sheet 2: copy 15 rows-paste -> increment paste start & end area by 25: 10 + 15 and so on...
You can also replace this:
Sheets.Add after:=Worksheets(SheetCnt) 'Add the Target Sheet
ActiveSheet.Name = "Target"
Set ws1 = Sheets("Target")
with this:
Set ws1 = Sheets.Add(after:=Worksheets(SheetCnt)) 'Add the Target Sheet
ws1.Name = "Target"
If you eliminate all "Select" statements and refer to each object explicitly it will allow you to reduce code, and un-needed complexity
Here is my version:
Option Explicit
Public Sub Combine()
Const HEADR As Byte = 1
Dim i As Long, rngCurrent As Range
Dim ws As Worksheet, wsTarget As Worksheet
Dim lCol As Long, lCel As Range
Dim lRow As Long, toLRow As Long
With Application
.DisplayAlerts = False
.EnableEvents = False
.ScreenUpdating = False
End With
For Each ws In Worksheets 'Delete Target Sheet if it exists
With ws
If .Name = "Target" Then
.Delete
Exit For
End If
End With
Next
Set wsTarget = Worksheets.Add(After:=Worksheets(Worksheets.Count))
wsTarget.Name = "Target"
Set lCel = GetMaxCell(Worksheets(1).UsedRange)
If lCel.Row > 1 Then
With Worksheets(1)
'Expected: all sheets will have the same number of columns
lCol = lCel.Column
lRow = HEADR
toLRow = HEADR
.Range(.Cells(HEADR, 1), .Cells(HEADR, lCol)).Copy
With wsTarget
.Range(.Cells(HEADR, 1), .Cells(HEADR, lCol)).PasteSpecial xlPasteAll
End With
End With
For i = 1 To Worksheets.Count 'concatenate data ---------------------------
Set lCel = GetMaxCell(Worksheets(i).UsedRange)
If lCel.Row > 1 Then
With Worksheets(i)
If .Name <> "Target" Then 'exclude the Target
toLRow = toLRow + lRow 'last row on Target
lRow = lCel.Row 'last row on current
Set rngCurrent = .Range(.Cells(HEADR + 1, 1), _
.Cells(lRow, lCol))
lRow = lRow - HEADR
With wsTarget
.Range(.Cells(toLRow, 1), _
.Cells(toLRow + (lRow - HEADR), lCol)) = _
rngCurrent.Value
End With
End If
End With
End If
Next '--------------------------------------------------------------------
With wsTarget
.Columns.AutoFit
.Range("A1").Select
End With
With Application
.CutCopyMode = False
.DisplayAlerts = True
.EnableEvents = True
.ScreenUpdating = True
End With
End If
End Sub
Public Function GetMaxCell(Optional ByRef rng As Range = Nothing) As Range
'Returns the last cell containing a value, or A1 if Worksheet is empty
Const NONEMPTY As String = "*"
Dim lRow As Range, lCol As Range
If rng Is Nothing Then Set rng = Application.ActiveWorkbook.ActiveSheet.UsedRange
If WorksheetFunction.CountA(rng) = 0 Then
Set GetMaxCell = rng.Parent.Cells(1, 1)
Else
With rng
Set lRow = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByRows)
If Not lRow Is Nothing Then
Set lCol = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByColumns)
Set GetMaxCell = .Parent.Cells(lRow.Row, lCol.Column)
End If
End With
End If
End Function
'--------------------------------------------------------------------------------------
Offsetting the paste area is done by incrementing lRow and toLRow
Edit:
If you use this code and you want to transfer cell formatting for all data cells replace this section:
'copy data to Target sheet
With wsTarget
.Range(.Cells(toLRow, 1), .Cells(toLRow + (lRow - HEADR), lCol)) = _
rngCurrent.Value
End With
with this:
'copy data to Target sheet
rngCurrent.Copy
With wsTarget
With .Range(.Cells(toLRow, 1), .Cells(toLRow + (lRow - HEADR), lCol))
.PasteSpecial xlPasteAll
End With
End With
but it will become slower if you're processing a lot of sheets
EDIT: to show how to handle special cases
The above solution is more generic and dynamically detects the last column and row containing data
The number of columns (and rows) to be processed can be manually updated. For example, if your sheets contain 43 columns with data, and you want to exclude the last 2 columns, make the following change to the script:
Line
Set lCel = GetMaxCell(Worksheets(1).UsedRange)
changes to
Set lCel = Worksheets(1).UsedRange("D41")

Excel VBA copying rows using autofilter

Looking to copy rows from all sheets apart from my active sheet that meet a certain criteria in column J using VBA.
Not experienced in writing code in VBA so I have tried to frankenstein together the necessary parts from looking through other questions and answers;
below is the code I have written so far;
Sub CommandButton1_Click()
Dim lngLastRow As Long
Dim ws As Worksheet
Dim r As Long, c As Long
Dim wsRow As Long
Set Controlled = Sheets("Controlled") ' Set This to the Sheet name you want all Ok's going to
Worksheets("Controlled").Activate
r = ActiveSheet.Cells(Rows.Count, 2).End(x1up).Row
c = ActiveSheet.Cells(1, Columns.Count).End(x1ToLeft).Column
Range("J").AutoFilter
For Each ws In Worksheets
If ws.Name <> "Controlled" Then
ws.Activate
wsRow = ActiveSheet.Cells(Rows.Count, 2).End(x1up).Row + 1
Range("A" & r).AutoFilter Field:=10, Criteria1:="Y"
.Copy Controlled.Range("A3" & wsRow)
End If
Next ws
End If
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
End Sub
Where Controlled is the sheet I want the data to appear in from the other sheets, and all other sheets are searched to see if their column J meets the criteria="Y"
I won't need to copy over formatting as all Sheets will have the formatting exactly the same and if possible I want the rows that are copied over to start at row 3
Try this:
Option Explicit
Sub ConsolidateY()
Dim ws As Worksheet, wsCtrl As Worksheet
Dim lrow As Long, rng As Range
Set wsCtrl = Thisworkbook.Sheets("Controlled")
With Application
.ScreenUpdating = False
.DisplayAlerts = False
End With
For Each ws In Thisworkbook.Worksheets
If ws.Name = "Controlled" Then GoTo nextsheet
With ws
lrow = .Range("J" & .Rows.Count).End(xlUp).Row
.AutoFilterMode = False
Set rng = .Range("J1:J" & lrow).Find(what:="Y", after:=.Range("J" & lrow))
If rng Is Nothing Then GoTo nextsheet
.Range("J1:J" & lrow).AutoFilter Field:=1, Criteria1:="Y"
.Range("J1:J" & lrow).Offset(1,0).SpecialCells(xlCellTypeVisible).EntireRow.Copy
wsCtrl.Range("A" & wsCtrl.Rows.Count).End(xlUp).Offset(1,0).PasteSpecial xlPasteValues
.AutoFilterMode = False
Application.CutCopyMode = False
End With
nextsheet:
Next
With Application
.ScreenUpdating = True
.DisplayAlerts = True
End With
End Sub
I think this covers everything or most of your requirement.
Not tested though so I leave it to you.
If you come across with problems, let me know.