Even though I thought this would be a common question, I have searched and can't seem to find an answer.
I have a work schedule and I'd like to search the today's date and return all the names of those who are scheduled to work today. I built a code that works but it takes a long time to complete and is not 100% effective every time its run. I am sure there must be a better and faster way to do this but I have not yet been able to find one. I have it broken apart into two different codes. The first one finds the column where today's date is located and the second one gathers the names and places them onto the next sheet.
Here's the First Sub:
Sub GetDateRow_()
'//////Finds the row that has today's date and returns the value of that row to cell C34\\\\\\
Dim SearchMe As Integer
SearchMe = Sheets("Sheet1").Range("C33")
Set FindMe = Sheets("Sheet1").Range("C5:AD5").Find(What:=SearchMe, LookIn:=xlValues, LookAt:= _
xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False _
, SearchFormat:=False)
Sheets("Sheet1").Range("C34").Value = Cells(1, FindMe.Column)
End Sub
And the Second Sub:
Sub CopyScheduledToList()
'//////Searches Today's day Column from the schedule given by GetDateRow Sub & assimbles \\\\\\\
'////// the names of those who are scheduled to work today to a list on Page 2 \\\\\\\
Dim Ccount As Integer
Dim lngLoop As Long
Dim RowCount As Integer
Dim dShift As String
Dim cShift As String
Ccount = 1
dShift = "A63"
cShift = "TLA"
RowCount = Sheets("Sheet1").Range("C34").Value
lngLoop = 1
For lngLoop = 1 To Rows.count
If Cells(lngLoop, RowCount).Value = cShift Then Worksheets("Sheet2").Cells(1, 4).Value = Worksheets("Sheet1").Cells(lngLoop, 1).Value
'////// Get's the Team Leader and places name into column D on Page 2
If Cells(lngLoop, RowCount).Value = dShift Then Worksheets("Sheet2").Cells(Ccount, 1).Value = Worksheets("Sheet1").Cells(lngLoop, 1).Value
If Worksheets("Sheet2").Range("A" & Ccount).Value <> "" Then Ccount = Ccount + 1
'////// Places the name from the working list onto page 2 and adds a count so the next value found
'////// will go to the next empty row on Sheet 2
Next lngLoop
End Sub
Again, your help is greatly appreciated!
Your excessively long run times will be due to looping over 1048576 cells in your For lngLoop = 1 To Rows.count loop. That can be improved by just processing as far as the last cell that contains data in the applicable column.
The problem of it not always working correctly is almost certainly due to the fact that you have some Cells references which aren't qualified with the sheet that you want to use, and therefore they refer to the ActiveSheet.
Sub GetDateRow_()
'//////Finds the row that has today's date and returns the value of that row to cell C34\\\\\\
Dim SearchMe As Date
Dim FindMe As Range
With Worksheets("Sheet1")
SearchMe = .Range("C33").Value
Set FindMe = .Range("C5:AD5").Find(What:=SearchMe, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If FindMe Is Nothing Then
MsgBox "Date not found!"
Else
'I think this line
'.Range("C34").Value = .Cells(1, FindMe.Column).Value
'should be
.Range("C34").Value = FindMe.Column
'so that it saves the column number you want
End If
End With
End Sub
Sub CopyScheduledToList()
'//////Searches Today's day Column from the schedule given by GetDateRow Sub & assembles \\\\\\\
'////// the names of those who are scheduled to work today to a list on Page 2 \\\\\\\
Dim Ccount As Integer
Dim lngLoop As Long
Dim TodaysCol As Long
Dim dShift As String
Dim cShift As String
Dim lastRow As Long
Dim wsSrc As Worksheet
Dim wsDst As Worksheet
Ccount = 1
dShift = "A63"
cShift = "TLA"
Set wsSrc = Worksheets("Sheet1")
Set wsDst = Worksheets("Sheet2")
TodaysCol = wsSrc.Range("C34").Value
'Find last used row in today's column
lastRow = wsSrc.Cells(wsSrc.Rows.Count, TodaysCol).End(xlUp).Row
For lngLoop = 1 To lastRow
If wsSrc.Cells(lngLoop, TodaysCol).Value = cShift Then
wsDst.Cells(1, "D").Value = wsSrc.Cells(lngLoop, "A").Value
End If
'////// Get's the Team Leader and places name into column D on Page 2
If wsSrc.Cells(lngLoop, TodaysCol).Value = dShift Then
If wsSrc.Cells(lngLoop, "A").Value <> "" Then
wsDst.Cells(Ccount, "A").Value = wsSrc.Cells(lngLoop, "A").Value
Ccount = Ccount + 1
End If
End If
'////// Places the name from the working list onto page 2 and adds a count so the next value found
'////// will go to the next empty row on Sheet 2
Next lngLoop
End Sub
Your first sub is, in fact, a function (or should be) which returns a value which can be assigned to a variable in your second sub. The following code realises that concept. It is different in other respects too, but I think you will like it. Please try.
Option Explicit
Sub CopyScheduledToList()
'//////Searches Today's day Column from the schedule given by GetDateRow Sub & assambles \\\\\\\
'////// the names of those who are scheduled to work today to a list on Page 2 \\\\\\\
' it is best practise, not to have any hard-coded addresses in the code.
' Therefore it is good to place all parameters separate from and before the code:
Const SearchRow As Long = 5
Const dShift As String = "A63"
Const cShift As String = "TLA"
Dim WsMain As Worksheet ' better to set your own variable
Dim WsOutput As Worksheet ' even if it will be "ActiveSheet"
Dim TgtColumn As Long
Dim Rlast As Long ' last data row in WsMain
Dim Rcount As Long ' output row counter
Dim R As Long
Set WsMain = ActiveSheet ' might be Sheets("Sheet1")
Set WsOutput = Worksheets("Sheet2") ' or, simply, Sheet1
TgtColumn = DateColumn(WsMain, SearchRow)
If TgtColumn < 1 Then Exit Sub
Rcount = 1
With WsMain
Rlast = .Cells(.Rows.Count, TgtColumn).End(xlUp).Row
For R = 1 To Rlast
Select Case .Cells(R, TgtColumn).value
Case cShift
'////// Get's the Team Leader and places name into column D on WsOutput
WsOutput.Cells(Rcount, "D").value = .Cells(R, 1).value
Case dShift
WsOutput.Cells(Rcount, "A").value = .Cells(R, 1).value
Case Else
Exit Sub ' define the correct response if neither is found
End Select
If Len(WsOutput.Cells(Rcount, "A").value) Then Rcount = Rcount + 1
'////// Places the name from the working list onto page 2 and adds a count so the next value found
'////// will go to the next empty row on Sheet 2
Next R
End With
End Sub
Private Function DateColumn(Ws As Worksheet, _
ByVal SearchRow As Long) As Long
' returns the row that has today's date
' return 0 if not found
Dim SearchMe As Variant
Dim TgtDate As String
Dim Fnd As Range
If SearchRow < 1 Then Exit Function
Do
TgtDate = InputBox("Enter the target date", _
"List shift workers", _
Format(Date, "Short Date"))
' you can also set the default like Format(Date + 1, "d/m/yy")
' the sample date format must match your regional settings
If IsDate(TgtDate) Then
SearchMe = CDate(TgtDate)
' SearchMe will have the date in the format set
' in your regional settings
Exit Do
Else
MsgBox "Please enter a valid date" & vbCr & _
"in d-m-yy format", vbExclamation, "Invalid date"
' adjust the required format to your regional settings
End If
Loop While Len(TgtDate) ' enter blank or press Cancel to exit
Set Fnd = Ws.Rows(SearchRow).Find(What:=SearchMe, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If Not Fnd Is Nothing Then DateColumn = Fnd.Column
End Function
Related
i have tried many sites and am really struggling as i cant seem to understand the VBA code
tab1 = data from C8:Rx? ... the data will continously grow so table will get bigger all the time
Column C in tab1 contains dates 21/05/2021
I want to be able to have 2 prompt boxes where i enter a date from and date to 21/05/2021 - 22/05/2021
when i action the macro it will take only the data from the table in tab1 in between these dates
and paste them in tab2 at cell ref c8 (the start of the table)
Option Explicit
'This subroutine prompts the user to select dates
Public Sub PromptUserForInputDates()
Dim strStart As String, strEnd As String, strPromptMessage As String
'Prompt the user to input the start date
strStart = InputBox("Please enter the start date")
'Validate the input string
If Not IsDate(strStart) Then
strPromptMessage = "Oops! It looks like your entry is not a valid " & _
"date. Please retry with a valid date..."
MsgBox strPromptMessage
Exit Sub
End If
'Prompt the user to input the end date
strEnd = InputBox("Please enter the end date")
'Validate the input string
If Not IsDate(strStart) Then
strPromptMessage = "Oops! It looks like your entry is not a valid " & _
"date. Please retry with a valid date..."
MsgBox strPromptMessage
Exit Sub
End If
'Call the next subroutine, which will do produce the output workbook
Call CreateSubsetWorkbook(strStart, strEnd)
End Sub
'This subroutine creates the new workbook based on input from the prompts
Public Sub CreateSubsetWorkbook(StartDate As String, EndDate As String)
Dim wbkOutput As Workbook
Dim wksOutput As Worksheet, wks As Worksheet
Dim lngLastRow As Long, lngLastCol As Long, lngDateCol As Long
Dim rngFull As Range, rngResult As Range, rngTarget As Range
'Set references up-front
lngDateCol = 3 '<~ we know dates are in column C
Set wbkOutput = Workbooks.Add
'Loop through each worksheet
For Each wks In ThisWorkbook.Worksheets
With wks
'Create a new worksheet in the output workbook
Set wksOutput = wbkOutput.Sheets.Add
wksOutput.Name = wks.Name
'Create a destination range on the new worksheet that we
'will copy our filtered data to
Set rngTarget = wksOutput.Cells(1, 1)
'Identify the data range on this sheet for the autofilter step
'by finding the last row and the last column
lngLastRow = .Cells.Find(What:="*", LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
lngLastCol = .Cells.Find(What:="*", LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
Set rngFull = .Range(.Cells(1, 1), .Cells(lngLastRow, lngLastCol))
'Apply a filter to the full range to get only rows that
'are in between the input dates
With rngFull
.AutoFilter Field:=lngDateCol, _
Criteria1:=">=" & StartDate, _
Criteria2:="<=" & EndDate
'Copy only the visible cells and paste to the
'new worksheet in our output workbook
Set rngResult = rngFull.SpecialCells(xlCellTypeVisible)
rngResult.copy Destination:=rngTarget
End With
'Clear the autofilter safely
.AutoFilterMode = False
If .FilterMode = True Then
.ShowAllData
End If
End With
Next wks
'Let the user know our macro has finished!
MsgBox "Data transferred!"
End Sub
What this solution does:
Assumes your dates are in Column A of your worksheet.
Can be used to replace the CreateSubsetWorkbook sub you have.
You can still use the PromptUserForInputDates and then call this sub instead of CreateSubsetWorkbook.
Public Sub FillOutputRange(ByVal StartDate As Date, ByVal EndDate As Date)
Dim SourceSheet As Worksheet
Dim DestinationSheet As Worksheet
Dim TargetCell As Range
Dim LastRow As Long
Dim StartRow As Long
Dim EndRow As Long
Dim RowLoopCounter As Long
Dim EndColumn As Long
Dim OutputDataArray As Variant
With ThisWorkbook
Set SourceSheet = .Sheets("Sheet1") 'Change this to the name of your source sheet
Set DestinationSheet = .Sheets("Sheet2") 'Change this to the name of your destination sheet
End With
With SourceSheet
LastRow = .Cells(Rows.Count, "A").End(xlUp).Row
For Each TargetCell In .Range("A1:A" & LastRow)
If TargetCell.Value = StartDate Then
StartRow = TargetCell.Row
Exit For
End If
Next TargetCell
If StartRow = 0 Then MsgBox "Start Date was not found", vbOKOnly, "No Start Date": Exit Sub
For RowLoopCounter = LastRow To StartRow Step -1
If .Range("C" & RowLoopCounter).Value = EndDate Then
EndRow = RowLoopCounter
Exit For
End If
Next RowLoopCounter
If EndRow = 0 Then MsgBox "End Date was not found", vbOKOnly, "No End Date": Exit Sub
EndColumn = .Cells(EndRow, .Columns.Count).End(xlToLeft).Column
OutputDataArray = .Range(.Cells(StartRow, "A"), .Cells(EndRow, EndColumn)).Value
End With
With DestinationSheet
.Range("C8").Resize(UBound(OutputDataArray, 1), UBound(OutputDataArray, 2)).Value = OutputDataArray
End With
End Sub
How does it work?
The flow of this sub can be described as:
First we set variables to use as reference to our SourceSheet and DestinationSheet - Be sure to change these to the correct worksheet names for your workbook.
Then with the SourceSheet we find the LastRow - see "Better way to find last row"
Then we use a For Each Next Statement to loop through each cell in the Range("A1:A" & LastRow") - If LastRow is say, 10 then this would be equivalent to Range("A1:A10")
With each iteration of the loop we are checking if the cell's value matches our StartDate argument passed from the PromptUserForInputDates Sub.
Once we have our first match, we assign the Row of that cell to the StartRow variable and the loop is exited and the code continues.
The next loop is a For Next Statement which works with slightly different syntax. I've used this to demonstrate using the different statement. We loop backwards from the end of the range, back to the StartRow so this way the EndRow will be established from the last occurrence of the EndDate in your range.
Now that we have the StartRow and EndRow we know which rows the target data are in between (inclusive).
EndColumn is found based on the last column with data in the EndRow - you can find this based on any row, I just chose to find it with the end row.
Using the 3 variables, StartRow, EndRow, and EndColumn we can build our OutputDataArray by assigning the value of the target range to the array variable. This automatically builds a two dimensional array with all the data in it.
Finally, with our DestinationSheet we now write the array to a range in the sheet. I've hard coded this to start at Range("C8") per your question.
The Range.Resize Property is used to change the Range size to match the Array size, this way the data from the Array writes directly into the sheet.
This Chip Pearson article is great for learning about Arrays.
Note: I've added If...Then statements after each loop to catch errors that will appear if either of the variables StartRow and/or EndRow are not assigned (meaning they retain their default value of 0). This handles the error by throwing a messagebox to the user advising whichever date hasn't been found.
Demonstration
Based on the following dates being used:
StartDate = 3/6/2021
EndDate = 6/6/2021
Sample Source data:
Outcome of running sub:
my code error
Error says application defined or object defined error
Sorry for all the trouble #samuel
Option Explicit
'This subroutine prompts the user to select dates
Public Sub PromptUserForInputDates()
Dim strStart As String, strEnd As String, strPromptMessage As String
'Prompt the user to input the start date
strStart = InputBox("Please enter the start date")
'Validate the input string
If Not IsDate(strStart) Then
strPromptMessage = "Oops! It looks like your entry is not a valid " & _
"date. Please retry with a valid date..."
MsgBox strPromptMessage
Exit Sub
End If
'Prompt the user to input the end date
strEnd = InputBox("Please enter the end date")
'Validate the input string
If Not IsDate(strStart) Then
strPromptMessage = "Oops! It looks like your entry is not a valid " & _
"date. Please retry with a valid date..."
MsgBox strPromptMessage
Exit Sub
End If
'Call the next subroutine, which will do produce the output workbook
Call FillOutputRange(strStart, strEnd)
End Sub
Public Sub FillOutputRange(ByVal StartDate As Date, ByVal EndDate As Date)
Dim SourceSheet As Worksheet
Dim DestinationSheet As Worksheet
Dim TargetCell As Range
Dim LastRow As Long
Dim StartRow As Long
Dim EndRow As Long
Dim RowLoopCounter As Long
Dim EndColumn As Long
Dim OutputDataArray As Variant
With ThisWorkbook
Set SourceSheet = .Sheets("Branches consolidated Master (4") 'Change this to the name of your source sheet
Set DestinationSheet = .Sheets("Date Extract (5)") 'Change this to the name of your destination sheet
End With
With SourceSheet
LastRow = .Cells(Rows.Count, "C").End(xlUp).Row
For Each TargetCell In .Range("C8:C" & LastRow)
If TargetCell.Value = StartDate Then
StartRow = TargetCell.Row
Exit For
End If
Next TargetCell
For RowLoopCounter = LastRow To StartRow Step -1
If Range("C" & RowLoopCounter).Value = EndDate Then
EndRow = RowLoopCounter
Exit For
End If
Next RowLoopCounter
EndColumn = .Cells(EndRow, .Columns.Count).End(xlToLeft).Column
OutputDataArray = .Range(.Cells(StartRow, "A"), .Cells(EndRow, EndColumn)).Value
End With
With DestinationSheet
.Range("C8").Resize(UBound(OutputDataArray, 1), UBound(OutputDataArray, 2)).Value = OutputDataArray
End With
End Sub
I have two Excel sheets in a workbook that I am wanting to copy data between and I can't figure it out. I am trying to adapt the code from a prior answer here but I can't get it to function how I want.
In short the two sheets are "Active" and "Term" for if an employee is active with the company or not. I am trying to get a pop-up when I run the macro to request entry of the employee ID. Once entered I want to find that unique ID in column A and then select a portion of the cells (cells A to G) in that row (of the unique value) then cut and paste it in the next empty row in the "term" sheet. Once that is done I want to delete the entire row from the active sheet.
I've used the match function nested within an index function to reference the unique value and return data from the sheets and others but I can't figure out if those will somehow help and how to implement them. What I am asking may not be possible. I can record a macro but the values are static and would only work that one exact time. Thank you in advance.
Sub EmployeeTermination()
Dim x As Long
Dim iCol As Integer
Dim MaxRowList As Long
Dim S As String
Dim fVal As String
Dim fRange As Range
Set wssource = Worksheets("Active")
Set wstarget = Worksheets("Term")
iCol = 1
MaxRowList = wssource.Cells(Rows.Count, iCol).End(xlUp).Row
For x = MaxRowList To 1 Step -1
S = wssource.Cells()
If S = "Yes" Or S = "yes" Then
fVal = InputBox(Enter employee ID:)
Set fRange = wstarget.Columns("A:A").Find(What:=fVal, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False)
If fRange Is Nothing Then
AfterLastTarget = wstarget.Cells(Rows.Count, 1).End(xlUp).Row + 1
wssource.Rows(x).Copy
wstarget.Rows(AfterLastTarget).PasteSpecial Paste:=xlPasteValuesAndNumberFormats
End If
End If
Next
Application.ScreenUpdating = True
End Sub
I know this basically rewrote your code, but I added a few checks to ensure you want to delete your employee. I made the assumption that the employee's name is in column B, so if not you can change this line:
If MsgBox("Are you sure you want to terminate " & wsActive.Cells(rngEmployee.Row, _
2) & "?", vbYesNo) Then
by replacing the 2 in the second row with whatever column number you would like. (or you can simply delete this check altogether).
I also added minimal error handling.
Option Explicit
Const Err_EmpNotFound = 1000
Public Function NextRow(ByVal ws As Worksheet, Optional ByVal col As Variant = 1) As Long
With ws
NextRow = .Cells(.Rows.Count, col).End(xlUp).Row + 1
End With
End Function
Sub EmployeeTermination()
'On Error GoTo ErrHandler
Dim wsActive As Worksheet, wsTerm As Worksheet
Set wsActive = ThisWorkbook.Worksheets("Active")
Set wsTerm = ThisWorkbook.Worksheets("Term")
'Locate the employee
Dim rngEmployee As Range, sEmployeeID As String, empDataArr As Variant
sEmployeeID = Application.InputBox("Enter Employee ID")
Set rngEmployee = wsActive.Range("A:A").Find(sEmployeeID, Lookat:=xlWhole)
If rngEmployee Is Nothing Then
Err.Raise vbObjectError + Err_EmpNotFound, Description:="Employee Not Found"
End If
'Prompt before termination (assume's employee's name is column 2 (col B)
If MsgBox("Are you sure you want to terminate " & wsActive.Cells(rngEmployee.Row, _
2) & "?", vbYesNo) = vbNo Then
Exit Sub
End If
empDataArr = rngEmployee.Columns("A:G").Value
'Delete the data
rngEmployee.EntireRow.Delete
'Add employee to termination sheet (and date column "H")
With wsTerm.Rows(NextRow(wsTerm))
.Columns("A:G") = empDataArr
.Columns("H") = Date
End With
'Notify user of completion
MsgBox "Employee was successfully terminated!"
Exit Sub
ErrHandler:
Dim errBox As Long
Select Case Err.Number - vbObjectError
Case Err_EmpNotFound
errBox = MsgBox(Err.Description, vbRetryCancel)
If errBox = vbRetry Then
Err.Clear
EmployeeTermination
End
End If
Case Else
MsgBox Err.Description, Title:=Err.Number
End Select
End Sub
I am trying to find an automated solution for the following daily task.
I have a master workbook with 13 sheets.
Names are Jan-Dec (all 12 months) and Data.
Every sheet has 2 sets of 3 columns: Item Code (A1), Year (B1), Price (C1) and Item Code (E1), Year (F1), Price (G1).
Every day I have over 1000 new entries in "Data" sheet and then have to find matching item code (in Column A) in other 12 sheets, columns A-C, cut and move new matching data to E-G and highlight the new entries.
I tried the following vba code:
Sub TestNewCode()
Application.ScreenUpdating = False
Dim varMainRange As Range
Dim varSubRange As Range
Set varMainRange = Range(Worksheets("Jul").Range("A2:C65536"), _
Worksheets("Jul").Range("A65536").End(xlUp))
For Each MainCell In varMainRange
Set varSubRange = Range(Worksheets("Data").Range("A2"), _
Worksheets("Data").Range("A65536").End(xlUp))
For Each SubCell In varSubRange
If MainCell.Value = SubCell.Value Then
Worksheets("Data").Range("A2:C2").Copy _
Worksheets("Jul").Range("E2:G2")
Exit For
End If
Next SubCell
Next MainCell
Application.ScreenUpdating = True
End Sub
As you can see this code can move only one cell.
I'll appreciate if someone can show a solution to this matter.
I didn't fully test this code, in part because I doubt that you really want to post the data to any of the 12 monthly sheets. Instead, I suspect that the data must be posted to one particular of the monthly sheets. However, that isn't what you said, and therefore my code will look in all sheets and stop looking after it finds a match. This is something you may find easy to adjust. Otherwise I can help you do it.
However, what this code needs now is thorough testing. :-)
Sub TestNewCode()
' 16 Sep 2017
Const Tabs As String = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"
Dim WsData As Worksheet
Dim Ws As Worksheet ' any of the monthly sheets
Dim WsName() As String
Dim Rend As Long, Rl As Long ' last row in WsData / Ws
Dim R As Long, Rm As Long ' row counter WsData / Ws
Dim Entry As Variant ' one Data entry
Set WsData = Worksheets("Data")
WsName = Split(Tabs, " ")
Application.ScreenUpdating = False
With WsData
Rend = .Cells(.Rows.Count, "A").End(xlUp).Row
For R = 2 To Rend
Entry = .Range(.Cells(R, 1), .Cells(R, 3)).Value ' A:C
Rm = FindMatch(Entry, Ws, WsName)
If Rm Then ' rm = 0 if not found
With Ws.Cells(Rm, 5).Resize(1, UBound(Entry, 2))
.Value = Entry
.Interior.Color = vbYellow
End With
End If
Next R
End With
Application.ScreenUpdating = True
End Sub
Private Function FindMatch(Entry As Variant, _
Ws As Worksheet, _
WsName() As String) As Long
' return zero if no match was found
Dim Rng As Range ' search range
Dim Fnd As Range
Dim Rl As Long
Dim i As Long
For i = 0 To UBound(WsName)
On Error Resume Next
Set Ws = Worksheets(WsName(i))
If Err Then
MsgBox "Worksheet " & WsName(i) & " doesn 't exist.", _
vbInformation, "Missing worksheet"
Else
With Ws
Rl = .Cells(.Rows.Count, 1).End(xlUp).Row
Set Rng = .Range(.Cells(2, 1), .Cells(Rl, 3))
Set Fnd = Rng.Find(What:=Entry(1, 1), _
After:=Rng.Cells(Rng.Cells.Count), _
LookIn:=xlValues, _
Lookat:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, _
MatchCase:=False, _
MatchByte:=False)
If Not Fnd Is Nothing Then
FindMatch = Fnd.Row
Exit For
End If
End With
End If
Next i
If Fnd Is Nothing Then
MsgBox "Code " & Entry(1, 1) & " wasn't found.", _
vbInformation, "Missing Code"
End If
End Function
I would appreciate any help on this matter. I am trying to create an Excel 2010 macro in VBA that will read strings in one spreadsheet row by row, and then search another spreadsheet to see if the value exists in a column of strings.
If/When it finds a matching string in column A, I would like to compare the string in column C of the original spreadsheet with the string in Column C of the spreadsheet being searched. If both strings are the same, I would like to move on back to the column A search and continue.
If the strings are different I would like to overwrite the string in Column C of the spreadsheet being searched. I would also like to highlight this change on the searched spreadsheet.
If no matching string is found in column A of the search spreadsheet, then I want to copy the row of the original spreadsheet into the searched spreadsheet and highlight it.
Here's what I have so far, but I can't seem to get it to work properly:
Sub SearchRows()
Dim bottomA1 As Integer
bottomA1 = Sheets("Original Spreadsheet").Range("A" & Rows.Count).End(xlUp).Row
Dim bottomA2 As Integer
bottomA2 = Sheets("Searched Spreadsheet").Range("A" & Rows.Count).End(xlUp).Row
Dim rng1 As Range
Dim rng2 As Range
Dim x As Long
Dim y As Long
Dim foundColumnA As Range
Dim foundColumnC As Range
For Each rng1 In Sheets("Original Spreadsheet").Range("A2:A" & bottomA1)
With Sheets("Searched Spreadsheet").Range("A2:A" & bottomA2)
Set foundColumnA = .Find(what:=rng1, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
For Each rng2 In Sheets("Original Spreadsheet").Range("E2:E" & bottomA1)
With Sheets("Searched Spreadsheet").Range("E2:E" & bottomA2)
Set foundSize = .Find(what:=rng2, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=True)
If foundColumnC Is Nothing Then
bottomE2 = Sheets("Column C Changes").Range("E" & Rows.Count).End(xlUp).Row
y = bottomA2 + 1
rng2.EntireRow.Copy Sheets("Column C Changes").Cells(y, "A")
Sheets("Column C Changes").Cells (y, "A").EntireRow.Interior.ColorIndex = 4
End If
End With
Next rng2
If foundTag Is Nothing Then
bottomA2 = Sheets("Column A Changes").Range("A" & Rows.Count).End(xlUp).Row
x = bottomA2 + 1
rng1.EntireRow.Copy Sheets("Column A Changes").Cells(x, "A")
Sheets("Column A Changes").Cells(x, "A").EntireRow.Interior.ColorIndex = 3
End If
End With
Next rng1
End Sub
You actually have too much code, but they're not set up cleanly. Qualify a lot of things as much as possible so it's cleaner, and try to be consistent with your style. This way you can identify the error as much as possible.
Anyway, on to the code. The basic logic you want is as follows, based on the details above:
Check if a string in Sheet1!A is in Sheet2!A.
If found, compare Column C values.
If Column C values are different, set value of Sheet2 to that in Sheet1 and highlight.
Else, exit.
If not found, copy whole row to Sheet2 and highlight.
Now that we have that written down, it's simpler! :)
Please check my screenshots for my set-up:
SCREENSHOTS:
Sheet1:
Sheet2:
Note that for Sheet2, I don't have BK207 onwards. ;) Now, onto the code.
CODE:
Sub LoopMatchReplace()
Dim ShSrc As Worksheet, ShTar As Worksheet
Dim SrcLRow As Long, TarLRow As Long, NextEmptyRow As Long
Dim RefList As Range, TarList As Range, RefCell As Range, RefColC
Dim TarCell As Range, TarColC As Range
Dim IsFound As Boolean
Dim ToFind As String
With ThisWorkbook
Set ShSrc = .Sheets("Sheet1")
Set ShTar = .Sheets("Sheet2")
End With
'Get the last rows for each sheet.
SrcLRow = ShSrc.Range("A" & Rows.Count).End(xlUp).Row
TarLRow = ShTar.Range("A" & Rows.Count).End(xlUp).Row
'Set the lists to compare.
Set RefList = ShSrc.Range("A2:A" & SrcLRow)
Set TarList = ShTar.Range("A2:A" & TarLRow)
'Initialize boolean, just for kicks.
IsFound = False
'Speed up the process.
Application.ScreenUpdating = False
'Create the loop.
For Each RefCell In RefList
ToFind = RefCell.Value
'Look for the value in our target column.
On Error Resume Next
Set TarCell = TarList.Find(ToFind)
If Not TarCell Is Nothing Then IsFound = True
On Error GoTo 0
'If value exists in target column...
If IsFound Then
'Compare the Column C of both sheets.
Set TarColC = TarCell.Offset(0, 2)
Set RefColC = RefCell.Offset(0, 2)
'If they are different, set the value to match and highlight.
If TarColC.Value <> RefColC.Value Then
TarColC.Value = RefColC.Value
TarColC.Interior.ColorIndex = 4
End If
Else 'If value does not exist...
'Get next empty row, copy the whole row from source sheet, and highlight.
NextEmptyRow = ShTar.Range("A" & Rows.Count).End(xlUp).Row + 1
RefCell.EntireRow.Copy ShTar.Rows(NextEmptyRow)
ShTar.Rows(NextEmptyRow).SpecialCells(xlCellTypeConstants).Interior.ColorIndex = 3
End If
'Set boolean check to False.
IsFound = False
Next RefCell
Application.ScreenUpdating = True
End Sub
Kindly read the comments for the codeblocks so you get an understanding of what I'm doing. Also, note the way that I have qualified everything and properly set them up in a very clean way. Clean code is 50% good code.
Check the following screenshot to see the results after running the code.
END RESULT:
Note the added rows at the end and the changed values in Column C. I did not have the whole row highlighted as I believe that's bad practice and messy, but it's up to you to change the respective lines and values to suit your taste for the end result.
Let us know if this helps.
I think you can use this code.
Values not found will be added to the end of destination sheet.
Differences are signed with a blue(change if you want) background color.
Sub copy_d()
Dim r1 As Long, rfound, vfound
Dim w1, w2, v, lastR As Long, lastC As Long
Set w1 = Sheets("sheet1") ' change the origin sheet at will
Set w2 = Sheets("sheet2") ' change the destination sheet at will
r1 = 1 ' assuming data start in row 1, change it if not
Do While Not IsEmpty(w1.Cells(r1, 1))
v = w1.Cells(r1, 1)
rfound = Application.Match(v, w2.Columns(1), 0) ' look for value
If Not IsError(rfound) Then ' found it?
vfound = w2.Cells(rfound, 3)
If w1.Cells(r1, 3) <> vfound Then ' value in column C is different?
w2.Cells(rfound, 3) = w1.Cells(r1, 3) ' update based on origin sheet
lastC = w2.Cells(rfound, 1).End(xlToRight).Column
w2.Range(w2.Cells(rfound, 1), w2.Cells(rfound, lastC)).Interior.ColorIndex = 5
End If
Else
lastR = w2.Cells(1, 1).End(xlDown).Row + 1
w1.Rows(r1).copy Destination:=w2.Rows(lastR) ' copy to last row of dest sheet
lastC = w2.Cells(lastR, 1).End(xlToRight).Column
w2.Range(w2.Cells(lastR, 1), w2.Cells(lastR, lastC)).Interior.ColorIndex = 5
End If
r1 = r1 + 1
Loop
End Sub
I'm not an advanced VBA programmer. I'm working on an excel macro which will allow me to select a range(using input box) to clean the data(makes consistent with mySQL schema) on worksheet. I get this file from anther team and
1.) the order of columns is not fixed
2) levels of categories(there are few columns for categories like level1 level2 etc.) can be anything between 3-10.
I want to concatenate the columns for categories(in image level 1, level 2 etc.) using | as a separator and put the values in first category column(level1) while deleting remaining columns(level 2, level 3...[level 10]).
I removed some code from the end to reduce the length here but it still makes sense:
Sub cleanData()
Dim rngMyrange As Range
Dim cell As Range
On Error Resume Next
Do
'Cleans Status column
Set rngMyrange = Application.InputBox _
(Prompt:="Select Status column", Type:=8)
On Error GoTo 0
'Is a range selected? Exit sub if not selected
If rngMyrange Is Nothing Then
End
Else
Exit Do
End If
Loop
With rngMyrange 'with the range just selected
.Replace What:="Dead", Replacement:="Inactive", SearchOrder:=xlByColumns, MatchCase:=False
'I do more replace stuff here
End With
rngMyrange.Cells(1, 1) = "Status"
Do
'Concatenates Category Columns
Set rngMyrange = Application.InputBox _
(Prompt:="Select category columns", Type:=8)
On Error GoTo 0
'Is a range selected? Exit sub if not selected
If rngMyrange Is Nothing Then
End
Else
Exit Do
End If
Loop
With rngMyrange 'with the range just selected
'Need to concatenate the selected columns(row wise)
End With
rngMyrange.Cells(1, 1) = "Categories"
End Sub
Please do not suggest a UDF, I want to do this with macro. I must do this on files before importing them on SQL database, so a macro will be handy. Please ask if I failed to mention anything else.
EDIT: Image attached for illustration
UPDATE:
I now have a working code with help from vaskov17 on mrexcel but it does not delete the columns from where the levels are picked-level 2, level 3...etc. to shift next columns to left and the major challenge for me is to implement that code in my existing macro using range type instead of long type. I do not want to enter start column and finish column separately, instead I should be able to select range like in my original macro. Code for that macro is below, please help me:
Sub Main()
Dim start As Long
Dim finish As Long
Dim c As Long
Dim r As Long
Dim txt As String
start = InputBox("Enter start column:")
finish = InputBox("Enter ending column:")
For r = 2 To Cells(Rows.Count, "A").End(xlUp).Row
For c = start To finish
If Cells(r, c).Text <> "" Then
txt = txt & Cells(r, c).Text & "|"
Cells(r, c).Clear
End If
Next
If Right(txt, 1) = "|" Then
txt = Left(txt, Len(txt) - 1)
End If
Cells(r, start) = txt
txt = ""
Next
End Sub
I have removed the inputbox for selection of the category columns. Since they are always named Level x»y it makes it easier to find them automatically. That's why added a FindColumns() Sub to your code. It assigns the first fCol and last lCol Category column to global variables.
The ConcatenateColumns() concatenates cells in each row using "|" as separator.
The DeleteColumns() deletes the other columns
Cells(1, fCol).Value = "Category renames Level 1 to Category and Columns.AutoFit resizes all columns widths to fit the text.
Code:
Option Explicit
Dim fCol As Long, lCol As Long
Sub cleanData()
Dim rngMyrange As Range
Dim cell As Range
On Error Resume Next
Do
'Cleans Status column
Set rngMyrange = Application.InputBox _
(Prompt:="Select Status column", Type:=8)
On Error GoTo 0
'Is a range selected? Exit sub if not selected
If rngMyrange Is Nothing Then
End
Else
Exit Do
End If
Loop
With rngMyrange 'with the range just selected
.Replace What:="Dead", Replacement:="Inactive", SearchOrder:=xlByColumns, MatchCase:=False
'I do more replace stuff here
End With
rngMyrange.Cells(1, 1) = "Status"
' Concatenate Category Columns
FindColumns
ConcatenateColumns
DeleteColumns
Cells(1, fCol).Value = "Category"
Columns.AutoFit
End Sub
Private Sub FindColumns()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim i As Long, j As Long
For i = 1 To ws.Cells(1, Columns.Count).End(xlToLeft).Column
If StrComp(ws.Cells(1, i).Text, "Level 1", vbTextCompare) = 0 Then
For j = i To ws.Cells(1, Columns.Count).End(xlToLeft).Column
If InStr(1, ws.Cells(1, j).Text, "Level", vbTextCompare) Then
lCol = j
End If
Next j
fCol = i
Exit Sub
End If
Next i
End Sub
Private Sub ConcatenateColumns()
Dim rng As Range
Dim i As Long, j As Long
For i = 2 To Cells(Rows.Count, fCol).End(xlUp).Row
Set rng = Cells(i, fCol)
For j = fCol + 1 To lCol
rng = rng & "|" & Cells(i, j)
Next j
rng = "|" & rng & "|"
Set rng = Nothing
Next i
End Sub
Private Sub DeleteColumns()
Dim i As Long
For i = lCol To fCol + 1 Step -1
Columns(i).Delete Shift:=xlToLeft
Next i
End Sub