I have a For loop that iterates through each row, I need to pull the value from the D column for each row to use in the loop.
I was trying to use the following to pull the value by using the value of the counter as the row number.
X = Worksheets("Test").Cells(4, Counter).Value
I keep encountering a Type mismatch error #13
These are the two scenarios I can think of when you will get that error
A You have defined X as a specific data type but are assigning a different type to it. For example. You have defined X as Long but the cell contains a String. Let's say your cell is A1 and it has excelSU. To replicate the error see this example
Sub Sample()
Dim x As Long
x = ThisWorkbook.Sheets("Sheet1").Range("A1").Value
End Sub
Similarly you could have declared Counter of a specific type but using as a different. For example
Sub Sample()
Dim counter As Excel.Application
For counter = 1 To 20
x = ThisWorkbook.Sheets("Sheet1").Range("A" & counter).Value
Next
End Sub
B Let's again take the example of cell A1. Your cell has a formula error like #N/A or #DIV/0! or some other error. To replicate the error use the same code as above and you will get the Type Mismatch Error
EDIT
by using the value of the counter as the row number
BTW, Counter is not being used as a Row but as Column. The syntax is Cells(Row,Column)
You might be pulling strings into a Integer array, or something similar.
As for your for-loop :
I just answered someone's question on a somewhat similar problems.
I suggest going over the fully-functional code example I gave him.
Namely because:
You are using hard-coded values (4rth column). You will eventually run into maintenance problems. A user that adds a column will easily screw up your code.
Your code won't speak for itself. If you replace your hard-coded values with properly named variables, they will.
NOTE: This might be more consuming on the processor due to the number of intersections. You can always adapt your strategy if you ever come up with tables that have over 10,000 rows.
Check the link and answer, but meanwhile here's a short-version of how I'd do it with Tables / ListObjects:
dim listO as ListObject
set listO = Worksheets("Test").ListObjects(1)
dim listC as ListColumn
set listC = ListO.ListColumns("ColumnTitle")
dim listR as ListRow
dim anArrayX() as Variant
ReDim anArrayX (1 to listO.ListRows.Count)
dim intersectedRange as Range
for i = 1 to listO.ListRows.count
set rowRange = ListO.ListRows(i).Range
set intersectedRange = Application.Intersect(rowRange, listC.Range)
anArrayX(i) = intersectedRange.Value
Debug.Print anArrayX(i)
next i
Related
this is both my first post on stack overflow and my first vba project so please forgive and correct any mistakes.
I am trying to create a function that looks at two ranges and pastes only non-null duplicate values into a third range.
My function takes in the two ranges that are to be compared as input (rangeOne and rangeTwo). It also takes in a third range, which is the first cell in the paste range (strtCell). I am trying to make it so that the duplicate values are pasted along a column for now, but so that they start at the cell that the user inputs.
The problem is that whenever I reference strtCell, the code just stops running. I have tested various breakpoints and using strtCell in many different contexts. However, whenever it is used at all in the code, the code stops executing at that line without any error message. If I comment out or delete the lines referencing it, the cell I enter the function in is set to "Success" as expected.
Also as an aside, whenever I type Option Explicit on the top line, it disappears from the workspace but still seems to be in effect. I am confused as to why it is disappearing.
Here is the relevant code [I put the bracketed text for comments I added in the post]:
(Option Explicit)
Public Function CopyDuplicates(rangeOne As Range, strtCell As Range, rangeTwo As Range)
'Declare necessary things
Dim arrayOne(), arrayTwo(), element, element2, element3, element4
Dim cell As Range
Dim pairs As New Collection
pairs.Add ""
Dim flag As Boolean
Dim numberOfDuplicateValues As Integer, i As Integer
numberOfDuplicateValues = 0
i = 0
'Import the Ranges
'Set array lengths to sizes of ranges
ReDim arrayOne(rangeOne.Count - 1)
ReDim arrayTwo(rangeTwo.Count - 1)
'Set array values to values of ranges
'Range 1
For Each cell In rangeOne
arrayOne(i) = cell.Value
i = i + 1
Next cell
'Range 2
[Exact same process as Range 1]
'Check for Duplicates
[This is just some code that adds all the duplicate values into pairs.
I have used a breakpoint to verify that pairs is being updated as intended.]
'Print duplicates
For Each element4 In pairs
strtCell.Value = CStr(element4)
strtCell = strtCell.Offset(1, 0)
Next element4
CopyDuplicates = "Success"
End Function
I'll also just add that strtCell is meant to be a single-cell range, but I have tested it as both a single and multi cell range, with the same weird stopping result in both cases.
Thanks for any help, this problem has had me stumped for hours!
I'd think that the following code should produce a diagonal of numbers, and I am wondering why this code doesn't work as intended:
Sub RangeExample()
Dim a As Range
Set a = Sheets(1).Range("a1")
a.Value = 0
Dim i As Integer
For i = 1 To 100
a = a.Offset(1, 1)
a.Value = i
Next i
End Sub
I understand there are many ways of producing a diagonal of numbers, I'm not asking how to do that.
What I'm asking is how I would change my range variable a to become a different range, and do that iteratively. It seems to me that as a.offset(1,1) returns a range object that's one over and one down, I should be able to reassign a as this new range, assign a value, and move on.
Your current issue is that you're missing a Set:
Set a = a.Offset(1, 1)
Note that you could also just use i and not reSet:
a.Offset(i, i).Value = i
Another option is to use Cells, e.g.
Sheets(1).Cells(i + 1, i + 1).Value = i
There's more than one way to skin a cat - pick whatever is easiest and most intuitive to future you.
Thanks for the answer, I didn't know set was required in this case. The specific answer I was looking for I have now found at:
What does the keyword Set actually do in VBA?
Specifically, the following answer by LeppyR64. "Set is used for setting object references, as opposed to assigning a value."
I didn't know that equality alone only impacted the value of the range object a. To actually change the range a was referencing, I needed set because a is supposed to refer to a new range object.
the issue has already been addressed by #BigBen
but you could avoid re-setting the range at every iteration by means of With...End With block
Option Explicit
Sub RangeExample()
Dim i As Long
With Sheets(1).Range("a1") ' reference topleftmost cell
.Value = 0 ' write referenced cell value
For i = 1 To 100
.Offset(i, i).Value = i 'write referenced cell current offset value
Next
End With
End Sub
So, I came across an issue with arrays in VBA not too long ago, that I was able to fix. However, I can't figure out why the fix worked, since logically what I did is telling VBA the exact same thing. None of the people in my office who code in VBA can answer this question.
The issue is that I want to take data from a table, store it in an array, and then copy it to another table. The code below works just fine for that, as it should since this is a simple task, so long as the table has at least 2 rows. If the table only has 1 row though, it spits out:
runtime 13 type mismatch error
I commented the fix in the if statement below, so you can see what fixes it. If the table has one row of data, and say 1 column, then I'm redimming the array as 1,1 for size. This should match the table I want to copy the data to if it's also 1 row and 1 column. However, it won't let me just do MyArray() = new table range. I have to specifically tell it if there's 1 row then do MyArray(nr, 1), which is the same thing to me, since the array is redimmed as nr, 1 anyway.
What am I missing here?
Option Explicit
Option Base 1
Sub test()
Dim MyArray() As Variant
Dim nr As Integer
nr = Range("Tbl_Test[Data]").Rows.Count
ReDim MyArray(nr, 1)
MyArray() = Range("Tbl_Test[Data]")
'If nr = 1 Then
'MyArray(nr, 1) = Range("Tbl_Test[Data]")
'Else:
'MyArray() = Range("Tbl_Test[Data]")
'End If
Range("Tbl_Test2[Data]") = MyArray()
End Sub
This is a very strangely specific need, and the last thing I need to complete my suite of new macros.
Note: The '---' at the top of the sheet is there to represent several months of the same report going back in time
As you can see in the image linked above, I have two highlighted sections. I need to make column G the sum of E and F from the previous report's numbers. Because there is a new set of data added every day, I can't reference specific cells and it must be dynamic. The larger problem here is that my number of customers will change every so often. It will only go up and it will always be in the same order; even if a lose a customer they stay on the report in the same spot.
My only theories on how to get this done are:
Find the second to last instance of customer A and define a rng based on the offset cells to the right. My problem with this is that—to my understanding—even filling that formula all the way down will just give me the one value.
Adding =SUM((INDIRECT(ADDRESS(ROW()-5,COLUMN()-2))):(INDIRECT(ADDRESS(ROW()-5,COLUMN()-1)))) to the blank cells. My problem with this is that the -5 in the offset is able to change, and even defining it by the number of blank cells will cause a mistake the first time a new customer comes on.
Any insight would be very much appreciated. And please let me know if you have any clarifying questions; I'm happy to answer/edit the original post as needed.
It can probably be optimised further by actually pre-calculating the range, but the naive version would be:
=SUMIFS([Outstanding Mail],[Date],LOOKUP([#Date]-1,[Date]),[Customer],[#Customer])
+SUMIFS([Outstanding Faxes],[Date],LOOKUP([#Date]-1,[Date]),[Customer],[#Customer])
Which relies on the fact that your dates are sorted, and that LOOKUP returns the last value that is not greater than the supplied value, so the [#Date]-1 makes it look up the biggest date that is less than the provided date. Will not work on an unsorted range.
#Gserg got an answer ahead of me, and his solution is one good elegant line, although i think it goes on the assumption there will be items every day there (if I`m not wrong?), and your screenshot suggest they may not be consecutive days all the time.
If you are still looking at a VBA solution as well, I would do something like this:
Option Explicit
Sub addOffsetFormula()
'Declare and set your workbook
Dim wb As Workbook: Set wb = ActiveWorkbook
'Declare and set your spreadsheet
Dim shData As Worksheet: Set shData = wb.Worksheets("Data")
'Set your last row/column for a dynamic aproach
Dim lRow As Long: lRow = shData.Cells(1, 1).End(xlDown).Row
Dim lCol As Long: lCol = shData.Cells(1, shData.Columns.Count).End(xlToLeft).Column
'Declare some further variables to help
Dim R As Long, X As Long
Dim sumFormula As String
'Declare and set your array to hold your data - much faster to iterate through the array than spreadsheet itself
Dim tblData(): tblData = shData.Range(shData.Cells(1, 1), shData.Cells(lRow, lCol))
For R = LBound(tblData) + 1 To UBound(tblData) 'Iterate through your data
For X = LBound(tblData) + 1 To UBound(tblData) 'Iterate through the same data again
If tblData(R, 4) = tblData(X, 4) And X > R Then 'Check for match with the next client found (assuming clients are unique)
'Set your formula to a variable, helps with debugging
sumFormula = "=SUM(R[-" & X - R & "]C[-2]+R[-" & X - R & "]C[-1])"
'Assign the formula to the respective cell _
If the spreadsheet is massive, you might need to add some optimisation _
(ie: assign everything to an array first, then dump into the spreadsheet)
shData.Cells(X, 7).FormulaR1C1 = sumFormula
End If
Next X
Next R
End Sub
Note: It won't add anything to the first few lines or new clients, as there is nothing to match against previously, but i expect that should work the same with any formula too.
I need to extract the data from an excel worksheet to an array that will be used in an application that uses VBScript as scripting language (Quick Test Professional). We can use the following code for that:
' ws must be an object of type Worksheet
Public Function GetArrayFromWorksheet(byref ws)
GetArrayFromWorksheet = ws.UsedRange.Value
End Function
myArray = GetArrayFromWorksheet(myWorksheet)
MsgBox "The value of cell C2 = " & myArray(2, 3)
All nice and well, but unfortunately the array that gets returned does not only contain the literal text strings, but also primitives of type date, integer, double etc. It happened multiple times that that data got transformed.
[edit] Example: when entering =NOW() in a cell and set the cell formatting to hh:mm makes the displayed value 17:45, the above method retuns a variable of type double and a value like 41194.7400990741
The following solution worked better: I can get the literal text from a cell by using the .Text property, but they only work on one cell and not on a range of cells. I cannot do this at once for an array as I could with the .Value property, so I have to fill the array one cell at a time:
Public Function GetArrayFromWorksheet_2(byref ws)
Dim range, myArr(), row, col
Set range = ws.UsedRange
' build a new array with the row / column count as upperbound
ReDim myArr(range.rows.count, range.columns.count)
For row = 1 to range.rows.count
For col = 1 to range.columns.count
myArr(row, col) = range.cells(row, col).text
Next
Next
GetArrayFromWorksheet_2 = myArr
End Function
But ouch... a nested for loop. And yes, on big worksheets there is a significant performance drop noticable.
Does somebody know a better way to do this?
As we covered in the comments, in order to avoid the issue you will need to loop through the array at some point. However, I am posting this because it may give you a significant speed boost depending on the type of data on your worksheet. With 200 cells half being numeric, this was about 38% faster. With 600 cells with the same ratio the improvement was 41%.
By looping through the array itself, and only retrieving the .Text for values interpreted as doubles (numeric), you can see speed improvement if there is a significant amount of non-double data. This will not check .Text for cells with Text, dates formatted as dates, or blank cells.
Public Function GetArrayFromWorksheet_3(ByRef ws)
Dim range, myArr, row, col
Set range = ws.UsedRange
'Copy the values of the range to temporary array
myArr = range
'Confirm that an array was returned.
'Value will not be an array if the used range is only 1 cells
If IsArray(myArr) Then
For row = 1 To range.Rows.Count
For col = 1 To range.Columns.Count
'Make sure array value is not empty and is numeric
If Not IsEmpty(myArr(row, col)) And _
IsNumeric(myArr(row, col)) Then
'Replace numeric value with a string of the text.
myArr(row, col) = range.Cells(row, col).Text
End If
Next
Next
Else
'Change myArr into an array so you still return an array.
Dim tempArr(1 To 1, 1 To 1)
tempArr(1, 1) = myArr
myArr = tempArr
End If
GetArrayFromWorksheet_3 = myArr
End Function
Copy your worksheet into a new worksheet.
Copy Paste values to remove formulas
Do a text to columns for each column, turning each column into Text
Load your array as you were initially doing
Delete the new worksheet
You cant do this quickly and easily without looping through the worksheet.
If you use the technique above with 2 lines of code it must a variant type array.
I've included a real example from my code that does it in 6 lines because I like to A) work with the worksheet object and B) keep a variable handy with the original last row.
Dim wsKeyword As Worksheet
Set wsKeyword = Sheets("Keywords")
Dim iLastKeywordRow As Long
iLastKeywordRow = wsKeyword.Range("A" & wsKeyword.Rows.Count).End(xlUp).Row
Dim strKeywordsArray As Variant
strKeywordsArray = wsKeyword.Range("A1:N" & iLastKeywordRow).Value
Note your array MUST be a variant to be used this way.
The reason that Variants work like this is that when you create an array of variants, each 'cell' in the array is set to a variant type. Each cell then get's it's variant type set to whatever kind of value is assigned to it. So a variant being assigned a string gets set to variant.string and can now only be used as a string. In your original example it looks like you had time values which were kind of stored as variant.time instead of variant.string.
There are two ways you can approach your original problem
1) loop through and do the process with more control, like the double nested for loop. explained in another answer which gives you complete control
2) store all the data in the array as is and then either re-format it into a second array, or format it as desired text as you use it (both should be faster)