Use VBA to replace sporadic #N/A cells with a formula - vba

I have data that comes from different sources via Get Data/Connections that goes into a sheet/table called RawData. I have created a macro linked to a button the user must click to fill in some of the data in RawData with an index/match function that points to another workbook where the reservation number from RawData is matched to the other workbook to determine the length of stay.
I am not appending said workbook to the RawData table for different reasons. I can go that route if need be but I figured there would be an easier route before we cross that bridge. The issue I have is that once the macro is ran, the other workbook, at times, may not have the reservation data. In these cases, I had to manually determine this data.
This new data can be placed within my main workbook [but I currently have it in it's own workbook (LOSNAintoActualLOS.xlsm) as I've been running tests on making this work]. And the formula has to be pasted into the table because when the table refreshes, the row of data that would normally have the length of stay removes the formula and replaces it with it's original value, a blank cell.
What I need is for my code to loop through all the cells within the F column of a table, determine the cells with #N/A errors, then paste a formula in the error cell. I have done some research and can't seem to find something that suits my needs. I attempted doing this as a loop but seeing as I'm still pretty new to VBA coding, it's definitely not my strong suit.
Sub Macro2()
Dim r As Integer
LastRow = Range("B" & Rows.Count).End(xlUp).Row
With Worksheets("Sheet1").Range("F2:F" & LastRow)
For r = 2 To LastRow
If .IsError(Worksheets("Sheet1").Range("F" & r)).Value Then
.Formula = "INDEX(LOSNAintoActualLOS.xlsm!Table1[#Data],MATCH([#Reservation],LOSNAintoActualLOS.xlsm!Table1[Reservation],0),7)"
.Value = .Value
End If
Next r
End With
End Sub
When I run the code for my If statement I get Run-time error '438': Object doesn't support this property or method. If I remove the first period from .IsError I get Compile error: Invalid qualifier.

IsError is not part of Worksheets("Sheet1").Range("F2:F" & LastRow)
Switch it up like this.
Sub Macro2()
Dim r As Long
Dim LastRow As Long
LastRow = Worksheets("Sheet1").Range("B" & Rows.Count).End(xlUp).Row
For r = 2 To LastRow
With Worksheets("Sheet1").Range("F" & r)
If IsError(.Value) Then
.Formula = "INDEX(LOSNAintoActualLOS.xlsm!Table1[#Data],MATCH([#Reservation],LOSNAintoActualLOS.xlsm!Table1[Reservation],0),7)"
.Value = .Value
End If
End With
Next r
End Sub

Related

Using Vlookup to copy and paste data into a separate worksheet using VBA

Alright I'm a beginner with VBA so I need some help. Assuming this is very basic, but here are the steps I am looking at for the code:
-Use Vlookup to find the value "Rec" in column C of Sheet1, and select that row's corresponding value in column D
-Then copy that value from column D in Sheet1 and paste it into the first blank cell in column B of another worksheet titled Sheet2
I've got a basic code that uses Vlookup to find Rec as well as it's corresponding value in column D, then display a msg. The code works fine, and is the following:
Sub BasicFindGSV()
Dim movement_type_code As Variant
Dim total_gsv As Variant
movement_type_code = "Rec"
total_gsv = Application.WorksheetFunction.VLookup(movement_type_code,Sheet1.Range("C2:H25"), 2, False)
MsgBox "GSV is :$" & total_gsv
End Sub
I also have another one that will find the next blank cell in column B Sheet2, it works as well:
Sub SelectFirstBlankCell()
Dim Sheet2 As Worksheet
Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) = True Then cell.Select: Exit For
Next cell
End Sub
Not sure how to integrate the two, and I'm not sure how to make the code paste the Vlookup result in Sheet2. Any help would be greatly appreciated, thanks!
So for being a beginner you're off to a good start by designing two separate subroutines that you can confirm work and then integrating. That's the basic approach that will save you headache after headache when things get more complicated. So to answer your direct question on how to integrate the two, I'd recommend doing something like this
Sub BasicFindGSV()
Dim movement_type_code As Variant
Dim total_gsv As Variant
movement_type_code = "Rec"
total_gsv = Application.WorksheetFunction.VLookup(movement_type_code, Sheet1.Range("C2:H25"), 2, False)
AssignValueToBlankCell (total_gsv)
End Sub
Sub AssignValueToBlankCell(ByVal v As Variant)
Dim Sheet2 As Worksheet
Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) = True Then cell.Value2 = v
Next cell
End Sub
That being said, as Macro Man points out, you can knock out the exact same functionality your asking for with a one liner. Keeping the operational steps separate (so actually a two liner now) would look like this.
Sub FindGSV()
AssignValueToBlankCell WorksheetFunction.VLookup("Rec", Sheet1.Range("C2:H25"), 2, False)
End Sub
Sub AssignValueToBlankCell(ByVal v As Variant)
Sheet3.Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value2 = v
End Sub
Like I said, if you plan to continue development with this, it's usually a good idea to design your code with independent operations the way you already have begun to. You can build off of this by passing worksheets, ranges, columns, or other useful parameters as arguments to a predefined task or subroutine.
Also, notice that I use Value2 instead of Value. I notice you're retrieving a currency value, so there's actually a small difference between the two. Value2 gives you the more accurate number behind a currency formatted value (although probably unnecessary) and is also faster (although probably negligible in this case). Just something to be aware of though.
Also, I noticed your use of worksheet objects kind of strange, so I thought it'd help to mentioned that you can select a worksheet object by it's object name, it's name property (with sheets() or worksheets()), index number (with sheets() or worksheets()), or the "Active" prefix. It's important to note that what you're doing in your one subroutine is reassigning the reference of the Sheet2 object to your active sheet, which means it may end up being any sheet. This demonstrates the issue:
Sub SheetSelectDemo()
Dim Sheet2 As Worksheet
Set Sheet2 = Sheets(1)
MsgBox "The sheet object named Sheet2 has a name property equal to " & Worksheets(Sheet2.Name).Name & " and has an index of " & Worksheets(Sheet2.Index).Index & "."
End Sub
You can view and change the name of a sheet object, as well as it's name property (which is different) here...
The name property is what you see and change in the worksheet tab in Excel, but once again this is not the same as the object name. You can also change these things programmatically.
Try this:
Sub MacroMan()
Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value = _
WorksheetFunction.VLookup("Rec", Sheet1.Range("C2:H25"), 2, False)
End Sub
The Range("B" & Rows.Count).End(xlUp) command is the equivalent of going to the last cell in column B and pressing Ctrl + ↑
We then use .Offset(1, 0) to get the cell after this (the next blank one) and write the value of your vlookup directly into this cell.
If Both work, then good, you have two working subs and you want to integrate them. You probably want to keep them so they might be useful for some other work later. Integrating them means invoking them in some third routine.
For many reasons, it is surely better and advised to avoid as much as possible to use (select, copy, paste) in VBA, and to use rather a direct copying method (range1.copy range2).
You need to make your routines as functions that return ranges objects, then in some third routine, invoke them
Function total_gsv() as range
Dim movement_type_code As Variant: movement_type_code = "Rec"
Set total_gsv = Application.WorksheetFunction.VLookup(movement_type_code,Sheet1.Range("C2:H25"), 2, False)
End Sub
Function FindFirstBlankCell() as Range
Dim Sheet2 As Worksheet: Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) Then Set FindFirstBlankCell= cell: exit For
Next cell
End Sub
Sub FindAndMoveGsv()
total_gsv.copy FindFirstBlankCell
... 'some other work
End Sub

How to move to next blank cell?

I have data on multiple sheets in a workbook that I want copied all to one sheet in that same workbook. When I run the macro, I would like it to start by deleting the current data in the "iPage Data Export" sheet and then replacing it with data from the other sheets.
I want the process to occur one column at a time since I may not bring over everything. Right now I am trying to learn how to do just one column.
I was able to get it to copy all of the contents of a column from one sheet, but when it moves to the next sheet, it overwrites the existing data. In the end, I only get one sheets worth of data copied.
Here are my 4 problems:
How do I make it clear the data on this sheet before running the routine?
How can I make it start each copy function at the bottom of that row (i.e. after the last cell with a value)? I have tried many of the suggestions on this and other boards without success. I will admit I am not very experienced in this.
How can I make it copy to a particular column (currently it just seems to default to A.
How can I concatenate multiple columns during the paste function? I.e. what if I want it to insert: A2&", "B2 instead of just A2
Sub CombineData()
Dim Sht As Worksheet
For Each Sht In ActiveWorkbook.Worksheets
If Sht.Name <> "iPage Data Export" Then
Sht.Select
Range("C:C").Copy
Sheets("iPage Data Export").Select
ActiveSheet.Paste
Else
End If
Next Sht
End Sub
How do I make it clear the data on this sheet before running the routine?
Sht.Cells.ClearContents
How can I make it start each copy function at the bottom of that row (i.e. after the last cell with a value)? I have tried many of the suggestions on this and other boards without success. I will admit I am not very experienced in this.
Range("C" & Rows.Count).End(xlUp).Offset(1, 0)
In detail:
Rows.Count will return the number of rows in the sheet, so in the legacy style *.xls workbooks this would return the number 65,536. Therefore "C" & Rows.Count is the same as C65536
Range("C" & Rows.Count).End(xlUp) is the same as going to C65536 and pressing Ctrl + ↑ - The command End(xlDirection) tells the program to go the last cell in that range. In this case, we would end up at the last cell containing data in column C.
.Offset(1, 0) means that we want to return the range offset by an amount of rows and/or columns. VBA uses RC (Rows Columns) references, so whenever you see something like the Offset() function with two numbers being passed as the arguments, it usually relates to the row, and the column, in that order. In this case, we want the cell that is one row below the last cell we referenced.
All-in-all the phrase Range("C" & Rows.Count).End(xlUp).Offset(1, 0) means go to the last cell in column C, go up until we hit the last cell with data, and then return the cell below that - which will be the next empty cell.
How can I make it copy to a particular column (currently it just seems to default to A.
Range("C:C").Copy Destination:=Sheets("iPage Data Export").Range("A:A")
You can pass the Destination argument in the same line and actually bypass the clipboard (faster and cleaner)
How can I concatenate multiple columns during the paste function? I.e. what if I want it to insert: A2&", "B2 instead of just A2
Lets say you wanted to reference column A, B, and F - just use:
Range("A1, B1, F1").EntireColumn
To summarise, you could streamline your existing code to something like (untested):
Sub CombineData()
Dim Sht As Worksheet
For Each Sht In ActiveWorkbook.Worksheets
If Sht.Name <> "iPage Data Export" Then
Sht.Range("C1:C" & Cells(Sht.Rows.Count, 3).End(xlUp).Row).Copy Destination:=Sheets("iPage Data Export").Range("A:A")
End If
Next
End Sub
This should do for the copying:
Sub CombineData()
Dim sheet As Worksheet
For Each sheet In Worksheets
If (sheet.Name <> "iPage Data Export") Then
sheet.Select
Range("A1", ActiveCell.SpecialCells(xlLastCell)).Select
Selection.Copy
Worksheets("iPage Data Export").Activate
Cells(1, ActiveCell.SpecialCells(xlCellTypeLastCell).Column + 1).Select
ActiveSheet.Paste
End If
Next
End Sub
For the concatenation you need to be more specific - but I guess you should open a new question with a clearer focus if you need specific help on that.

Find duplicate and copy adjacent cell into

I need some help getting some direction on this task.
I have two spreadsheets dumps with large quantities of information. I need to combine them into one organized sheet.
Spreadsheet A has path to the file (via hard drive), with loads of additional info needed to be retained.
Spreadsheet B had path to the file (via hard drive), and for those in the content management system, the path in the CMS.
I would like to copy spreadsheet B into worksheet 2 in spreadsheet A, then run a macro that will search for matching values (path to file via hard drive), and where the same, copy the adjacent value of worksheet B (path in CMS) and copy that in the appropriate cell in spreadsheet A.
I have a vlookup function that does exactly what I need it to do, but how do I go about and put it in a macro to run?
=VLOOKUP(H1,A:B,2,FALSE)
How would I put this into a macro that returns the value, not just puts the formula in the cell?
Sub PCMSLookup()
Dim LastRow As Long
LastRow = Range("B" & Cells.Rows.Count).End(xlUp).Row
Range("J15:J" & LastRow).Formula = "=VLOOKUP(B15,'PCMS-dump'!A:B,2,FALSE)"
End Sub
The quickest way to put the value into the cells is to block in the formula then revert the formula to the returned value.
Sub PCMSLookup()
Dim LastRow As Long
with sheets("Sheet1") '<-set this worksheet reference properly
LastRow = .Range("B" & Cells.Rows.Count).End(xlUp).Row
with .Range("J15:J" & LastRow)
.Formula = "=VLOOKUP(B15, 'PCMS-dump'!A:B, 2, FALSE)"
.cells = .value2
end with
end with
End Sub
Note that when you are within a With/End With grouping, all of the range and cell references are prefixed with a period (e.g. . or full stop). This shows that they are children of closest With/End With.

Curious Delete Behavior with Command Button VBA

I am continuing my efforts with data pulled from a mainframe. Currently, I am continuing work on sorting data for use in forms and potential data processing and the like. The data is alphanumeric and is similar in form to one of my previous questions related to my continuing data efforts.
One of my current development lines involved increased usability in the form of macro-enabled buttons. One such button involves a macro which is intended to delete all data in one column "A" from A2 to the end of any existing datarows in the sheet.
The code is as follows:
Sub DeleteCriteria_Click()
Dim CriteriaRange As Range
Dim LastCriteriaCell As Long
With Sheets("Criteria")
LastCriteriaCell = .Range("A" & Sheets("Criteria").Rows.Count).End(xlUp).Row
Set CriteriaRange = .Range("A2:A" & LastCriteriaCell)
End With
CriteriaRange.Cells.Select
Selection.Delete
End Sub
Curiously, this code ends up shifting my columns over by one each time I activate the button, and ends up deleting my header and subsequent columns with repeated clicks. This behavior resembles that of a normal delete function for a column. Refactoring the range commands CriteriaRange.Cells.Select | Selection.Delete into the forms CriteriaRange.Cells.Delete and CriteriaRange.Delete does not correct this issue.
My intent is to completely delete the entries so that there are no blanks or leftover datasets when new criteria is entered after the entries are deleted. I thus have two questions:
1) What is causing this behavior, i.e. what I am doing incorrectly, here?
2) How do I correct this behavior to utterly delete the cells or functionally perform the equivalent, thereby prevent any references to blank or null cells, while retaining my column?
Change your code to
CriteriaRange.Delete Shift:=xlUp
The default is
CriteriaRange.Delete Shift:=xlToLeft
Because of this your columns are moved.
Your code can be written as
Sub DeleteCriteria_Click()
Dim LastCriteriaCell As Long
With Sheets("Criteria")
LastCriteriaCell = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> This is required so that your header cells are not touched
If LastCriteriaCell < 2 Then Exit Sub
.Range("A2:A" & LastCriteriaCell).Delete Shift:=xlUp
End With
End Sub
OR
Sub DeleteCriteria_Click()
Dim LastCriteriaCell As Long
With Sheets("Criteria")
LastCriteriaCell = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> This is required so that your header cells are not touched
If LastCriteriaCell >= 2 Then _
.Range("A2:A" & LastCriteriaCell).Delete Shift:=xlUp
End With
End Sub

VBA VLOOKUP Convert to Values Gives #N/A

I'm having some trouble with VLOOKUP in my VBA. Here's an example of the code I'm using:
Sub Macro15()
'
' Macro15 Macro
Dim LR As Long
LR = Cells(Rows.Count, "A").End(xlUp).Row
Range("B1:B" & LR).FormulaR1C1 = _
"=VLOOKUP(RC[-1],'https://internal_sharepoint_address
/[Vendor_Information.xlsx]Sheet1'!R3C3:R150C18,4,FALSE)"
Range("C1:C" & LR).FormulaR1C1 = _
"=VLOOKUP(RC[-2],'https://internal_sharepoint_address
/[Vendor_Information.xlsx]Sheet1'!R3C3:R150C18,5,FALSE)"
With Range("B1:C" & LR)
.Value = .Value
End With
End Sub
The problem is that the values in Columns B & C (the VLOOKUP formulas) return a value of #N/A.
However, if I stop the code before converting the formula to values (the "With Range("B1:C" & LR)" line), the VLOOKUP formula returns the correct values.
Also strange - if I clear the contents of Columns B & C and re-run the above code, the values return fine. If I try to add a second cycle to the VBA, however, it does NOT work.
Any wisdom that anyone can provide would be a huge help. I've been stuck on this for a long time, and I'm just at my wit's end.
Thanks all,
David
You'll probably need to add in a step that runs a calculation cycle before you try to replace with the value:
Application.Calculate
Edit from comment: I would imagine that retrieving lookup data from a linked workbook on a Sharepoint site would take awhile. Maybe add some delay loops? Can you make two separate macros (one ending with the formulas, and a second one starting at the Paste Values), and run them separately with a pause in between?