I have an Lmonth Loop code that searches though a list of dates to find jobs that were ordered in january (1) for example and copy&pastes those into a new sheet.
The code runs fine but when it gets to then end it flags a #debug error 13'
If i disable the line the code does not function but i cannot work out what is broken.
Sub Search_Month()
Dim datasheet As Worksheet
Set datasheet = Sheet2
Dim Mreport As Worksheet
Set Mreport = Sheet9
Dim Lmonth As Integer
Search = Range("m4").Value
Dim i As Integer
Mreport.Unprotect Password:=rapid1
Mreport.Range("a2:a300").ClearContents
datasheet.Activate
For i = 7 To 5000
Lmonth = Month(Cells(i, 6))
If Lmonth = Search Then
Range(Cells(i, 2), Cells(i + 3, 2)).Copy
Mreport.Activate
Range("A1000").End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
datasheet.Activate
End If
Next i
Mreport.Activate
Mreport.Protect Password:=rapid1
MsgBox "End of Month Report Updated"
End Sub
The line that flags is Lmonth = Month(Cells(i, 6)) but i dont know why.
All the results the macro finds are correct, just the error at the end is annoying. I think its saying 'the next search line does not show lmonth=1 so i cant run the code anymore so it must be broken"
Debug error 13 is a type mismatch. So the function "Month" is given a value, that cannot be processed.
See Documentation for reference. It needs to be a date.
Your main problem seems the fact, that you just go from line 7 to 5000 without even checking if there is any content. I don't think you can trust the fact, that there are always 4993 entries in the table.
So I recomment to change the loop to something like For i = 7 To ActiveSheet.UsedRange.Rows.Count. Also you can check the datatype before using "Month()" with the "IsDate"-function if you are unsure.
Copy Monthly
I've changed the variable rapid1 to a string. You might want to
change this to make the code work.
Although implementing constants (change only once and change quickly
'in one place' (at the beginning)) and naming them appropriately is
probably increasing readability for others (and for you, after a
while), it might not be so while developing. Therefore I've included
the No Constants Version below the Main Version.
Main Version
Sub Search_Month()
' Data
Const cSearch As String = "M4" ' Search Value Cell Range
Const cFRD As Long = 7 ' First Row Number
Const cOffset As String = 3 ' Copy Row Offset
Const cCol As Variant = "F" ' Search Column Letter/Number
Const cCopy As Variant = "B" ' Copy Column Letter/Number
' Report
Const cFRR As Long = 2 ' First Row Number
Const cWrite As Variant = "A" ' Write Column Letter/Number
' Data
Dim datasheet As Worksheet ' Worksheet
Dim rng As Range ' Last Cell Range
Dim Search As Long ' Search Month
Dim vntMonth As Variant ' Current Month
Dim i As Long ' Row Counter
' Report
Dim Mreport As Worksheet ' Worksheet
Dim FER As Long ' First Empty Row
' Create References to Worksheets
Set datasheet = Sheet2
Set Mreport = Sheet9
' Speed up
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
On Error GoTo ProcedureExit
' In Data Worksheet
With datasheet
' Assign value from Search Value Cell Range to Search Month.
Search = .Range(cSearch).Value
' In Search Column
With .Columns(cCol)
' Calculate Last Cell Range in Search Column.
Set rng = .Find("*", , xlFormulas, xlWhole, xlByColumns, xlPrevious)
End With
If rng Is Nothing Then ' No data in column (Highly unlikely).
MsgBox "No Data in column '" _
& Split(.Cells(1, cCol).Address, "$")(1) & "'."
GoTo ProcedureExit
End If
' In Report Worksheet
With Mreport
.Unprotect Password:="rapid1"
' Clear contents from First Row to bottom cell of Write Column.
.Cells(cFRR, cWrite).Resize(.Rows.Count - cFRR + 1).ClearContents
' Write First Row Number to First Empty Row.
FER = cFRR
End With
' Loop through cells of Data Worksheet.
For i = cFRD To rng.Row
' Write value of current cell to Current Month.
vntMonth = .Cells(i, cCol)
' Check if Current Month is a date or can be converted to a date.
If IsDate(vntMonth) Then
' Check if month of current cell value is equal to Current Month.
If Month(vntMonth) = Search Then
' Write data from Data Worksheet to Report Worksheet.
Mreport.Cells(FER, cWrite).Resize(cOffset) = _
.Cells(i, cCopy).Resize(cOffset).Value
FER = FER + cOffset
End If
End If
Next
End With
' In Report Worksheet
With Mreport
.Protect Password:="rapid1"
MsgBox "End of Month Report Updated"
End With
ProcedureExit:
' Speed down
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
No Constants Version
Sub Search_Month_No_Constants()
' Data
Dim datasheet As Worksheet ' Worksheet
Dim rng As Range ' Last Cell Range
Dim Search As Long ' Search Month
Dim vntMonth As Variant ' Current Month
Dim i As Long ' Row Counter
' Report
Dim Mreport As Worksheet ' Worksheet
Dim FER As Long ' First Empty Row
' Create References to Worksheets
Set datasheet = Sheet2
Set Mreport = Sheet9
' Speed up
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
On Error GoTo ProcedureExit
' In Data Worksheet
With datasheet
' Assign value from Search Value Cell Range to Search Month.
Search = .Range("M4").Value
' In Search Column
With .Columns("F")
' Calculate Last Cell Range in Search Column.
Set rng = .Find("*", , xlFormulas, xlWhole, xlByColumns, xlPrevious)
End With
If rng Is Nothing Then ' No data in column (Highly unlikely).
MsgBox "No Data in column 'F'." _
GoTo ProcedureExit
End If
' In Report Worksheet
With Mreport
.Unprotect Password:="rapid1"
' Clear contents from First Row to bottom cell of Write Column.
.Cells(2, "A").Resize(.Rows.Count - 2 + 1).ClearContents
' Write First Row Number to First Empty Row.
FER = 2
End With
' Loop through cells of Data Worksheet.
For i = 7 To rng.Row
' Write value of current cell to Current Month.
vntMonth = .Cells(i, "F")
' Check if Current Month is a date or can be converted to a date.
If IsDate(vntMonth) Then
' Check if month of current cell value is equal to Current Month.
If Month(vntMonth) = Search Then
' Write data from Data Worksheet to Report Worksheet.
Mreport.Cells(FER, "A").Resize(3) = _
.Cells(i, "B").Resize(3).Value
FER = FER + 3
End If
End If
Next
End With
' In Report Worksheet
With Mreport
.Protect Password:="rapid1"
MsgBox "End of Month Report Updated"
End With
ProcedureExit:
' Speed down
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
well this is embarrassing...
I tried many of your options above to no avail, I then went to paste something into my code and it pasted a job number which i thought was odd, but was actually the job no of where the code broke down. So i went to look at this field and found i had entered the date for that field as 07/02/19/, the final forward breaker was throwing a code error.
Deleted the / and re run the code and it worked perfectly with no debug error.
Thanks everyone for your help and advice, I will use your coding and feedback to improve this code and many more in the future
Many thanks again!
rookieerror!
Related
I am attempting to identify the row and column of which the intersection is the data I want to retrieve, much like an index match match formula in Excel. My method is find the data in the column, get the column letter, and do the same with the data in the row and retrieve the row number. The problem I am having is that I have to reference a cell in a separate workbook where the macro is located to open another spreadsheet's name that changes with the month. I'm sure this whole thing's method is not very good, advice would be appreciated!
Option Explicit
Sub RevenueTest()
'GVS1 Revenue Index Match Test
'DELETES & COPIES GVS1 revenue into P&R File
Dim GVS1 As String
GVS1 = ThisWorkbook.Sheets("Revenue").Range("v13")
Dim GVS1IS As String
GVS1IS = ThisWorkbook.Sheets("Revenue").Range("V7")
Dim GVS1Open As String
GVS1Open = Excel.Workbooks.Open(GVS1)
Dim Row As String
Row = Range("B5:B25").Find("Generation").Select.ActiveCell.Row
Dim Month As String
Month = ThisWorkbook.Sheets("Revenue").Range("V4")
Dim MonthActual As String
MonthActual = Month & " Actual"
Dim Column As String
Column = Range("A1:P15").Find(MonthActual).Select.ActiveCell.Column
Dim GVS1RowAndColumn As String
GVS1RowAndColumn = Column & Row
'OPENS / Indexes and Copies Revenue
Excel.Workbooks.Open (GVS1)
Columns("C:Q").EntireColumn.Delete
Range(GVS1RowAndColumn).Copy
'PASTES GVS1 revenue into P&R File
Dim Revenue As Worksheet
Set Revenue = ThisWorkbook.Sheets("Revenue")
ThisWorkbook.Activate
Revenue.Range("D3:D11").Find("Revenue").Select
Selection.End(xlToRight).Select
ActiveCell.Offset(0, 1).Range("A1").PasteSpecial xlPasteValues
End Sub
I'm not sure if I properly understood this line
My method is find the data in the column, get the column letter, and do the same with the data in the row and retrieve the row number.
But I do have a similar index match function in my stock, take a look and modify it to your needs.
Option Explicit
Sub Return_value()
Dim Rmrks As Range, Itm_Rng As Range
Dim ItmLstPR As Range, ItmLstCode As Range
On Error Resume Next
With Application
.EnableEvents = False
.ScreenUpdating = False
'they are table range btw like, "tabe_name[column_name]"
'in this range the return value will be pasted
Set Rmrks = .Range("Pip_Line[Remarks]")
'this range has the key word that needs to be matched
Set Itm_Rng = .Range("Pip_Line[Item_Code]")
' from "DMY_Pip_Line[Remarks]" range matched value will be returned
Set ItmLstPR = .Range("DMY_Pip_Line[Remarks]")
'we use "DMY_Pip_Line[Item_Code]" to match our keyword from "Pip_Line[Item_Code]" range
Set ItmLstCode = .Range("DMY_Pip_Line[Item_Code]")
'Return Remarks
Call Match_Value(ItmLstPR, Itm_Rng, ItmLstCode, Rmrks)
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
you can insert above code in a worksheet or in a module.
Paste below code in a module to get the final result.
Option Explicit
Public Sub Match_Value(ByVal ReturnVal As Range, ByVal LookupVal As Range, ByVal LookupRng As Range, ByVal PasteRng As Range)
Dim rng As Range, ResultRow As Long, foundcell As Range, ColmnDist As Long, FoundVal As String
'find column offset
ColmnDist = ReturnVal.Column - LookupRng.Column
ResultRow = PasteRng.Column - LookupVal.Column
On Error Resume Next
For Each rng In LookupVal
'return due placing location row
Set foundcell = LookupRng.Find(rng.Value, LookIn:=xlValues, Lookat:=xlWhole, MatchCase:=True)
'return value
FoundVal = foundcell.Offset(0, ColmnDist).Value
If Not foundcell Is Nothing And FoundVal <> vbNullString Then
rng.Offset(0, ResultRow).Value = FoundVal
End If
Next
End Sub
In order to understand your code I have rearranged it. In order for you to understand your code I have commented it. To be clear: this is your code, unchanged! We just study.
Sub RevenueTest()
'GVS1 Revenue Index Match Test
'DELETES & COPIES GVS1 revenue into P&R File
Dim Revenue As Worksheet
Dim GVS1 As String
Dim GVS1IS As String
Dim GVS1Open As String
Dim Row As String ' "Row" is an Excel object
Dim Month As String
Dim MonthActual As String
Dim Column As String ' "Column" is an Excel object
Dim GVS1RowAndColumn As String
GVS1 = ThisWorkbook.Sheets("Revenue").Range("V13")
GVS1IS = ThisWorkbook.Sheets("Revenue").Range("V7")
GVS1Open = Excel.Workbooks.Open(GVS1) ' the workbook is an object: can't assign to String
' "ActiveCell.Row" is a number: why assign to a string variable?
' "Row" is an object: can't be the name of a variable
' don't select anything: create a range object instead
' "Find" returns a range object if successful
' if unsuccessful attempting to access that range must fail
' since you don't specify any sheet, 'Range("B5:B25")' is presumed
' to be on the ActiveSheet
Row = Range("B5:B25").Find("Generation").Select.ActiveCell.Row
Month = ThisWorkbook.Sheets("Revenue").Range("V4")
MonthActual = Month & " Actual"
Column = Range("A1:P15").Find(MonthActual).Select.ActiveCell.Column
GVS1RowAndColumn = Column & Row
'OPENS / Indexes and Copies Revenue
Excel.Workbooks.Open GVS1 ' don't enclose arguments in parentheses
Columns("C:Q").EntireColumn.Delete ' columns are in the ActiveSheet
Range(GVS1RowAndColumn).Copy ' Range is on the ActiveSheet
'PASTES GVS1 revenue into P&R File
Set Revenue = ThisWorkbook.Sheets("Revenue")
ThisWorkbook.Activate ' no need to activate anything
Revenue.Range("D3:D11").Find("Revenue").Select ' no need to select anything
Selection.End(xlToRight).Select '
ActiveCell.Offset(0, 1).Range("A1").PasteSpecial xlPasteValues
End Sub
Then I tried to re-write your code so that it might work. You can see how far I got. Look for the specification of the search ranges. They are on the ActiveSheet. Which is the ActiveSheet? We have no clue. But the code shows you how to approach the subject.
Sub RevenueTest_2()
Dim GVS1Book As Workbook
Dim Revenue As Worksheet
Dim Fnd As Range ' result of 'Find'
Dim R As Long ' a row number
Dim C As Long ' a column number
Dim GVS1 As String
Dim GVS1IS As String
Dim Month As String
Set Revenue = ThisWorkbook.Sheets("Revenue") ' use "Set" to assign an object to a variable
With Revenue
GVS1 = .Range("V13").Value ' { always specify the property you want
GVS1IS = .Range("V7").Value ' { here it's the Value
Month = .Range("V4").Value ' "Month" is a string (like "April", not 4)
End With
Set Fnd = ActiveSheet.Range("B5:B25").Find("Generation")
If Fnd Is Nothing Then
MsgBox """Generation"" not found."
Exit Sub
Else
R = Fnd.Row
End If
Set Fnd = ActiveSheet.Range("A1:P15").Find(Month & " Actual")
If Fnd Is Nothing Then
MsgBox """" & Month & " Actual"" not found."
Exit Sub
Else
C = Fnd.Column
End If
Set GVS1Book = Workbooks.Open(GVS1) ' GVS1 must be a path & name
End Sub
When the other workbook is opened I gave up. At that moment Excel will make that workbook the ActiveWorkbook and the sheet which was active when that workbook was saved will become the ActiveSheet. Your code immediately starts deleting columns on that unknown sheet. I couldn't get myself to do that.
As you complete the code I started, just remember that you don't need to activate the other workbook. Excel has done it for you and will give you back your original view when you close that book. Consider using Application.ScreenUpdating = False not to show the active sheet. You don't need to select any worksheet on which you want to delete columns. But you do need to specify the worksheets on which you take action. I have strong doubt that my code looks for the two search criteria on the correct sheet. I specified ActiveSheet because that's what your code implied. So we're probably both wrong :-)
I have a sheet with data more then 30 000 rows and I want to copy all rows to a new excel file if column of a certain (for example "B") row contains certain values (list of these values will be in other sheet "Code").
So for example:
In sheet "Code" I have ten (could be even 30) different numbers (criteria) in column "A".
Start search to copy all rows (in new excel file) that contain any of these numbers from sheet "Code" in column "A".
Not very good at VBA yet but working on it:)
Thanks for everyone for help!
Filter By Multiple Criteria and Export to Another Workbook
Just to demonstrate why the question is not so well received. It's sort of 50 questions in one.
Adjust the values in the constants section, and you should be good to go.
"Sheet2" is actually your worksheet "Code". "Sheet1" is the first worksheet.
The Code
Option Explicit
Sub exportMultiToWorkbook()
' Error Handler
' Initialize error handling.
Const procName As String = "exportMultiToWorkbook"
On Error GoTo clearError ' Turn on error trapping.
' Constants
' Criteria
Const critName As String = "Sheet2"
Const critFirstCell As String = "A2"
' Source
Const srcName As String = "Sheet1"
Const srcFirstCell As String = "A1"
Const srcCritColumn As Long = 2
Dim wbs As Workbook
Set wbs = ThisWorkbook ' The workbook containing this code.
' Target
Const tgtFirstCell As String = "A1"
Dim tgtPath As String
' The same path as Source Workbook ('wbs'). Change if necessary.
tgtPath = wbs.Path & Application.PathSeparator & "Criteria"
' Other
Dim Success As Boolean
Dim AfterCop As Boolean
' Criteria
' Define Criteria Worksheet ('crit').
Dim crit As Worksheet
Set crit = wbs.Worksheets(critName)
' Define Criteria First Cell Range ('fcel').
Dim fcel As Range
Set fcel = crit.Range(critFirstCell)
' Define Criteria Processing Column Range ('pcr').
Dim pcr As Range
Set pcr = fcel.Resize(crit.Rows.Count - fcel.Row + 1)
' Define Criteria Last Non-Empty Cell Range ('lcel').
Dim lcel As Range
Set lcel = pcr.Find(What:="*", _
LookIn:=xlFormulas, _
SearchDirection:=xlPrevious)
' Validate Last Non-Empty Cell Range.
If lcel Is Nothing Then
GoTo ProcExit
End If
' Define Criteria Column Range ('cr').
Dim cr As Range
Set cr = crit.Range(fcel, lcel)
' Write values from Criteria Column Range to 1D Criteria Array ('Criteria'),
' probably using Criteria 2D Array ('Crit2D').
Dim Criteria As Variant
Dim i As Long
If cr.Rows.Count > 1 Then
' Criteria Column Range has multiple cells (rows).
' Write values from Criteria Range to Criteria 2D Array.
Dim Crit2D As Variant
Crit2D = cr.Value
' Write values from Criteria 2D Array to 1D Criteria Array.
ReDim Criteria(1 To UBound(Crit2D, 1))
For i = 1 To UBound(Crit2D)
Criteria(i) = CStr(Crit2D(i, 1)) ' AutoFilter prefers strings.
Next i
Else
' Criteria Column Range has one cell (row) only.
' Write the only value from Criteria Column Range to Criteria Array.
ReDim Criteria(1)
Criteria(1) = CStr(cr.Value) ' AutoFilter prefers strings.
End If
' Source
' Define Source Worksheet ('src').
Dim src As Worksheet
Set src = wbs.Worksheets(srcName)
' Define Source First Cell Range ('fcel').
Set fcel = src.Range(srcFirstCell)
' Define Source Last Cell Range ('lcel').
Set lcel = fcel.End(xlToRight).End(xlDown)
' Define Copy Range
Dim cop As Range
Set cop = src.Range(fcel, lcel)
' Turn off screen updating.
Application.ScreenUpdating = False
' Turn off filter, if on.
If src.FilterMode Then
cop.AutoFilter
End If
' Filter data. AutoFilter prefers the whole range.
cop.AutoFilter Field:=srcCritColumn, _
Criteria1:=Criteria, _
Operator:=xlFilterValues
' Enable the use of 'SafeExit' instead of 'ProcExit' after possible error.
AfterCop = True
' Target
' Add a new workbook.
With Workbooks.Add
' Copy Copy Range to the first sheet of a new workbook.
cop.Copy .Worksheets(1).Range(tgtFirstCell)
' I prefer to save this way; always a different file.
tgtPath = tgtPath & " " & Format(Now, "YYYYMMDD_HHMMSS")
.SaveAs Filename:=tgtPath, _
FileFormat:=xlOpenXMLWorkbook ' i.e. ".xlsx"
' If you prefer the file to have the same name and for it to be
' overwritten without Excel complaining, then rather use the following:
' Application.DisplayAlerts = False
' .SaveAs Filename:=tgtPath, _
' FileFormat:=xlOpenXMLWorkbook ' i.e. ".xlsx"
' Application.DisplayAlerts = True
.Close
End With
Success = True
SafeExit:
' Source
' Turn off filter.
cop.AutoFilter
wbs.Saved = True
' Turn on screen updating.
Application.ScreenUpdating = True
ProcExit:
' Inform user.
If Success Then
MsgBox Prompt:="Created file '" & tgtPath & "'.", _
Buttons:=vbInformation, _
Title:="Multiple Criteria Filter - Success"
Else
MsgBox Prompt:="Could not finish task.", _
Buttons:=vbCritical, _
Title:="Multiple Criteria Filter - Fail"
End If
Exit Sub
clearError:
Debug.Print "'" & procName & "': " & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
On Error GoTo 0 ' Turn off error trapping.
If Not AfterCop Then
GoTo ProcExit
Else
GoTo SafeExit
End If
End Sub
I understand you are new and don't want to discourage you from seeking help in the future. Please attempt to ask more specific questions in the future. For example, you could ask how to find out if the value of one cell matches the value of any cell in a range of cells. That said, I have the sense you didn't know where to start, so I'll give it a shot. VBasic 2008 already provided an excellent answer, and actually wrote code for you, which you should not expect. VBasic 2008's code is great but be a more than you need and also a bit much for a beginner to understand. In the code below, you really only need to modify the three "set" lines in the CopyFilteredDemo procedure.
Below is simple code that makes several simplifying assumptions. Based on your request, I assume this meets your needs. If not, add more specificity. Many of these limitations of the assumptions listed below are easily overcome, but I don't want to write code for the heck of it.
Either the source and destination workbooks are the same, or they are both open. (I only tested different sheets in the same workbook, but it should work across workbooks.)
The source and destination worksheets are not the same. An error is intentionally raised if they are the same.
The destination worksheet already exists.
$) The desitnation worksheet will be completely cleared and overwritten. Change True to False in CopyFilteredDemo so as to pass, so as to pass False to CopyFiltered.
Search only the first column of the source range for a an exact match in the filter range. Since the whole row is copied, it doesn't matter what column you set as the first column in fromRange. Just pick the column you wish to compare to values in filterRange.
Where not filtered out, the entire worksheet row will be copied.
No duplicates in filter criteria. I have not tested this to see if it causes duplicates in the destination worksheet.
Not performance tested on thousands of rows. If you see issues, first set Application.ScreenUpdating = False. Turn it on again at the end. Be sure you have error handling to turn in back on in case of an error. Otherwise ScreenUpdatingwill remain off, which you'll find is highly undesirable. If this is beyond your current comfort level, don't disable ScreenUpdating.
As an outline, the main procedure is CopyFiltered, which copies data from one sheet to another. This procedure calls the IsInRange function, which returns true if argument valueToFind exactly matches a value in the range specified by argument RangeToSearch. So, when comparing the source range (fromRange) to the filter criteria (filterRange), the first column of fromRange is compared. fromRange does not determine which columns are copied, since you requested to copy entire rows. Rather fromRange has 2 purposes. First, it determine the rows from which to copy. Second, the first column of fromRange is compared to the filterRange for a match.
I placed a good amount of comments in the code, so I hope it is relatively easy to understand.
Option Explicit
' Option Explicit must be the first line of code in the module.
' It forces you to declare every variable. It may seem a nuisance
' to a beginner, but you will quickly learn its value. It will
' keep you from spelling the same variable two ways and failing
' to understand why your code failed. There are other benefits
' that you'll pick up over time, such as conserving memory and
' forcing data typing.
Public Function IsInRange(ByVal valueToFind, ByVal RangeToSearch As Range)
' If any cell in RangeToSearch = valueToFind, return True
' Else return False.
Dim x
' If valueToFind is not in RangeToSearch, expect
' error 91. That's okay, we'll handle that error
' and return False. If we get a differnt error,
' we'll raise it.
On Error GoTo EH
x = RangeToSearch.Find(valueToFind)
On Error GoTo 0
' If we made it this far, we found it!
IsInRange = True
Exit Function
EH:
If Err.Number = 91 Then
' this error is expected if valueToFind is not in RangeToSearch
IsInRange = False
Err.Clear
Else
' Unexpected error.
Err.Raise Number:=Err.Number, Source:=Err.Source _
, Description:=Err.Description
End If
End Function
Sub CopyFiltered(ByVal fromRange As Range, ByVal toRange As Range _
, ByVal filterRange As Range _
, Optional clearFirst As Boolean = True)
' Arguments:
' fromRange: the full range from which to copy
' toRange: the top left cell fromRange will be pasted to the
' top left cell of toRange. The size of toRange
' is irrelevant. Only the top left cell is used
' for reference.
' fitlerRange: a range containing values with which to filter.
' clearFirst: if True, clear all content from range containing
' toRange before pasting new values.
Dim rng As Range, rowOffset As Integer
Dim rowNum As Integer, colNum As Integer, i As Integer
Dim errMsg As String, cell As Range
Set toRange = toRange.Cells(1, 1)
Set fromRange = fromRange.Columns(1)
' If fromRange and toRange are on the same worksheet,
' raise an exception.
If fromRange.Worksheet.Name = toRange.Worksheet.Name Then
errMsg = "fromRange and toRange cannot be on the same worksheet."
Err.Raise 1000, "CopyFiltered", errMsg
Exit Sub
End If
' Clear all content from the destination worksheet.
toRange.Worksheet.Cells.ClearContents
'
' Loop through each row of fromRange
rowOffset = -1
For i = 1 To fromRange.Rows.Count
Set cell = fromRange.Cells(i, 1)
Debug.Print cell.Address
' If the the cell in the first column of fromRange
' exaclty equals any cell in filterRange, proceed.
If IsInRange(cell.Value, filterRange) Then
' Add one to rowOffset, so we copy this row
' below the last pasted row of the sheet
' containing toRange
rowOffset = rowOffset + 1
cell.EntireRow.Copy toRange.Offset(rowOffset, 0).EntireRow
End If
Next i
End Sub
Sub CopyFilteredDemo()
Dim fromRange As Range, toRange As Range, filterRange As Range
' Set our to, from and filter ranges
Set fromRange = Sheets("Sheet1").Range("c10:c40")
Set toRange = Sheets("Sheet2").Range("A2")
Set filterRange = Sheets("Sheet1").Range("B2:B6")
' Run our filtered copy procedure.
CopyFiltered fromRange, toRange, filterRange, True
End Sub
For the above table in excel (please refer to the picture), I am building a Macro to automatically update the monthly balance. I have the data from January to December, for simplicity, I assume there are only Jan and Feb data.
By using VLOOKUP, I am able to append the Feb balance to the adjacent column of Jan Balance. However, what concerns me is the new account of existing client every month(e.g. Client B 2345675555 in Cell F8), because VLOOKUP will simply ignore it. I need to copy the data of new Client ID and paste them into a new row under the existing table. Please note that the number of accounts for each client will only increase. Really appreciate if you could provide me with a solution (achievable with VBA Macro) .
I imagined your "Jan" sheet, in fact, to be your "Master" sheet which would have all the months names in its header row, and these would also be the names of the worksheets in the same workbook from which you want to import the balances.
In consequence, I arrived at the idea that you could just double-click a month name on the "Master" sheet (name doesn't matter) and import the balances from the sheet whose name you clicked (name must match exactly) into the column that you clicked. The code below does exactly that. Like in VLOOKUP, the sequence in which that sheet lists the client names is immaterial. But new names will be added at the bottom. Before the next run you can sort them differently. Unlike when you use VLOOKUP, there is no permanent link between the sheets. Here's the code.
Option Explicit
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, _
Cancel As Boolean)
' 084
Const TriggerRow As Long = 1 ' change to suit
Dim TabName As String ' worksheet name to update from
Dim WsS As Worksheet ' data source: Tabname
With Target
If .Row = TriggerRow Then ' skip if row is different
TabName = .Value
If Len(TabName) Then ' if the clicked cell isn't blank
On Error Resume Next
Set WsS = Worksheets(TabName)
If Err Then
MsgBox "The worksheet """ & TabName & """ hasn't been set up yet.", _
vbInformation, "Invalid tab name"
Else
Application.ScreenUpdating = False ' this is faster
TransferBalances Target, WsS
Application.ScreenUpdating = True
End If
End If
Err.Clear
Cancel = True
End If
End With
End Sub
Private Sub TransferBalances(Target As Range, _
WsS As Worksheet)
' 084
' the same columns are used in both the Master and all Source sheets
' columns need not be positioned in numeric sequence (1 = "A", 2="B" etc)
Const NameClm As Long = 1 ' specify the column where the Client names are
Const IdClm As Long = 2 ' specify the column where the Client IDs are
Const BalClm As Long = 3 ' specify the column where the balance are
Dim IdRng As Range ' existing IDs in "Master" sheet
Dim SrcRng As Range ' cell range containing source data
Dim Src As Variant ' value of SrcRng (for faster access)
Dim R As Long ' loop counter: Rows
Dim Rt As Long ' target row
With Target.Worksheet
Set IdRng = .Range(.Cells(1, IdClm), .Cells(.Rows.Count, IdClm).End(xlUp))
' the range will not be extended to include added items
End With
With WsS
R = .Cells(.Rows.Count, IdClm).End(xlUp).Row ' last used row (IdClm)
' row 2 is the first row with data to be transferred
' copy all columns from A to the last one used in row 1
Set SrcRng = .Range(.Cells(2, 1), _
.Cells(1, .Columns.Count).End(xlToLeft).Offset(R - 1))
End With
Src = SrcRng.Value
For R = 1 To UBound(Src)
On Error Resume Next
Rt = WorksheetFunction.Match(Src(R, IdClm), IdRng, 0)
With Target.Worksheet
If Err Or (Rt = 1) Then ' disallow match in header row
Rt = .Cells(.Rows.Count, IdClm).End(xlUp).Row + 1
.Rows(Rt - 1).Copy
.Rows(Rt).Insert Shift:=xlDown ' copy formats from above
Application.CutCopyMode = False
.Cells(Rt, NameClm).Value = Src(R, NameClm)
.Cells(Rt, IdClm).Value = Src(R, IdClm)
End If
.Cells(Rt, Target.Column).Value = Src(R, BalClm)
End With
Next R
Err.Clear
End Sub
Since the code responds to the double-click event it must be installed in your Master worksheet's code module. This location is essential because the double-click won't be noticed anywhere else in your workbook. Only by pasting the code in that module can you enjoy the promised automation.
Hello all this is my first question so I will try my best to format this best I can.
Quick description without specific cell names below
I am trying to write a macro where a user enters a value(X) and a macro searches a range of cells for a value(X), and then the macro returns the cell values in the 3 spaces next to wherever the location of value(X) is.
A couple things that are making this impossible to solve are the fact that the user inputs the value on Sheet1 and the value is moved to Sheet2 by a formula, I can't seem to figure out how to use Find where the values I am searching for isn't already defined in the macro.
The other thing making this difficult is that the range is not strictly definable either, as the list could be longer or shorter than it currently is, and I can't know when it will change. So the range of the search has to start based on which List is input by the User and needs to go until it hits a blank spot.
For example: Range.("C7:D10") wont work because the user could enter new info that changes the working range as described below.
Below is a screenshot with further explanation
https://i.stack.imgur.com/wlnhg.jpg
So in this screenshot the cells C3 and D3 are imported values from Sheet1.
C3 is (=Sheet1!B2)
D3 is (=Sheet1!B3)
The idea is that the macro runs and searches down column A till it has a match with C3.
Then the search function moves over two cells and searches down till it has a match with D3 or until it hits an empty space.
I don't know how to ask a macro to search based on an imported value, and I don't know how to ask it to search this weird certain range I need. The idea is that someone at my work could come along and add a row below C10 and add the necessary information and the macro would still work and search to C11 and there would be a blank space after to tell the macro to stop.
After the search finds a match for D3 it would return the values adjacent to the match to the corresponding cells at the top, E3, F3, and G3.
I hope this question is asked in a way that people can understand, I am very tired so can't tell if I wrote something that makes sense. Thank you for reading my post, y'all are the best!!
Search Twice
Workbook Download (Dropbox)
Sub SearchTwice()
Const cSheet As String = "Sheet2" ' Source Worksheet Name
Const cList As String = "C3" ' List Cell Range Address
Const cName As String = "D3" ' Name Cell Range Address
Const cListCol As String = "A" ' List Column Letter
Const cNameCol As String = "C" ' Name Column Letter
Const cFirst As Long = 6 ' First Row
Const cCol As Long = 3 ' Number of Columns
Dim rng1 As Range ' Find List Cell Range
' Found Name Cell Range
Dim rng2 As Range ' Next List Cell Range
' Name Search Range
Dim strList As String ' List
Dim strName As String ' Name
' In Source Worksheet
With ThisWorkbook.Worksheets(cSheet)
' Write from List Cell Range to List.
strList = .Range(cList)
' Write from Name Cell Range to Name.
strName = .Range(cName)
' Check if Cell Ranges do NOT contain data.
If strList = "" Or strName = "" Then ' Inform user.
MsgBox "Missing List or Name.", vbCritical, "Missing data"
Exit Sub
End If
' In List Column
With .Columns(cListCol)
' Create a reference to Find List Cell Range (rng1) containing
' List (strList).
Set rng1 = .Find(strList, .Cells(cFirst - 1), xlValues, xlWhole)
' Check if List has not been found.
If rng1 Is Nothing Then ' Inform user and exit.
MsgBox "The list '" & strList & "' has not been found", _
vbCritical, "List not found"
Exit Sub
End If
' Create a reference to Next List Cell Range (rng2).
Set rng2 = .Find("*", .Cells(rng1.Row), xlValues, xlWhole)
End With
' In Name Column
With .Columns(cNameCol)
' Check if the row of Next List Cell Range (rng2) is greater than
' the row of List Cell Range (rng1) i.e. if a cell with a value
' has been found below List Cell Range (rng1) in List Column.
If rng2.Row > rng1.Row Then ' Next List Cell Range FOUND.
' Create a reference to Name Search Range (rng2) which spans
' from the cell below Find List Cell Range (rng1) to the cell
' above the Next List Cell Range (rng2), but in Name Column.
Set rng2 = .Cells(rng1.Row + 1).Resize(rng2.Row - rng1.Row - 1)
Else ' Next List Cell Range NOT found.
' Create a reference to Name Search Range (rng2) which spans
' from the cell below Find List Cell Range (rng1) to the bottom
' cell, but in Name column.
Set rng2 = .Cells(rng1.Row + 1).Resize(.Rows.Count - rng1.Row)
End If
End With
' In Name Search Range (rng2)
With rng2
' Create a reference to Found Name Cell Range (rng1).
Set rng1 = .Find(strName, .Cells(.Rows.Count), xlValues, xlWhole)
End With
' Check if Name has not been found.
If rng1 Is Nothing Then ' Inform user and exit.
MsgBox "The name '" & strName & "' has not been found", _
vbCritical, "Name not found"
Exit Sub
End If
' Remarks:
' Source Range is calculated by moving the Found Name Cell Range (rng1)
' one cell to the right and by resizing it by Number of Columns (cCol).
' Target Range is calculated by moving the Name Cell Range one cell
' to the right and by resizing it by Number of Columns (cCol).
' Copy values of Source Range to Target Range.
.Range(cName).Offset(, 1).Resize(, cCol) _
= rng1.Offset(, 1).Resize(, cCol).Value
End With
' Inform user of succes of the operation.
MsgBox "The name '" & strName & "' was successfully found in list '" & _
strList & "'. The corresponding data has been written to the " _
& "worksheet.", vbInformation, "Success"
End Sub
One reason for being tired is that you tried to go for the kill before you had set up for slaughter. The solution below took an hour to prepare and 10 minutes to encode. Paste the entire code in a standard code module and call the function MatchRow either from the Immediate window (? MatchRow) or from your own code as shown in the test proc further down.
Option Explicit
Enum Nws ' worksheet navigation
' 01 Mar 2019
NwsCriteriaRow = 3
NwsList = 1 ' Columns: (1 = A)
NwsID = 3
NwsNumber ' (undefined: assigns next integer)
End Enum
Function MatchRow() As Long
' 01 Mar 2019
' return 0 if not found
Dim Ws As Worksheet
Dim Rng As Range
Dim R As Long
' The ActiveWorkbook isn't necessarily ThisWorkbook
Set Ws = ActiveWorkbook.Worksheets("Sheet2") ' replace tab's name here
With Ws
Set Rng = .Range(.Cells(NwsCriteriaRow, NwsList), .Cells(.Rows.Count, NwsList).End(xlUp))
R = FindRow(.Cells(NwsCriteriaRow, NwsID).Value, Rng, True)
If R Then ' skip if no match was found
Set Rng = .Cells(R + 1, NwsID)
Set Rng = .Range(Rng, Rng.End(xlDown))
MatchRow = FindRow(.Cells(NwsCriteriaRow, NwsNumber).Value, Rng)
End If
End With
End Function
Private Function FindRow(Crit As Variant, _
Rng As Range, _
Optional ByVal SearchFromTop As Boolean) As Long
' 01 Mar 2019
' return 0 if not found
Dim Fun As Range
Dim StartCell As Long
With Rng
If SearchFromTop Then
StartCell = 1
Else
StartCell = .Cells.Count
End If
Set Fun = .Find(What:=Crit, _
After:=.Cells(StartCell), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
MatchCase:=False)
End With
If Not Fun Is Nothing Then FindRow = Fun.Row
End Function
The function MatchRow returns the row number of Sheet2 where D3 is found, searching only that part of column D which belongs to the list identified in C3. The function returns 0 if no match was found, either of the list or the ID.
You didn't specify what you want to do with the found row. The procedure below will return data from that row. You might use the capability to address the cells to write to them instead.
Private Sub RetrieveData()
Dim R As Long
R = MatchRow
MsgBox "ID = " & Cells(R, NwsID).Value & vbCr & _
"Number = " & Cells(R, NwsNumber).Value
End Sub
Being intended for testing only the above proc doesn't specify the worksheet and, therefore, returns data from the ActiveSheet, presumed to be Sheet2.
VBA Solution
I think the non-VBA solution is ideal here, but I will leave this here separately just in case. This should work for your situation assuming no values in your tables are blank.
Sub Test()
Dim ws As Worksheet: Set Worksheet = ThisWorkbook.Sheets("Sheet2")
Dim iList As Range, iName As Range
Dim aLR As Long, cLR As Long
aLR = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set iList = ws.Range("A1:A" & aLR).Find(ws.Range("C3"), LookIn:=xlWhole)
If Not iList Is Nothing Then
cLR = iList.Offset(0, 2).End(xlDown).Row
Set iName = ws.Range(ws.Cells(iList.Row, 3), ws.Cells(cLR, 3)).Find(ws.Range("C4"), LookIn:=xlWhole)
If Not iName Is Nothing Then
ws.Range("E3:G3").Value = iName.Offset(0, 1).Resize(1, 3).Value
End If
End If
End Sub
Non VBA Solution
Convert your two list ranges to tables
Change the name of your tables by (Formulas Tab > Name Manager > Select Table/Change Name). Specifically, you will want to change the names to your desired list name. (Table 1 Name = List1 & Table 2 Name = List2)
Next, drop these formulas inside E3, F3, & G3
E3 = VLOOKUP(D3, Indirect(C3), 2, 0)
F3 = VLOOKUP(D3, Indirect(C3), 3, 0)
G3 = VLOOKUP(D3, Indirect(C3), 4, 0)
This wil update dynamically as your table sizes expand. you can also add as many tables as you'd like and this will continue to work.
In use, it looks something like below
My last suggestion would be to nest each formula above inside an IFERROR()
I have column K in "filter" sheets that need to be compare with column A in "Active_Buy", "Active_Others" and "Active_Make" sheets accordingly.
First it need to be compare with active_buy sheets. if there is value that in column K (filter sheet) but not in column A (active_Buy sheet), then it need to hold that value and compare it with column A (active_others sheets). If also didnt match, it need to compared with column A (Active_Make sheets).
So, if there is no any match, then the value need to be paste in new sheets name (Unmatched Part No).
I already search everywhere but only can find code that can only compare 2 worksheets only but not more than that.
'Below is the code that i found but only compared two worksheets only
' the concept just same like this but need to hold unmatch value and compare to next worksheet and so on.
Sub compare()
Sheets(3).Activate 'Go to sheet 3
Cells.Clear 'and clear all previous results
Range("a1").Select 'set cursor at the top
Sheets(1).Activate 'go to sheet 1
Range("a1").Select 'begin at the top
Dim search_for As String 'temp variable to hold what we need to look for
Dim cnt As Integer 'optional counter to find out how many rows we found
Do While ActiveCell.Value <> "" 'repeat the follwoing loop until it reaches a blank row
search_for = ActiveCell.Offset(0, 1).Value 'get a hold of the value in column B
Sheets(2).Activate 'go to sheet(2)
On Error Resume Next 'incase what we search for is not found, no errors will stop the macro
Range("b:b").Find(search_for).Select 'find the value in column B of sheet 2
If Err <> 0 Then 'If the value was not found, Err will not be zero
On Error GoTo 0 'clearing the error code
Sheets(1).Activate 'go back to sheet 1
r = ActiveCell.Row 'get a hold of current row index
Range(r & ":" & r).Select 'select the whole row
cnt = cnt + 1 'increment the counter
Selection.Copy 'copy current selection
Sheets(3).Activate 'go to sheet 3
ActiveCell.PasteSpecial xlPasteAll 'Past the entire row to sheet 3
ActiveCell.Offset(1, 0).Select 'go down one row to prepare for next row.
End If
Sheets(1).Activate 'return to sheet 1
ActiveCell.Offset(1, 0).Select 'go to the next row
Loop 'repeat
Sheets(3).Activate 'go to sheet 3 to examine findings
MsgBox "I have found " & cnt & " rows that did not exist in sheet 2"
End Sub
I'd use a For Each loop to run through the values on the 'Filter' sheet, set ranges on each of the other sheets, then check in each of the ranges. I've tested this code and it seems to do the trick. I've commented so you can see what's going on at each line.
(You'll need to adjust the sheet names to match you own, and adjust Application settings to make things run faster if you've got a lot of data.)
Sub compareColumns()
Dim lastRow1, lastRowAB, lastRowAO, lastRowAM, lastRowUMPN As Long
Dim rng1, rngAB, rngAO, rngAM As Range
Dim cell As Range
Dim found As Range
' Define our last rows for each sheet
lastRow1 = ThisWorkbook.Worksheets("FilterSheet").Range("K" & Rows.Count).End(xlUp).Row
lastRowAB = ThisWorkbook.Worksheets("ActiveBuy").Range("A" & Rows.Count).End(xlUp).Row
lastRowAO = ThisWorkbook.Worksheets("ActiveOthers").Range("A" & Rows.Count).End(xlUp).Row
lastRowAM = ThisWorkbook.Worksheets("ActiveMake").Range("A" & Rows.Count).End(xlUp).Row
lastRowUMPN = ThisWorkbook.Worksheets("UnmatchedPartNo").Range("A" & Rows.Count).End(xlUp).Row
' Set the ranges that we'll loop through
Set rng1 = ThisWorkbook.Worksheets("FilterSheet").Range("K1:K" & lastRow1)
Set rngAB = ThisWorkbook.Worksheets("ActiveBuy").Range("A1:A" & lastRowAB)
Set rngAO = ThisWorkbook.Worksheets("ActiveOthers").Range("A1:A" & lastRowAO)
Set rngAM = ThisWorkbook.Worksheets("ActiveMake").Range("A1:A" & lastRowAM)
' Loop through each cell in the filtered sheet
For Each cell In rng1
' Try to find the value in ActiveBuy sheet
Set found = rngAB.Find(cell.Value)
' If not found, try the next sheet
If found Is Nothing Then
Set found = rngAO.Find(cell.Value)
' If not found, try the next sheet
If found Is Nothing Then
Set found = rngAM.Find(cell.Value)
' If still not found, copy to the value to the 'Unmatched Parts' sheet
If found Is Nothing Then
ThisWorkbook.Worksheets("UnmatchedPartNo").Range("A" & lastRowUMPN + 1).Value = cell.Value
MsgBox "I have found a value " & cell.Value & " that did not exist in any sheets."
End If
End If
End If
' Reset 'found' to equal nothing for the next loop
Set found = Nothing
Next
End Sub
Here's a sub that takes 2 parameters;
A cell that has the value to search for, and a number indicating the sheet to search in.
When the sub doesn't find the value in neither of the sheets, it adds a new sheet "Unmatched Part No" if it doesn't exist and adds the value that's not found in column A in that sheet:
Sub searchSheet(ByVal searchFor As Range, sheetNum As Integer)
Dim sheetsArr As Variant
sheetsArr = Array("Active_Buy", "Active_Others", "Active_Make", "Unmatched Part No") 'You can change the names of your sheets here
If sheetNum = 3 Then 'When we reach the last sheet in our array, then we haven't find a match in neither of the previous sheets
Dim ws As Worksheet, wsExist As Boolean, lastRow As Integer
wsExist = False
'Check if the sheet "Unmatched Part No" exists
For Each ws In Worksheets
If ws.Name = sheetsArr(3) Then
wsExist = True
Exit For
End If
Next ws
'If the sheet "Unmatched Part No" doesn't exist add one with this name
If Not (wsExist) Then ThisWorkbook.Sheets.Add(after:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count)).Name = sheetsArr(3)
lastRow = ThisWorkbook.Sheets(sheetsArr(3)).Cells(Rows.Count, "A").End(xlUp).Row 'last used row in column A in the unmatched sheet
ThisWorkbook.Sheets(sheetsArr(3)).Range("A" & lastRow + 1).Value2 = searchFor.Value2 'append the unfound value in column A
'MsgBox "New value" & searchFor.Value2 & "appended to 'Unmatched Part No' A" & lastRow + 1
Exit Sub
End If
Dim search 'Search should be of a variant type to accept errors given by the match function
search = Application.Match(searchFor.Value2, ThisWorkbook.Sheets(sheetsArr(sheetNum)).Range("A:A"), 0)
If IsError(search) Then searchSheet searchFor, sheetNum + 1 'When match doesn't find the searchFor value, Search is an #N/A error, then search in the next sheet
End Sub
And you need another sub to call the first one passing each cell of column K of filter sheet to the first sub. Here it is:
Sub lookInSheets()
Dim lastRw As Integer, ctrlCol As Range
lastRw = ThisWorkbook.Sheets("filter").Cells(Rows.Count, "K").End(xlUp).Row 'To abbreviate the search to just the filled cells in column K
Set ctrlCol = ThisWorkbook.Sheets("filter").Range("K1:K" & lastRw)
For Each ctrlCell In ctrlCol
searchSheet ctrlCell, 0
Next ctrlCell
End Sub
Copy both subs in a new module and run the second one to achieve your goal.