Left Function Compile Error: Argument not optional (VBA) - excel

I am trying to do a few things with this subroutine.
First i need to insert a row after every 18th row
Then i need to insert a string in column A based on the string in the above row (i.e. 17th row in the first iteration).
Finally i need to insert a string in column B of the ith row again based on the previously established string.
So in Layman's terms, row 17 will say 120F_CASH
i want to find "_ " and take the characters in the string from the left of the "_ ", then in row 18 i want to put 120F & "_MULTI_STRAT" in column A
in column B of row 18, i want to insert "SSELECT PERF.IMST.INDEX WITH ENTITY = " & "120F" & "AND WITH CATEGORY1 = ""01"""
here's my code:
Option Explicit
Sub InsertRowEveryXrows()
Dim r As Long, lr As Long
Dim code As String
lr = Range("A" & Rows.Count).End(xlUp).Row
For r = 18 To lr Step 18
Rows(r).Insert Shift:=xlDown
Set code = Left(Range(Cells(r - 1, "A"), InStr(1, "_", Cells(r, "A")) - 1))
Cells(r, "A") = code & "_MULTI_STRAT"
Cells(r, "B") = "SSELECT PERF.IMST.INDEX WITH ENTITY = " & code & "AND WITH CATEGORY1 = ""01"""
Next r
End Sub
i am getting a compile error: Wrong number of arguments or invalid property assignments on this line:
Set code = Left(Range(Cells(r - 1, "A"), InStr(1, "_", Cells(r, "A"))
- 1))
the string's location is in row 17 (r-1) in column A & the number of characters is found by looking for "_"
What am i missing?
*fyi i had to add a space after the underscore for it to show up properly here, however, there SHOULD NOT be a space after the underscore.

Set code = Left(Range(Cells(r - 1, "A"), InStr(1, "_", Cells(r, "A")) - 1))
The Left function wants a string and an integer, but you haven't provided the integer. Here's the same expression, without the outer Left function call:
Set code = Range(Cells(r - 1, "A"), InStr(1, "_", Cells(r, "A")) - 1)
Notice anything? The result of InStr is being passed as the 2nd argument to the (unqualified) Range property, and that isn't what you intended.
In fact, you don't need that Range call at all. This should be closer to your intent:
Set code = Left(Cells(r - 1, "A"), InStr(1, "_", Cells(r, "A")) - 1)
Note that there is quite a bit of implicit code going on here; made explicit, reads like this:
Set code = Left(ActiveSheet.Cells(r - 1, "A").Value, InStr(1, "_", ActiveSheet.Cells(r, "A").Value) - 1)
Do you really intend to be working of just whatever worksheet happens to be active? Most likely not, but if so, consider explicitly qualifying these Cells calls with ActiveSheet. If you mean to be working with a specific sheet, use that sheet object as a qualifier instead.
Now, you're using Set for this assignment, but code is not an object reference, and that is another problem.
Strings are not objects in VBA, they're assigned with the regular value assignment syntax. That is, without a Set keyword (you could have the legacy Let keyword there if you wanted, but it's not needed):
code = Left(ActiveSheet.Cells(r - 1, "A").Value, InStr(1, "_", ActiveSheet.Cells(r, "A").Value) - 1)
Now, Range.Value (explicit here, implicit in your code) will be a Variant, not a String. In most cases, it won't matter.
Until one cell has a Variant/Error subtype (think #N/A, or #VALUE! worksheet errors); then everything blows up with a type mismatch error. To avoid this, you can use the Text of the cell instead of its Value, or you can pull that value into its own local Variant variable, and only proceed to treat it like a string when IsError returns False for it.

You could use something like so
Function Pop_String(strInput As String, strDelim As String, Optional lngInstance = 1)
Dim aTemp() As String
aTemp = Split(strInput, strDelim, lngInstance + 1)
Pop_String = aTemp(lngInstance - 1)
End Function
Calling like so
Pop_String("a-b-c-d-e-f","-",4) returns d
Pop_String("a-b-c-d-e-f","-",2) returns b
I would add some error checking too, the default (without 3rd argument) is the first section, so
Pop_String("test-string","-") would return test and
Pop_String("test-string","-",2) would return string.

Related

Why am I getting a 'Subscript out of Range' error on an my Array? [duplicate]

I have declared an array as such Dim rArray() As Variantbut when i try and use the values that is stored in it (as shown below) I get a subscript out of range error. The UBound(rArray)and LBound(rArray) both returns values 14 and 1, but the error occurs at the Debug.Print line.
If I use the for statement as below
For Each rArr in rArray
then it works without issues, but for the purposes I am creating this array I need the flexibility to select each item stored in that order- meaning I need to refer to them using subscripts.
I have tried multiple ways to try and solve this with no luck and spend almost half my day on this one issue. Could anyone point out what I need to change to get this to work.
Set rng = Range("D4", Range("D4").End(xlDown))
rng.NumberFormat = "0"
rArray = rng.Value
For x = UBound(rArray) To LBound(rArray) Step -1
Debug.Print rArray(x)
Next x
Edit: another fact worth mentioning is that he array is declared and used within a Function but it is not passed from or to the function. Can't arrays be declared and used in Functions?
When you assign worksheet values to a variant array, you always end up with a 2-D array that is 1 based (e.g. 1 to something, 1 to something; never 0 to something, 0 to something). If you are getting values from a single column the second Rank is merely 1 to 1.
This can be proven with the following.
Dim x As Long, rArray As Variant, rng As Range
Set rng = Range("D4", Range("D4").End(xlDown))
rng.NumberFormat = "0" 'don't really understand why this is here
rArray = rng.Value
Debug.Print LBound(rArray, 1) & ":" & UBound(rArray, 1)
Debug.Print LBound(rArray, 2) & ":" & UBound(rArray, 2)
For x = UBound(rArray, 1) To LBound(rArray, 1) Step -1
Debug.Print rArray(x, 1)
Next x
So you need to ask for the element in the first rank of the array; it is insufficient to just ask for the element.

Cell not populating

I have two cells that are refusing to populate in row 10 and 70. Every other cell populates and I have tried changing columns, even workbooks but I still get the same problem. There is no protection or passwords. I have no idea of the cause. This is the very simple code it is running on these cells:
i = 1
Worksheets("Output").Range("N1") = i
For z = 2 To lastrow - 1
If Worksheets("Output").Range("D" & z).Value < Worksheets("Output").Range("D" & z - 1).Value Then
i = i + 1
Worksheets("Output").Range("N" & z).Value = i
End If
If Worksheets("Output").Range("D" & z).Value = Worksheets("Output").Range("D" & z - 1).Value Then
Worksheets("Output").Range("N" & z).Value = i & " (tie)"
Worksheets("Output").Range("N" & z - 1).Value = i & " (tie)"
End If
If Worksheets("Output").Range("D" & z).Value = "" Then
i = i + 1
Worksheets("Output").Range("N" & z).Value = i
End If
Next z
I cannot fathom out why it is happening, the trouble is it messes up my sequence. I have tried forcing it to populate if it is blank with those last 3 lines but still nothing.
The principle error in your code is that it contains a logical trap:-
If [Condition 1] Then i = i + 1
If [Condition 2] Then i = i + 1
This is contrary to the logic that every row defined by z needs a result. The trap is in that nothing will be counted if neither of the two conditions are met. Therefore you should structure your code as follows.
If [Condition 1] Then
i = i + 1
ElseIf [Condition 2] Then
i = i + 1
Else
i = i - 1
End If
In this way, using Else, it will be impossible to skip a row.
However, there are more logical flaws in your code. and once I set out to determine what might be in column D I came to a totally different structure which I share with you below.
Sub STO_66111404()
Dim i As Long ' rank
Dim Tie As Boolean ' next item is of same value
Dim Tied As Boolean ' last item was of same value
Dim R As Long ' loop counter: rows
With Worksheets("Output")
For R = 1 To .Cells(.Rows.Count, "D").End(xlUp).Row - 1
i = i + Abs(Not Tie) ' Abs(Not Tie) = 1 if Tie is False
' Val() converts any non-numeric value, incl "", to 0
Tied = Tie
Tie = Val(.Cells(R, "D").Value) = Val(.Cells(R + 1, "D").Value)
.Cells(R, "N").Value = i & IIf(Tie Or Tied, " (tie)", "")
Next R
.Cells(R, "N").Value = i + Abs(Not Tie) & IIf(Tie, " (tie)", "")
End With
End Sub
It may take you a moment to recognize this code as your own. So, here are a few points to guide you.
With Worksheets("Output") helps you avoid repeating the sheet name over and over again. In the code that follows this line, and until End With, the object is represented merely by a leading period. .Cells(.Rows.Count, "D") stands for Worksheets("Output").Cells(Worksheets("Output").Rows.Count, "D")
Ranges comprising of single cells are most efficiently addressed by the syntax designed for that purpose, to wit, by a cell's coordinates instead of its range name. So, .Cells(R, "D") stands for Range("D" & R). This syntax has the added advantage that it is also equal to .Cells(R, 4), meaning you can easily calculate both row and column numbers.
The big difference in the approach is that your code focuses on the conditions and therefore uses a lot of IFs. In the above approach the focus is on the results of the conditions, expressed in the two variables, Tie and Tied. Your code has no equivalent for the latter but doesn't seem to need it, either. Note, however, that the above code may not handle the case correctly where the next value in column D is smaller than the preceding. The code just checks for equality and presumes that the next value is bigger if it isn't equal, setting Tie = False here: Tie = Val(.Cells(R, "D").Value) = Val(.Cells(R + 1, "D").Value). In your approach, this may be the reason for the skipped lines.

.Value does not read whole string

In my macro I need to read a value out of a Cell. The values always start with an ' followed by a number like '1 or '1001.
When I attempt to read the cell like s = wsout.Cells(headerVal(6, 1) + i, 1).Value, I only get 1 instead of '1.
wsout is a worksheet and s is defined as a String.
How do I read the actual contents of the cell?
You need the Range.PrefixCharacter Property whih returns the prefix character for the cell.
Here is an example
s = wsout.Cells(headerVal(6, 1) + i, 1).PrefixCharacter & _
wsout.Cells(headerVal(6, 1) + i, 1).Value
Debug.Print s
If the cell has '1 then s above will have '1

pasting in vba data

image worksheetI am setting up sheet with hotels details and column "D" has hospitals that are close by eg PMH,SCGH,FSH. What i am trying to do is search column "D" based on a cell value on same sheet. I have code below but it will only do what i want if the cells in column"D" are single entry eg pmh. I need to be able to search all the cells in Column "D" for any instance of the text.
Many Thanks for any assistance
`Option Explicit
Sub finddata()
Dim hospitalname As String
Dim finalrow As Integer
Dim i As Integer
Sheets("Results").Range("A4:D100").ClearContents
Sheets("Main").Select
hospitalname = Sheets("Main").Range("g3").Value
finalrow = Sheets("Main").Range("A1000").End(xlUp).Row
For i = 2 To finalrow
If Cells(i, 4) = hospitalname Then
Range(Cells(i, 1), Cells(i, 4)).Copy
Sheets("Results").Range("A4").End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
Sheets("Main").Range("g3").Select
End Sub
`
The two simplest ways to do this would be
Using the Like operator:
If Cells(i, 4).Value Like "*" & hospitalname & "*" Then
This method has the drawback that a hospital name of, for instance, PMH might be matched against another one such as SPMH.
Using the InStr function:
If Instr("," & Cells(i, 4).Value & ",", "," & hospitalname & ",") > 0 Then
In this line, I "wrap" both the cell being looked at, and the value being searched for, within commas so it ends up searching for the string (for instance) ",PMH," within the string ",PMH,SCGH,FSH,". InStr will return the character position at which a match occurs, or zero if no match is found. So testing for > 0 is testing whether a match occurred.

Write several values in one string

I am new to both VBA and stackoverflow. So please be patient ;).
I searched for a solution but could not find it.
My problem is as follows:
I have a column (A) with names and then a column (B) where some cells contain an "X" and others do not. I want to know which names have an "X" besides them.
Example:
I want now a string as a result, in one cell.
In this example:
Noah;Jacob;Elijah;Jayden
I got not very far.
For r = 1 To 20
If Cells(r, 2) = "X" Then A = Cells(r, 1) Else
Next
Then "A" is "Noah" and I can write it in a cell, but I want it to find all values and then write them combined, preferable seperated by ; in a cell.
Does anyone have any idea?
Create a string variable, then append your results to that variable based on "X" being in column B. Here's an example of how you could do it:
Sub Foo()
Dim i As Integer
Dim result As String
For i = 1 To 20
If UCase(Cells(i, 2).Value) = "X" Then
result = result & Cells(i, 1).Value & ";"
End If
Next
'// output the result to C1
Range("C1").Value = Left$(result, Len(result) - 1)
End Sub
Excel's native worksheet formulas do not handle concatenating an unknown number of strings together and compensating for the maximum number possible can get messy. A User Defined Function¹ (aka UDF) takes advantage of VBA's ability to process loops through a large number of rows while making numerical or string comparisons 'on-the-fly'.
build_List UDF
Function build_List(rNAMs As Range, rEXs As Range, vEX As Variant, _
Optional delim As String = ";", _
Optional bCS As Boolean = False)
Dim str As String, rw As Long, cl As Long
With rNAMs.Parent
Set rNAMs = Intersect(.UsedRange, rNAMs)
Set rEXs = .Cells(rEXs.Rows(1).Row, rEXs.Columns(1).Column). _
Resize(rNAMs.Rows.Count, rNAMs.Columns.Count)
End With
With rNAMs
For rw = .Rows(1).Row To .Rows(.Rows.Count).Row
For cl = .Columns(1).Row To .Columns(.Columns.Count).Row
If (.Cells(rw, cl).Offset(0, rEXs.Column + (cl - 1) - cl) = vEX And bCS) Or _
(LCase(.Cells(rw, cl).Offset(0, rEXs.Column + (cl - 1) - cl)) = LCase(vEX)) Then _
str = str & .Cells(rw, cl).Value & delim
Next cl
Next rw
End With
build_List = Left(str, Len(str) - Len(delim))
End Function
In D7 (as per image below) as,
=build_List(A:A, B:B, "x")
                               Applying the build_Lists UDf to your sample data
¹ A User Defined Function (aka UDF) is placed into a standard module code sheet. Tap Alt+F11 and when the VBE opens, immediately use the pull-down menus to Insert ► Module (Alt+I,M). Paste the function code into the new module code sheet titled something like Book1 - Module1 (Code). Tap Alt+Q to return to your worksheet(s).
Mate Juhasz answered the question very nice and simple, but now the answer dissapeared.
Mate wrote:
For r = 1 To 20
If Cells(r, 2) = "X" Then A = A & "; " & Cells(r, 1) Else
Next
And for me that solved it perfectly. Now "A" is a string as I wanted. Thank you so much!

Resources