I extract a date from sheet "A" to find in a sheet "B".
I have the same type of date in each sheet (type 7) and the date exists.
With the code below I have
Error 91 : Object variable or With undefined block variable
Sub SearchDate()
lastColTraining = ActiveSheet.UsedRange.SpecialCells(xlCellTypeLastCell).Column
lastLetterTraining = Split(Cells(1, lastColTraining).Address, "$")(1)
Set allTraining = Range("K3:" & lastLetterTraining & "7")
For Each training In allTraining.Columns
trainingDate = training.Rows(4)
With Worksheets("B")
lastRow = .Range("A" & .Rows.Count).End(xlUp).Row
allDate = .Range("A2:A" & lastRow)
firstRowDate = allDate.Find(What:=trainingDate, After:=.Range("A" & lastRow)).Row
End With
Next training
End Sub
I scoured many forums and tried different solutions without finding an answer.
There are a few things going wrong here:
First: Declare your variables, it's even best to use Option Explicit on top of your module to actually make you not forget any. Otherwise VBA will try to make an educated guess which will be a Variant type of the date type.
Second I would try to avoid ActiveSheet but instead use a CodeName. For example Sheet1.Range("..."). This post on SO can clarify a thing or two on this matter.
Third, UsedRange is not the most reliable way to return a last used column. Instead I would go with something like:
With Sheet1 'The explicit sheet reference from the first point
lastColTraining = .Cells(1, .Columns.Count).End(xlToLeft).Column
End with
Fourth: You don't really need the column letter to refer to the column. There are other ways, for example using .Cells within a range. You could use:
With Sheet1 'The explicit sheet reference from the first point
lastColTraining = .Cells(1, .Columns.Count).End(xlToLeft).Column
Set allTraining = .Range(.Cells(3,11),.Cells(lastColTraining,11))`
End with
Fifth: If you have a Range object, you want to (most likely) Set it as a Range object. Otherwise (as per my first point) Excel will make an educated guess and in your case will return an array when you write: allDate = .Range("A2:A" & lastRow), instead use: Set allDate = .Range("A2:A" & lastRow)
Sixth: As per #SiddharthRout his comment, you'll recieve an error once your value isn't found. You can test that first trying to Set a FoundRange and check if it's not nothing.
Considering all the above, your code would run smoother using:
Option Explicit
Sub SearchDate()
Dim lastColTraining As Long, lastRow As Long, firstRowDate
Dim allTraining As Range, training As Range, allDate As Range, FoundCell As Range
Dim trainingDate As Variant
With Sheet1 'Change according to your sheets CodeName
lastColTraining = .Cells(1, .Columns.Count).End(xlToLeft).Column
Set allTraining = .Range(.Cells(3, 11), .Cells(7, lastColTraining))
For Each training In allTraining.Columns
trainingDate = training.Rows(4)
With Worksheets("B")
lastRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set allDate = .Range("A2:A" & lastRow)
Set FoundCell = allDate.Find(What:=trainingDate, AFter:=.Range("A" & lastRow))
If Not FoundCell Is Nothing Then firstRowDate = FoundCell.Row
End With
Next training
End With
End Sub
I'm just not sure what you want with trainingDate = training.Rows(4). If you just interested in the 7th row of each column, then refer to that Range instead. Neither am I sure what your goal is with the code, but hopefully you can get it to work now.
Related
Attempting to write some vba but not having much luck. I have column A with a whole list of values that I am counting and looping through. For Each value in column A, there can be a match in range C:D. If a value in column A matches a value in column C. I want to insert the corresponding value in column D below the Column A value. I am not too certain on what my IF then statement should look like. I have my counter and loop... I am just unsure where to go with the middle portion of the code.
Sub SetListOrder()
Dim wp As Worksheet
Dim ef As Long
Set wp = Workbooks("Packing Slip FIXED").Worksheets("Locate Order")
ef = wp.Range("A" & Rows.Count).End(xlUp).Row
For i = 1 To ef
IF (UNSURE WHAT TO PLACE HERE!) THEN
Next i:
End Sub
Edit: adding sample data
Sample Data screenshot
In this example, I would like to insert a new row under the value in "A" where A=C. ie. Range in column "A" = Range in Column "C". I would like to then insert the value from "D". The new order in rows 4-6 would be:
Range
Order Group 1
2604291
I already have written the code to manually move my sheets around to follow the specific order once I am able to get the names in said order.
I agree with #BigBen that the simpler approach would be to insert a formula in column D that only replicates the column A value when a match is detected. Such a formula would probably look like the following -
=IF($A1=$C1,$A1,"")
This would be copied into cell D2 of your column and copied down as far as needed.
However, if you did want to achieve this with VBA and I have noted you used the word insert a value (as opposed to simple enter a value or copy & paste a value) then this could be your approach -
Sub SetListOrder()
Dim wp As Worksheet
Dim ef As Long
Dim i As Long
Set wp = Workbooks("Packing Slip FIXED").Worksheets("Locate Order")
ef = wp.Range("A" & Rows.Count).End(xlUp).Row
For i = ef To 1 Step -1
If wp.Range("A" & i).Value = wp.Range("C" & i).Value Then
wp.Range("D" & (i + 1)).Insert xlShiftDown
wp.Range("D" & (i + 1)).Value = wp.Range("A" & i).Value
Else
End If
Next i
End Sub
This approaches the problem in reverse by going up your column instead of going down. Note that by inserting your data, will cause each previous value to move down as well. If you don't want this, then simply erase the .Insert line and it will enter the value instead of inserting a cell.
Modify the below code and use:
Formula:
=IFNA(VLOOKUP(A1,$C$1:$D$5,2,0),"Missing")
VBA Code:
Option Explicit
Sub test()
Dim rngSearch As Range, rngFound As Range
Dim LastRowA As Long, LastRowC As Long, i As Long
With ThisWorkbook.Worksheets("Sheet1")
LastRowA = .Cells(.Rows.Count, "A").End(xlUp).Row
LastRowC = .Cells(.Rows.Count, "C").End(xlUp).Row
Set rngSearch = .Range("C1:D" & LastRowC)
For i = 1 To LastRowA
Set rngFound = rngSearch.Find(.Range("A" & i).Value, LookIn:=xlValues, Lookat:=xlWhole)
If Not rngFound Is Nothing Then
.Range("B" & i).Value = .Range("D" & rngFound.Row).Value
Else
.Range("B" & i).Value = "Missing"
End If
Next i
End With
End Sub
Result:
I'm new to VBA, so your help is really appreciated. My code is taking a partial value from a string in each row of a range (in sheet "Lookup"), looking up this partial value in a range (in sheet "CF"), and outputting the value corresponding to the partial value (but located in a different column) to a specific range in sheet "Lookup". The following code works for the very first instance of a loop and then spits out an error "Object variable not set or With missing". It looks like the issue is Val variable, but I can't get this to work. Could you please help me understand, if I'm not defining Val correctly? I'm using Val to store the row number that corresponds to the position of the value I'm trying to look up. Perhaps there's a faster way to do this lookup (the value of interest is in a different column from lookup value)? Thank you!
Sub CF_Lookup()
Dim Lastrow As Long
Dim i As Integer
Dim Str As String
Dim lRange As Range
Dim Val As Long
With Sheets("Lookup")
Lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 2 To Lastrow
With .Range("A" & i & ":A" & Lastrow)
Str = Left(Cells(i, 1).Value, 8)
Set lRange = Worksheets("CF").Range("A:M").Find(Str, Lookat:=xlPart)
Val(i) = lRange.Row
Worksheets("Lookup").Range("P" & i).Value = Worksheets("CF").Range("M" & Val(i)).Value
End With
Next i
End With
End Sub
i have an excel/vba issue which seems to occur in excel2010 but not excel2016. for me it is a non comprehensible conversion between a1 and r1c1 notation.
i have a range that is dynamic
Dim rng As Range
rng = Application.Range("worksheet!A4:A" & _
Worksheets("worksheet").Range("A" & rows.Count).End(xlUp).Row
also i have a name-variable (called "Norm") i use as a dropdown option in cells and would like to update it according to the dynamic range using
With Application.Names("Norm")
.Name = "Norm"
.RefersTo = rng.Address
.Comment = ""
End With
both run on Workbook_BeforeSave.
when saving while in vba editing mode everything works as expected, the name-variable has the correct range in a1-notation and the content of Norm is according to the range.
but saving in pure excel-mode results in the range in r1c1-notation which can not be processed by the name-variable leaving it empty. unfortunately i can't find any explanation or solution for that. is this an excel2010 issue or what can i do about that?
Range is a member of worksheet.
Names us a member of workbook.
You Set a range object.
RefersTo should point to the range object, not its address.
Revised code:
Dim rng As Range
WITH THISWORKBOOK.WORKSHEETS("worksheet")
SET rng = .Range(.cells(4, "a"), .cells(.rows.count, "a").end(xlup))
end with
With thisworkbook.Names("Norm")
.Name = "Norm" 'totally redundant, it already has a name identified in the line above
.RefersTo = rng 'no address, just rng
.Comment = ""
End With
Name has two properties RefersTo and RefersToR1C1, which means that you should assign appropriate address style. If you want to be sure you get correct notation, you should use ReferenceStyle parameter:
Names("Norm").RefersTo = "=" & Range("A1").Address(ReferenceStyle:=xlA1)
Names("Norm").RefersToR1C1 = "=" & Range("A1").Address(ReferenceStyle:=xlR1C1)
First, you are not setting your rng object correctly:
rng = Application.Range("worksheet!A4:A" & _
Worksheets("worksheet").Range("A" & rows.Count).End(xlUp).Row
should give you an error, you need to Set your rng object, see code below:
Dim Sht As Worksheet
Dim Rng As Range, LastRow As Long
' set the worksheet object
Set Sht = ThisWorkbook.Worksheets("worksheet")
With Sht
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row ' get last row with data in column A
' set the Range object
Set Rng = .Range("A4:A" & LastRow)
End With
' updating the range that "Norm" refres to
With ThisWorkbook.Names("Norm")
.RefersTo = Rng
End With
Can anyone explain why the string in D2 is being built out of order in the first loop?
This is only happening for the first search value, Dom. The rest of the strings are being built in the order in which they appear (see Column B). I treid adding SearchDirection:= xlNext but the output remained the same with or without that bit of code.
In the photo, Column A:B are the raw data and Column C:D are the output from macro.
The cell in question is D2. It should show USD/EUR/GBP instead of EUR/GBP/USD
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim FoundName As Range, SearchRange As Range, Names As Range, Name As Range
Dim MyString As String, i As Long
ws.Range("A1:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row).AdvancedFilter Action:=xlFilterCopy, CopyToRange:=ws.Range("C1"), Unique:=True
Set SearchRange = ws.Range("A2:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row)
Set Names = ws.Range("C2:C" & ws.Range("C" & ws.Rows.Count).End(xlUp).Row)
For Each Name In Names
Set FoundName = SearchRange.Find(Name, SearchDirection:=xlNext)
For i = 1 To Application.WorksheetFunction.CountIf(SearchRange, Name)
MyString = MyString & FoundName.Offset(, 1) & "/"
Set FoundName = SearchRange.FindNext(FoundName)
Next i
Name.Offset(, 1) = Left(MyString, Len(MyString) - 1)
MyString = ""
Next Name
According to Microsoft documentation about the Range.Find method, the After parameter is:
The cell after which you want the search to begin. This corresponds to the position of the active cell when a search is done from the user interface. Notice that After must be a single cell in the range. Remember that the search begins after this cell; the specified cell isn't searched until the method wraps back around to this cell. If you do no specify this argument, the search starts after the cell in the upper-left corner of the range.
(Emphasis mine)
In your code, you set the range you're searching like:
Set SearchRange = ws.Range("A2:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row)
which means that the first cell actually searched will be Range("A3"). There are two ways of fixing this:
Expand the search range to include "A1", so the default start is "A2"
Specify the After parameter as the last cell in the range. Since the search wraps back around to the first cell after reaching the last cell.
In your scenario, I believe the simplest solution would be (1). This can be done by simply adjusting your code line to read:
Set SearchRange = ws.Range("A1:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row)
All of the below methods have failed to reference the last column. What is a viable method?
For example 'Columns("1:" & llc.Address & "").Select through 'Columns("E:" & llc & "").Selectare trying to select sayColumns("E:N")`. But the last column is dynamic. In one instance it's column N, and in another application of the macro it's column AP.
Sub RestorePivtTable()
Set ws = ThisWorkbook.Sheets("sheet1")
llc = ws.Cells(2, ws.Columns.count).End(xlToLeft).Column
'Columns("1:" & llc.Address & "").Select
'Columns(1, llc).Select
'Range(Columns(1), Columns(llc)).Select
'Columns("E:" & Cells(3, llc).Address & "").Select
'Range("1:" & Cells(3, lc).Address & "").Select
'Range(Cells(1, 1).Address, Cells(3, llc).Address).Select
'Columns("E:" & llc & "").Select
Selection.ClearFormats
End Sub
If you are using your above method you will need to find the correct row to use. ie: you will need to know the row in which the data appears in the right-most column. If you want the last column out of anything, try:
ws.usedrange.columns
This just gives the number of columns in the used range of a sheet, which is defined as A1:[The bottom right cell which contains either values or formatting].
Note that this will not work if, say, you have formatting in E10, but you want to get column D, because D is the last column which has a value [ie: you want to exclude consideration of formatted cells].
I generally use this method, although you have to put checks in in case the sheet is empty (you can't return column 0).
Sub FindLastColumn()
Dim wrkSht As Worksheet
Set wrkSht = ThisWorkbook.Worksheets("Sheet1")
MsgBox wrkSht.Cells.Find(What:="*", After:=wrkSht.Cells(1, 1), SearchDirection:=xlPrevious).Column
End Sub
Basic example of how to find the last column in your sheet - I've included an If block in case the sheet is empty, but then I don't know why you would run this code on an empty sheet anyway...
Sub SO()
Dim lastCol As Integer
Dim lastCell As Excel.Range
'// Assuming the variable 'ws' has already been dimensioned and initialised
On Error Resume Next
Set lastCell = ws.Cells.Find(What:="*", After:=ws.Range("A1"), SearchDirection:=xlPrevious)
On Error GoTo 0
If lastCell Is Nothing Then
lastCol = 1
Else
lastCol = lastCell.Column
End If
MsgBox lastCol
End Sub
UsedRange can be unreliable in this instance, because it can still contain cells that were previously used but are now blank - and I'm guessing you're not interested in these cells. Using the Cells.Find() method means that you don't have to know which row will coincide with the last column (which is needed for Columns.Count.End(xlToLeft) method) so this is a bonus too when working with irregular data sets.
Trying to "read between the lines" of your code, I suspect that this is what you are after:
Public Sub RestorePivtTable()
Sheet1.Cells(2, Sheet1.Columns.Count).End(xlToLeft).EntireColumn.ClearFormats
End Sub
This will work as long as there are data in row 2.
Thanks everyone for your help. The below function and macro solved the issue of converting a column number reference into a letter reference:
Function GetColumnLetter(colNum As Long) As String
Dim vArr
vArr = Split(Cells(1, colNum).Address(True, False), "$")
GetColumnLetter = vArr(0)
End Function
Sub RestorePivtTable2()
Dim lc As Long
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("PivtTable")
lc = ws.Cells(5, ws.Columns.count).End(xlToLeft).Column
myCol = GetColumnLetter(lc)
Columns("E:" & myCol & "").Select
Selection.ClearFormats
End Sub