Macro that clears duplicates - excel

I have this macro that loops through rows F and G, clearing a range if it finds a duplicate F&G combo.
Right now, if it finds a unique combo (say, F(1) G(2)), it will delete all of those combos.
How can I change this macro to purge every time it clears, so that it is only clearing duplicates directly below the original?
Thanks.
Sub clearDupsA()
Dim lngMyRow As Long
Dim lngMyCol As Long
Dim lngLastRow As Long
Dim objMyUniqueData As Object
Application.ScreenUpdating = False
lngLastRow = Range("F:G").Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).row
Set objMyUniqueData = CreateObject("Scripting.Dictionary")
For lngMyRow = 1 To lngLastRow
If objMyUniqueData.exists(CStr(Cells(lngMyRow, 6) & Cells(lngMyRow, 7))) = False Then
objMyUniqueData.Add CStr(Cells(lngMyRow, 6) & Cells(lngMyRow, 7)), Cells(lngMyRow, 6) & Cells(lngMyRow, 7)
Else
Range(Cells(lngMyRow, 6), Cells(lngMyRow, 7)).ClearContents
End If
Next lngMyRow
Set objMyUniqueData = Nothing
On Error GoTo ErrorHandler
ErrorHandler:
Exit Sub
End Sub

do not use a dictionary. Instead just use a variable that gets replaced when a new combo is found:
Sub clearDupsA()
Dim lngMyRow As Long
Dim lngMyCol As Long
Dim lngLastRow As Long
Dim tempHolder As String
Application.ScreenUpdating = False
With ActiveSheet 'Don't let vba determine the sheet
lngLastRow = Range("F:G").Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
For lngMyRow = 1 To lngLastRow
If tempHolder <> (CStr(.Cells(lngMyRow, 6) & .Cells(lngMyRow, 7))) Then
tempHolder = (CStr(.Cells(lngMyRow, 6) & .Cells(lngMyRow, 7)))
Else
.Range(.Cells(lngMyRow, 6), .Cells(lngMyRow, 7)).ClearContents
End If
Next lngMyRow
End With
End Sub

Clear Consecutive Duplicates
Sub clearDupsA()
Const FirstRowAddress As String = "F2:G2"
Const Delimiter As String = "|!|"
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
Dim rg As Range
Dim rCount As Long
With ws.Range(FirstRowAddress)
Dim lCell As Range: Set lCell = .Resize(ws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Sub ' no data
rCount = lCell.Row - .Row + 1
Set rg = .Resize(rCount)
End With
Dim Data() As Variant: Data = rg.Value
Dim r As Long
Dim OldString As String
Dim NewString As String
For r = 1 To rCount
NewString = CStr(Data(r, 1)) & Delimiter & CStr(Data(r, 2))
If StrComp(NewString, OldString, vbTextCompare) = 0 Then
Data(r, 1) = Empty
Data(r, 2) = Empty
Else
OldString = NewString
End If
Next r
rg.Value = Data
End Sub

Related

Search and Find

Instead of on column performing, I want on two adjacent columns
For example looking columns N:M side by side cells and compare with columns B:C side by side cells if matched then go on.
Sub Find()
Application.ScreenUpdating = False
Dim i As Long, lRow As Long, lRow1 As Long
Dim rgFound As Range
lRow = Cells(Rows.Count, "b").End(xlUp).Row
lRow1 = Cells(Rows.Count, "n").End(xlUp).Row
For i = 2 To lRow
Set rgFound = Range("n2", "n" & lRow1).Find(Cells(i, "b"), LookIn:=xlValues)
If rgFound Is Nothing Then
MsgBox "not found"
Else
Cells(i, "l") = rgFound.Offset(, 2).Value
End If
Next
Application.ScreenUpdating = True
End Sub
A VBA Lookup: Multiple Adjacent Columns
Sub LookupData()
Const SRC_FIRST_LOOKUP_ROW As String = "M2:N2"
Const SRC_RETURN_COL As String = "P"
Const DST_FIRST_LOOKUP_ROW As String = "B2:C2"
Const DST_RETURN_COL As String = "I"
Const COL_DELIMITER As String = "#"
Const NOT_FOUND_VALUE As Variant = ""
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
Dim lCell As Range, lrg As Range, rrg As Range, lData(), rData()
Dim r As Long, rCount As Long, lc As Long, lcCount As Long, rStr As String
' Source
With ws.Range(SRC_FIRST_LOOKUP_ROW)
Set lCell = .Resize(ws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Sub ' no data
lcCount = .Columns.Count
rCount = lCell.Row - .Row + 1
Set lrg = .Resize(rCount)
End With
lData = lrg.Value
Set rrg = lrg.EntireRow.Columns(SRC_RETURN_COL)
If rCount = 1 Then
ReDim rData(1 To 1, 1 To 1): rData(1, 1) = rrg.Value
Else
rData = rrg.Value
End If
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
For r = 1 To rCount
rStr = CStr(lData(r, 1))
For lc = 2 To lcCount
rStr = rStr & COL_DELIMITER & CStr(lData(r, lc))
Next lc
If Not dict.Exists(rStr) Then dict(rStr) = rData(r, 1)
Next r
' Destination
With ws.Range(DST_FIRST_LOOKUP_ROW)
Set lCell = .Resize(ws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Sub ' no data
rCount = lCell.Row - .Row + 1
Set lrg = .Resize(rCount)
End With
lData = lrg.Value
Set rrg = lrg.EntireRow.Columns(DST_RETURN_COL)
ReDim rData(1 To rCount, 1 To 1)
' Result
For r = 1 To rCount
rStr = CStr(lData(r, 1))
For lc = 2 To lcCount
rStr = rStr & COL_DELIMITER & CStr(lData(r, lc))
Next lc
If dict.Exists(rStr) Then
rData(r, 1) = dict(rStr)
Else
rData(r, 1) = NOT_FOUND_VALUE
End If
Next r
rrg.Value = rData
' Inform.
MsgBox "Data lookup has finished.", vbInformation
End Sub

How to speed data copy from sheet1 to Other sheets by using Arrays, Excel vba?

I have workbook with three sheets.
I copy data from sheet1 to sheet2 & sheet3 depend on specific condition on sheet1, value = "Yes" on columns T or U.
The below code works fine using for Loop, but it is slow.
Now I transferred all data of sheet1 to array .
MyArray = Sheet1.Range("A3:U" & LastRow).Value2
is it possible to copy data from this array (by condition if specific value on it) to the other sheets .
I am new to vba , so any help will be appreciated .
Sub Copy_Data_On_Condition()
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim LastRow As Long
Dim ris_column As Range
Dim cell As Object
Dim DestRng As Range
Dim MyArray() As Variant
LastRow = Sheet1.Cells(Rows.count, 1).End(xlUp).Row
MyArray = Sheet1.Range("A3:U" & LastRow).Value2
Set ris_column = Sheet1.Range("T3:T" & LastRow)
For Each cell In ris_column
If cell.value = "Yes" Then
Set DestRng = Sheet2.Range("A" & Rows.count).End(xlUp).Offset(1, 0)
cell.EntireRow.Copy DestRng
End If
Next cell
Set ris_column = Sheet1.Range("U3:U" & LastRow)
For Each cell In ris_column
If cell.value = "Yes" Then
Set DestRng = Sheet3.Range("A" & Rows.count).End(xlUp).Offset(1, 0)
cell.EntireRow.Copy DestRng
End If
Next cell
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Update: Both two answers works perfectly , I tested on a sheet with total 2180 rows and copied rows about 1200. "FaneDure" Code takes about 4 second to finish and "Super Symmetry" code takes 0.07 of second which is significantly faster .
Please, try the next code:
Sub Copy_Data_On_Condition()
Dim sh1 As Worksheet, sh2 As Worksheet, sh3 As Worksheet, LastRow As Long
Dim arr_column, rngT As Range, rngU As Range, i As Long, lastCol As Long
Set sh1 = Sheet1: Set sh2 = Sheet2: Set sh3 = Sheet3 'only to make the code more compact
LastRow = sh1.cells(rows.count, 1).End(xlUp).row 'last row in A:A column
lastCol = sh1.UsedRange.Columns.count 'last column of Sheet1, to avoid copying the whole row
arr_column = sh1.Range("T3:U" & LastRow).Value2 'put in an array the columns to be processed against "Yes" string
'process both columns in the same iteration to make code faster
For i = 1 To UBound(arr_column) 'iterate between the array rows and process the columns values
If arr_column(i, 1) = "Yes" Then 'finding a match in column T:T:
If rngT Is Nothing Then 'if the rngT keeping the range to be copied is not Set (yet)
Set rngT = sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol)) 'the range is Set by the used range suitable row
Else
Set rngT = Union(rngT, sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol))) 'add the suitable row to the existing range
End If
End If
If arr_column(i, 2) = "Yes" Then 'finding a match in column U:U:
If rngU Is Nothing Then 'if the rngU keeping the range to be copied is not Set (yet)
Set rngU = sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol)) 'the range is Set by the used range suitable row
Else
Set rngU = Union(rngU, sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol))) 'add the suitable row to the existing range
End If
End If
Next i
If Not rngT Is Nothing Then 'if rngT has been set (it contains at least a row), copy it in Sheet2
rngT.Copy Destination:=sh2.Range("A" & sh2.rows.count).End(xlUp).Offset(1) 'copy the range at once
End If
If Not rngU Is Nothing Then 'if rngU has been set (it contains at least a row), copy it in Sheet3
rngU.Copy Destination:=sh3.Range("A" & sh3.rows.count).End(xlUp).Offset(1) 'copy the range at once
End If
End Sub
Because a direct autofilter is not an option, processing the array in memory should give you the fastest result as it minimises the interaction of VBA with the excel application. I believe the following should make your code significantly faster:
Sub Copy_Data_On_Condition()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim srcData As Variant
Dim sht2Data() As Variant
Dim sht2Rows As Long
Dim sht2CriteriaCol As Long: sht2CriteriaCol = 20 'T
Dim sht3Data() As Variant
Dim sht3Rows As Long
Dim sht3CriteriaCol As Long: sht3CriteriaCol = 21 'U
Dim outputCols As Long
Dim i As Long, j As Long
With Sheet1
srcData = .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row).Value
End With
outputCols = UBound(srcData, 2)
For i = LBound(srcData) To UBound(srcData)
If srcData(i, sht2CriteriaCol) = "Yes" Then
sht2Rows = sht2Rows + 1
ReDim Preserve sht2Data(1 To outputCols, 1 To sht2Rows)
For j = 1 To outputCols
sht2Data(j, sht2Rows) = srcData(i, j)
Next j
End If
If srcData(i, sht3CriteriaCol) = "Yes" Then
sht3Rows = sht3Rows + 1
ReDim Preserve sht3Data(1 To outputCols, 1 To sht3Rows)
For j = 1 To outputCols
sht3Data(j, sht3Rows) = srcData(i, j)
Next j
End If
Next i
If sht2Rows > 0 Then
Sheet2.Cells(Rows.Count, "A").End(xlUp).Offset(1, 0).Resize(sht2Rows, outputCols).Value = WorksheetFunction.Transpose(sht2Data)
End If
If sht3Rows > 0 Then
Sheet3.Cells(Rows.Count, "A").End(xlUp).Offset(1, 0).Resize(sht3Rows, outputCols).Value = WorksheetFunction.Transpose(sht3Data)
End If
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox "Time taken: " & Format(Timer - dStart, "0.000s")
End Sub
Another fast option is to add a dummy sheet (if possible), use autofilter then delete the dummy worksheet. This is very fast and the code is very simple:
Sub Copy_Data_On_Condition2()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Sheet1.Copy After:=Sheet1
With ActiveSheet
With .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20, Criteria1:="Yes"
.Copy Destination:=Sheet2.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20
.Rows(1).Offset(-1, 0).AutoFilter Field:=21, Criteria1:="Yes"
.Copy Destination:=Sheet3.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.AutoFilter
End With
.Delete
End With
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox Format(Timer - dStart, "0.000")
End Sub
Edit: (following comment and file share)
Your worksheet is protected but without password. Therefore, you can actually do autfilter in place without having to add a new dummy sheet. Your autfilter becomes:
Sub Copy_Data_On_Condition2()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.DisplayAlerts = False
' Check first if there's autfilter
If Sheet1.AutoFilterMode Then Sheet1.AutoFilter.ShowAllData
With Sheet2
If .AutoFilterMode Then .AutoFilter.ShowAllData
.Rows("4:" & .Rows.Count).ClearContents
End With
With Sheet3
If .AutoFilterMode Then .AutoFilter.ShowAllData
.Rows("4:" & .Rows.Count).ClearContents
End With
'=========== Super Symmetry Code _ Auto Filter
With Sheet1
.Unprotect
With .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20, Criteria1:="Yes"
.Copy Destination:=Sheet2.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20
.Rows(1).Offset(-1, 0).AutoFilter Field:=21, Criteria1:="Yes"
.Copy Destination:=Sheet3.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
End With
.Protect
End With
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox Format(Timer - dStart, "0.000")
End Sub
Autofilter is your best friend here if and when your data grows.
Copy Filtered Data
In this solution, it is assumed that you always want to start your resulting data in a given cell (dFirst) removing the previous contents.
Option Explicit
Sub CopyData()
Const sFirst As String = "A3"
Dim sCols As Variant: sCols = Array(20, 21)
Dim sCriteria As Variant: sCriteria = Array("Yes", "Yes")
Dim dFirst As Variant: dFirst = Array("A3", "A3")
Dim AutoFitColumns As Variant: AutoFitColumns = Array(True, True)
Dim sws As Worksheet: Set sws = Sheet1
Dim dws As Variant: dws = Array(Sheet2, Sheet3)
Dim srg As Range: Set srg = RefRange(sws.Range(sFirst))
If srg Is Nothing Then Exit Sub
Dim dData As Variant
Dim n As Long
For n = LBound(dws) To UBound(dws)
dData = GetCriteriaRows(srg, sCriteria(n), sCols(n))
If Not IsEmpty(dData) Then
WriteData dData, dws(n).Range(dFirst(n)), AutoFitColumns(n)
End If
Next n
End Sub
' Creates a reference to the range from a given first cell (range)
' to the cell at the intersection of the last non-empty row
' and the last non-empty column.
Function RefRange( _
ByVal FirstCellRange As Range) _
As Range
If FirstCellRange Is Nothing Then Exit Function
With FirstCellRange.Cells(1)
Dim rg As Range
Set rg = .Resize(.Worksheet.Rows.Count - .Row + 1, _
.Worksheet.Columns.Count - .Column + 1)
Dim lCell As Range
Set lCell = rg.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Function
Dim lRow As Long: lRow = lCell.Row
Set lCell = rg.Find("*", , , , xlByColumns, xlPrevious)
Set RefRange = .Resize(lRow - .Row + 1, lCell.Column - .Column + 1)
End With
End Function
' Returns a 2D one-based array containing the rows with matching criteria
' in a given column.
Function GetCriteriaRows( _
ByVal srg As Range, _
ByVal CriteriaString As String, _
Optional ByVal CriteriaColumn As Long = 1) _
As Variant
If srg Is Nothing Then Exit Function
If Len(CriteriaString) = 0 Then Exit Function
If CriteriaColumn < 0 Then Exit Function
Dim drCount As Long: drCount = Application.CountIf(srg, CriteriaString)
If drCount = 0 Then Exit Function
Dim srCount As Long: srCount = srg.Rows.Count
Dim cCount As Long: cCount = srg.Columns.Count
If CriteriaColumn > cCount Then Exit Function
Dim sData As Variant
If srCount + cCount = 2 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
Else
sData = srg.Value
End If
Dim dData As Variant: ReDim dData(1 To drCount, 1 To cCount)
Dim cValue As Variant
Dim r As Long, c As Long, n As Long
For r = 1 To srCount
cValue = CStr(sData(r, CriteriaColumn))
If cValue = CriteriaString Then
n = n + 1
For c = 1 To cCount
dData(n, c) = sData(r, c)
Next c
End If
Next r
GetCriteriaRows = dData
End Function
' Writes the values from a 2D one-based array to a range.
Sub WriteData( _
ByVal Data As Variant, _
ByVal FirstCellRange As Range, _
Optional ByVal AutoFitColumns As Boolean = False)
If FirstCellRange Is Nothing Then Exit Sub
If IsEmpty(Data) Then Exit Sub
Dim srCount As Long: srCount = UBound(Data, 1)
Dim scCount As Long: scCount = UBound(Data, 2)
Dim DoesFit As Boolean
Dim DoesNotFitExactly As Boolean
With FirstCellRange.Cells(1)
If .Worksheet.Columns.Count - .Column + 1 >= scCount Then
Select Case .Worksheet.Rows.Count - .Row + 1
Case srCount
DoesFit = True
Case Is > srCount
DoesFit = True
DoesNotFitExactly = True
End Select
End If
If DoesFit Then
Dim drg As Range: Set drg = .Resize(srCount, scCount)
drg.Value = Data
If DoesNotFitExactly Then
drg.Resize(.Worksheet.Rows.Count - .Row - srCount + 1) _
.Offset(srCount).ClearContents
End If
If AutoFitColumns Then
drg.EntireColumn.AutoFit
End If
End If
End With
End Sub
' Returns a 2D one-based array containing the values of a range
' (Not used because it is incorporated in 'GetCriteriaRows').
Function GetRange( _
ByVal rg As Range) _
As Variant
If rg Is Nothing Then Exit Function
Dim Data As Variant
If rg.Rows.Count + rg.Columns.Count = 2 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
Else
Data = rg.Value
End If
GetRange = Data
End Function
If you don't want to consider autofilter option.
Option Explicit
Sub Copy_Data_On_Condition()
'_____________________________________________________________
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
'_____________________________________________________________
Dim arr, findT As Range, findU As Range, arrStr As String, i As Long, j As Long
Dim LastRow As Long, ColT As Range, ColU As Range, k As Long, n As Long
LastRow = Sheet1.Cells(Sheet1.Rows.Count, 1).End(xlUp).Row
k = 3000
For j = 2 To LastRow Step WorksheetFunction.Min(LastRow, k)
'_____________________________________________________________
'Evaluate Column T for "Yes" and create range findT
Set ColT = Sheet1.Range("T" & j + 1 & ":T" & WorksheetFunction.Min(j + k, LastRow))
arr = Evaluate("Transpose(IF((" & ColT.Address & "=" & """YES""" & ")," & _
"""A""" & "& ROW(" & ColT.Address & ") &" & _
""":U""" & "& ROW(" & ColT.Address & "),""0""))")
arrStr = Replace(Join(arr, ","), ",0", "")
If Left(arrStr, 2) = "0," Then
arrStr = Right(arrStr, Len(arrStr) - 2)
End If
For n = 15 To Len(arrStr) - Len(Replace(arrStr, ",", "", , , vbTextCompare)) Step 15
arrStr = WorksheetFunction.Substitute(arrStr, ",", "|", n)
Next n
arr = Split(arrStr, "|")
For n = 0 To UBound(arr)
If findT Is Nothing Then
'arr = Split(arrStr, "|")
Set findT = Evaluate(arr(n))
Else
Set findT = Union(Evaluate(arr(n)), findT)
End If
Next n
Debug.Print findT.Cells.Count
'_____________________________________________________________
'Evaluate Column U for "Yes" and create range findU
Set ColU = Sheet1.Range("U" & j + 1 & ":U" & WorksheetFunction.Min(j + k, LastRow))
arr = Evaluate("Transpose(IF((" & ColU.Address & "=" & """YES""" & ")," & _
"""A""" & "& ROW(" & ColU.Address & ") &" & _
""":U""" & "& ROW(" & ColU.Address & "),""0""))")
arrStr = Replace(Join(arr, ","), ",0", "")
If Left(arrStr, 2) = "0," Then
arrStr = Right(arrStr, Len(arrStr) - 2)
End If
For n = 15 To Len(arrStr) - Len(Replace(arrStr, ",", "", , , vbTextCompare)) Step 15
arrStr = WorksheetFunction.Substitute(arrStr, ",", "|")
Next n
arr = Split(arrStr, "|")
For n = 0 To UBound(arr)
If findU Is Nothing Then
'arr = Split(arrStr, "|")
Set findU = Evaluate(arr(n))
Else
Set findU = Union(Evaluate(arr(n)), findU)
End If
Next n
Debug.Print findU.Cells.Count
'_____________________________________________________________
Next j
findT.Copy Sheet2.Range("A" & Sheet2.Rows.Count).End(xlUp).Offset(1)
findU.Copy Sheet3.Range("A" & Sheet3.Rows.Count).End(xlUp).Offset(1)
'_____________________________________________________________
SecondsElapsed = Round(Timer - StartTime, 2)
Debug.Print "This code ran successfully in " & SecondsElapsed & " seconds"
End Sub

Copy columns from one worksheet and append them to a list on another

I am trying to copy 2 separate columns from SHEET 1, Column B and D, and paste them to SHEET 2 but append them to data that is already present on that sheet in columns A & B.
Ultimately, I have a macro that has the user open an excel file and paste that information onto SHEET 1. Throughout the day, the user must rerun the macro and I lose that information on SHEET 1. I am looking to save the initial data from SHEET 1 to SHEET 2 to create a running list of data for that day but I am struggling to figure out the VBA. Each day starts with a new document.
I want to input this code before the "Closed OB" ClearContents code.
Sub Get_Data_From_File()
Dim FileToOpen As Variant
Dim OpenBook As Workbook
Dim sourceSheet As Worksheet
Set sourceSheet = ActiveSheet
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.Calculation = xlCalculationManual
Sheets("Closed OB").Visible = True
Sheets("Temp Closed").Visible = True
Sheets("Closed OB").Select
Range("A:J").ClearContents
FileToOpen = Application.GetOpenFilename(Title:="Browse for your File & Import Range", FileFilter:="Excel Files (*.xls*),*xls*")
If FileToOpen <> False Then
Set OpenBook = Application.Workbooks.Open(FileToOpen)
OpenBook.Sheets(1).Range("A4:G1000").Copy
ThisWorkbook.Worksheets(3).Range("A1:G1000").Value = OpenBook.Sheets(1).Range("A4:G1000").Value
OpenBook.Sheets(1).Range("H4:H1000").Copy
ThisWorkbook.Worksheets(3).Range("J1:J1000").Value = OpenBook.Sheets(1).Range("H4:H1000").Value
OpenBook.Sheets(2).Range("A4:M1000").Copy
ThisWorkbook.Worksheets(4).Range("A2:R998").Value = OpenBook.Sheets(2).Range("A4:M1000").Value
OpenBook.Close False
End If
ThisWorkbook.Worksheets("Closed OB").Range("G1").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.TextToColumns Destination:=Range("G1"), DataType:=xlDelimited, _
TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=True, Tab:=False, _
Semicolon:=False, Comma:=False, Space:=True, Other:=False, FieldInfo _
:=Array(Array(1, 1), Array(2, 1), Array(3, 1)), TrailingMinusNumbers:=True
LastRow = Sheets(4).UsedRange.SpecialCells(xlCellTypeLastCell).Row
Sheets("Temp Closed").Select
With Range("D2:D" & LastRow)
.NumberFormat = General
.Value = .Value
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End With
Sheets("Closed OB").Visible = False
Sheets("Temp Closed").Visible = False
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Call sourceSheet.Activate
End Sub
Backup Columns to Another Worksheet
Adjust the values in the constants section.
You only run the first procedure, the rest of them is being called by it.
Sub BackupColumns()
Const sName As String = "Sheet1"
Const sColsList As String = "B,D"
Const sfRow As Long = 2
Const dName As String = "Sheet2"
Const dInit As String = "A2"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sCols() As String: sCols = Split(sColsList, ",")
Dim sColsUpper As Long: sColsUpper = UBound(sCols)
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim slRow As Long
Dim clRow As Long
Dim n As Long
For n = 0 To sColsUpper
clRow = GetLastRowInOneColumn(sws.Cells(sfRow, sCols(n)))
If clRow > slRow Then
slRow = clRow
End If
Next n
If slRow = 0 Then
MsgBox "No data found.", vbExclamation, "Backup Columns"
Exit Sub
End If
Dim rCount As Long: rCount = slRow - sfRow + 1
Dim srg As Range
Dim sJData As Variant: ReDim sJData(0 To sColsUpper)
For n = 0 To sColsUpper
Set srg = sws.Cells(sfRow, sCols(n)).Resize(rCount)
sJData(n) = GetColumn(srg)
Next n
Dim dData As Variant: dData = GetEqualJaggedColumns(sJData)
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim diCell As Range: Set diCell = dws.Range(dInit)
Dim dCell As Range: Set dCell = diCell
Dim dlRow As Long
clRow = 0
For n = 0 To sColsUpper
clRow = GetLastRowInOneColumn(dCell)
If clRow > dlRow Then
dlRow = clRow
End If
Set dCell = dCell.Offset(, 1)
Next n
Dim dfCell As Range
If dlRow < diCell.Row Then
Set dfCell = diCell
Else
Set dfCell = dws.Cells(dlRow + 1, diCell.Column)
End If
Dim wasWritten As Boolean
wasWritten = writeDataSimple(dfCell, dData, False)
If wasWritten Then
MsgBox "Data succesfully written.", vbInformation, "Backup Columns"
Else
MsgBox "Something went wrong.", vbCritical, "Backup Columns"
End If
End Sub
Function GetLastRowInOneColumn( _
ByVal FirstCellRange As Range) _
As Long
If FirstCellRange Is Nothing Then Exit Function
Dim lCell As Range
With FirstCellRange.Cells(1)
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Function
End With
GetLastRowInOneColumn = lCell.Row
End Function
Function GetColumn( _
ByVal rg As Range) _
As Variant
If rg Is Nothing Then Exit Function
Dim Data As Variant
With rg.Columns(1)
Dim rCount As Long: rCount = rg.Rows.Count
If rCount = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
Else
Data = rg.Value
End If
End With
GetColumn = Data
End Function
Function GetEqualJaggedColumns( _
ByVal sJData As Variant) _
As Variant
If IsEmpty(sJData) Then Exit Function
Dim dasfa As Variant: dasfa = sJData(LBound(sJData))
Dim rCount As Long: rCount = UBound(sJData(LBound(sJData)), 1)
Dim cOffset As Long: cOffset = LBound(sJData) - 1
Dim cCount As Long: cCount = UBound(sJData) - cOffset
Dim dData As Variant: ReDim dData(1 To rCount, 1 To cCount)
Dim r As Long, c As Long
For c = 1 To cCount
For r = 1 To rCount
dData(r, c) = sJData(c + cOffset)(r, 1)
Next r
Next c
GetEqualJaggedColumns = dData
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes the values from a given 2D one-based array to a range
' defined by its given first cell (range) and the size
' of the array. Optionally (by default), clears the contents
' of the cells below the resulting range.
' Remarks: It's a method written as a function to return a success boolean.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function writeDataSimple( _
ByVal FirstCellRange As Range, _
ByVal Data As Variant, _
Optional ByVal doClearContentsBelow As Boolean = True) _
As Boolean ' This is a method.
On Error GoTo ClearError
If FirstCellRange Is Nothing Then Exit Function
If LBound(Data, 1) <> 1 Then Exit Function
If LBound(Data, 2) <> 1 Then Exit Function
Dim rCount As Long: rCount = UBound(Data, 1)
Dim cCount As Long: cCount = UBound(Data, 2)
With FirstCellRange
Dim wsrCount As Long
Dim wscCount As Long
With .Worksheet
wsrCount = .Rows.Count
wscCount = .Columns.Count
End With
With .Cells(1)
If wsrCount - rCount + 1 < .Row Then Exit Function
If wscCount - cCount + 1 < .Column Then Exit Function
.Resize(rCount, cCount).Value = Data
If doClearContentsBelow Then
.Resize(wsrCount - .Row - rCount + 1, cCount) _
.Offset(rCount).ClearContents
End If
writeDataSimple = True
End With
End With
ProcExit:
Exit Function
ClearError:
Resume ProcExit
End Function

Macro/VBA: highlight rows based on 2 conditions

Objective is to highlight rows that meet two different conditions:
If column A is equal to the previous workday (taking into consideration of holidays mentioned in the Reference sheet)
If column B is not equal to "AA"
I have the following code, but am unable to get appropriate rows highlighted (no rows get highlighted due to condition #1 not being met):
Sub code()
Dim lrow As Long
lrow = Cells(Rows.Count, "A").End(xlUp).Row
For i = 2 To lrow
If Cells(i, "A").Value = "=WORKDAY(today(),-1,Reference!$A$2:$A$12)" And Cells(i, "B").Value <> "AA" Then Cells(i, 1).EntireRow.Interior.ColorIndex = 6
Next i
End Sub
You could try this:
Option Explicit
Sub code()
Dim i As Long, lrow As Long
Dim objRangeHolidays As Range
Set objRangeHolidays = Worksheets("Reference").Range("$A$2", "$A$12")
lrow = Cells(rows.Count, "A").End(xlUp).row
For i = 2 To lrow
If CDate(Cells(i, "A").Value) = CDate(Application.WorksheetFunction.WorkDay(Date, -1, objRangeHolidays)) And Cells(i, "B").Value <> "AA" Then
Cells(i, 1).EntireRow.Interior.ColorIndex = 6
End If
Next i
Set objRangeHolidays = Nothing
End Sub
Your original code does not work as "=WORKDAY(today(),-1,Reference!$A$2:$A$12)" is a literal string on VBA, not a function call.
We use CDate() function to make our cell values comparable with WorksheetFunction.Workday() function.
WorksheetFunction.Today() is the same as Date() in VBA.
objRangeHolidays holds holidays defined in Reference sheet.
This is my test result:
Highlight Entire Rows
Adjust the values in the constants section.
Option Explicit
Sub highlightPreviousWorkday()
' Source
Const sName As String = "Sheet1"
Const sFirst As String = "A2"
Const sCritCol As String = "B"
Const sCriteria As String = "AA"
Const sColorIndex As Long = 6
' Holiday
Const hName As String = "Reference"
Const hFirst As String = "A2"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
wb.Activate ' `Evaluate` will fail if not active.
' Source
Dim srg As Range
With wb.Worksheets(sName).Range(sFirst)
Dim slCell As Range
Set slCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If slCell Is Nothing Then Exit Sub
Set srg = .Resize(slCell.Row - .Row + 1)
End With
' Holiday
Dim Holiday As String
With wb.Worksheets(hName).Range(hFirst)
Dim hlCell As Range
Set hlCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If Not hlCell Is Nothing Then
Holiday = ",'" & hName & "'!" _
& .Resize(hlCell.Row - .Row + 1).Address
End If
End With
' Evaluation
Dim evDate As Variant
evDate = Evaluate("WORKDAY(TODAY(),-1" & Holiday & ")")
' Combine
Dim drg As Range
If VarType(evDate) = vbDouble Then
Dim sCell As Range
Dim sValue As Variant
Dim sString As String
For Each sCell In srg.Cells
sValue = sCell.Value
If VarType(sValue) = vbDate Then
If CDbl(sValue) = evDate Then
sString = CStr(sCell.EntireRow.Columns(sCritCol).Value)
If sString <> sCriteria Then
Set drg = getCombinedRange(drg, sCell)
End If
End If
End If
Next sCell
End If
' Color
Application.ScreenUpdating = False
srg.EntireRow.Interior.ColorIndex = xlNone
If Not drg Is Nothing Then
drg.EntireRow.Interior.ColorIndex = sColorIndex
End If
Application.ScreenUpdating = True
End Sub
Function getCombinedRange( _
ByVal BuiltRange As Range, _
ByVal AddRange As Range) _
As Range
If BuiltRange Is Nothing Then
Set getCombinedRange = AddRange
Else
Set getCombinedRange = Union(BuiltRange, AddRange)
End If
End Function

How to copy Excel values across?

I need to copy the data in table 1 to a new sheet and format it like
on table 2.
I am using below function which does half of the job.
Function csvRange(myRange As Range)
Dim csvRangeOutput
Dim entry as variant
For Each entry In myRange
If Not IsEmpty(entry.Value) Then
csvRangeOutput = csvRangeOutput & entry.Value & ","
End If
Next
csvRange = Left(csvRangeOutput, Len(csvRangeOutput) - 1)
End Function
Join Data
JoinData is the recommended solution with some 'error handling'.
JoinDataShort is the same without any error handling.
Adjust the values in the constants section before using them.
Both of the mentioned procedures use the remaining three procedures.
The Code
Option Explicit
Sub joinData()
' Source
Const srcName As String = "Sheet1"
Const srcCols As String = "A:B"
Const srcLastRowCol As Long = 2
Const srcFirstRow As Long = 1
' Destination
Const dstName As String = "Sheet1"
Const dstFirst As String = "D1"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook
' Source
Dim rng As Range
With wb.Worksheets(srcName)
Set rng = defRangeFrLrc(.Columns(srcCols), srcFirstRow, srcLastRowCol)
End With
If rng Is Nothing Then
Exit Sub
End If
' Data
Dim Data As Variant: Data = getDelimited(rng)
If IsEmpty(Data) Then
Exit Sub
End If
' Destination
Dim isWritten As Boolean
With wb.Worksheets(dstName)
isWritten = writeDataToRange(.Range(dstFirst), Data, True)
End With
' Information
If isWritten Then
MsgBox "Data transferred.", vbInformation, "Success"
Else
MsgBox "An error occurred.", vbCritical, "Fail"
End If
End Sub
' Compare with 'joinData' to understand what can go wrong.
Sub joinDataShort()
' Source
Const srcName As String = "Sheet1"
Const srcCols As String = "A:B"
Const srcLastRowCol As Long = 2
Const srcFirstRow As Long = 1
' Destination
Const dstName As String = "Sheet1"
Const dstFirst As String = "D1"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook
' Source
Dim rng As Range
With wb.Worksheets(srcName)
Set rng = defRangeFrLrc(.Columns(srcCols), srcFirstRow, srcLastRowCol)
End With
' Data
Dim Data As Variant: Data = getDelimited(rng)
' Destination
writeDataToRange wb.Worksheets(dstName).Range(dstFirst), Data, True
End Sub
Function defRangeFrLrc( _
rng As Range, _
Optional ByVal FirstRow As Long = 1, _
Optional ByVal LastRowColumn As Long = 1) _
As Range
On Error GoTo clearError
If Not rng Is Nothing Then
Dim cel As Range
With rng
Set cel = .Columns(LastRowColumn) _
.Resize(.Rows.Count - FirstRow + 1).Offset(FirstRow - 1).Find( _
What:="*", _
LookIn:=xlFormulas, _
SearchDirection:=xlPrevious)
If Not cel Is Nothing Then
Set defRangeFrLrc = .Resize(cel.Row - FirstRow + 1) _
.Offset(FirstRow - 1)
End If
End With
End If
ProcExit:
Exit Function
clearError:
Resume ProcExit
End Function
Function getDelimited( _
rng As Range, _
Optional ByVal Delimiter As String = ",") _
As Variant
On Error GoTo clearError
' Arrays
With rng.Columns(1)
Dim rCount As Long
rCount = .Rows.Count - Application.CountBlank(.Offset)
Dim Data As Variant: Data = .Resize(, 2).Value
End With
Dim sCount As Long: sCount = UBound(Data, 1)
Dim Result() As String: ReDim Result(1 To rCount, 1 To 2)
' Headers
Result(1, 1) = Data(1, 1)
Result(1, 2) = Data(1, 2)
' Body
Dim arl As Object: Set arl = CreateObject("System.Collections.ArrayList")
Dim i As Long
Dim k As Long: k = 1
Dim cIndex As Long: cIndex = 1
Dim doAdd As Boolean
For i = 2 To sCount
If Len(Data(i, 1)) > 0 Then
If i > 2 Then
Result(cIndex, 2) = Join(arl.ToArray, Delimiter)
arl.Clear
End If
k = k + 1
cIndex = k
Result(k, 1) = Data(i, 1)
End If
arl.Add Data(i, 2)
Next i
Result(cIndex, 2) = Join(arl.ToArray, Delimiter)
' Result
getDelimited = Result
ProcExit:
Exit Function
clearError:
Resume ProcExit
End Function
Function writeDataToRange( _
FirstCellRange As Range, _
Data As Variant, _
Optional ByVal ClearToBottom As Boolean = False, _
Optional ByVal ClearWorksheet As Boolean = False) _
As Boolean
On Error GoTo clearError
With FirstCellRange.Resize(, UBound(Data, 2))
If ClearWorksheet Then
.Worksheet.Cells.Clear
Else
If ClearWorksheet Then
.Resize(.Worksheet.Rows.Count - .Row + 1).Clear
End If
End If
.Resize(UBound(Data, 1)).Value = Data
End With
writeDataToRange = True
ProcExit:
Exit Function
clearError:
Resume ProcExit
End Function
Here's a simple code which should work for you. See the comments and edit them to suit your needs.
Public Sub RearrangeData()
Dim rngSource As Range, rng As Range
Dim lngOutRow As Long
Application.ScreenUpdating = False
'\\ Set Source Range
Set rngSource = Range("A2:B" & Range("B" & Rows.Count).End(xlUp).Row)
lngOutRow = 1 '\\Define Output Row
For Each rng In rngSource.Columns(1).Cells
If Len(rng.Value) > 0 Then
lngOutRow = lngOutRow + 1
Range("C" & lngOutRow).Value = rng.Value '\\Define Output Column 1
End If
If Len(rng.Offset(0, 1).Value) > 0 Then
If Len(Range("D" & lngOutRow).Value) = 0 Then
Range("D" & lngOutRow).Value = rng.Offset(0, 1).Value '\\Define Output Column 2
Else
Range("D" & lngOutRow).Value = Range("D" & lngOutRow).Value & "," & rng.Offset(0, 1).Value '\\Define Output Column 2
End If
End If
Next rng
Application.ScreenUpdating = True
End Sub

Resources