Excel VBA - UDF returns 0 or empty or #value - excel

I am creating my customized function. I wrote the code and tested it as “sub and it worked well. Then i converted it into a function to be able to use it in general. Things that i changed are; adding a function declaration, taking input from excel cell and specifying function output. All others remained same.
My function only has one input which is a selected cell from an excel sheet. And i expect that function returns one output. However, it returns 0.
• function declaration. "Function IbpBomLevel(ByVal Target As Range) As Variant
• input of function as selected cell. "ProductID = Target
• output of function. "IbpBomLevel = fullText
I used option explicit to avoid non-exist functionalities. Also, I am sure about the input, function really takes the selected cell as input. But the problem is that in each loop “ProductID must be changed. Hovewer, when i declared that "IbpBomLevel (output of the function) = ProductID and saw that ProductID is the first parameter that user selected from a cell. It means loop not works. When i test it as “sub, i got the result that i want. I am not sure what the problem is.
Option Explicit
Function IbpBomLevel(ByVal Target As Range) As Variant
Dim Wb As Workbook
Dim Ws As Worksheet
Dim MyRange As Range
Dim SourceID As Variant
Dim SourceID2 As Variant
Dim SourceID3 As Variant
Dim Product As Variant
Dim Item As Variant
Dim Location As Variant
Dim Resource As Variant
Dim I As Variant
Dim T As Variant
Dim Z As Variant
Dim X As Variant
Dim Y As Variant
Dim Index As Variant
Dim Index2 As Variant
Dim Index3 As Variant
Dim BomLevel As Variant
Dim FoundCell As Variant
Dim fullText As Variant
Dim ProductID As Variant
ProductID = Target
Set Wb = Workbooks("Kitap.xlsx")
Windows("Kitap.xlsx").Activate
On Error GoTo T_Error
Set Ws = Wb.Worksheets("Production Source Header")
Sheets("Production Source Header").Select
Set MyRange = Worksheets("Production Source Header").Range("B:C")
SourceID = CVar(Application.WorksheetFunction.VLookup(ProductID, MyRange, 2, False))
I = 1
T = 0
Z = 1
If IsEmpty(SourceID) = False Then
Do While (IsEmpty(SourceID) = False) And (T = 0)
BomLevel = Z
Windows("Kitap.xlsx").Activate
Set Ws = Wb.Worksheets("Production Source Header")
Sheets("Production Source Header").Select
Set MyRange = Worksheets("Production Source Header").Range("B:C")
SourceID = CVar(Application.WorksheetFunction.VLookup(ProductID, MyRange, 2, False))
Set FoundCell = ActiveSheet.Range("C:C").Find(What:=SourceID)
If Not FoundCell Is Nothing Then
Index = FoundCell.Row
Location = Cells(Index, 1)
Product = Cells(Index, 2)
Else
End If
X = I
I = I + 1
Windows("Kitap.xlsx").Activate
Set Ws = Wb.Worksheets("Production Source Item")
Sheets("Production Source Item").Select
Set MyRange = Worksheets("Production Source Item").Range("B:B")
SourceID2 = CVar(Application.WorksheetFunction.VLookup(SourceID, MyRange, 1, False))
Do While (IsEmpty(SourceID2) = False) And (I - X = 1)
Set MyRange = Worksheets("Production Source Item").Range("B:B")
SourceID2 = CVar(Application.WorksheetFunction.VLookup(SourceID, MyRange, 1, False))
Set FoundCell = ActiveSheet.Range("B:B").Find(What:=SourceID2)
If Not FoundCell Is Nothing Then
Index2 = FoundCell.Row
Item = Cells(Index2, 1)
Windows("Kitap.xlsx").Activate
Set Ws = Wb.Worksheets("Production Source Header")
Sheets("Production Source Header").Select
Else
End If
Y = I
I = I + 1
Windows("Kitap.xlsx").Activate
Set Ws = Wb.Worksheets("Production Source Resource")
Sheets("Production Source Resource").Select
Set MyRange = Worksheets("Production Source Resource").Range("B:B")
SourceID3 = CVar(Application.WorksheetFunction.VLookup(SourceID, MyRange, 1, False))
Do While IsEmpty(SourceID3) = False And (I - Y = 1)
Set MyRange = Range("B:B")
SourceID3 = CVar(Application.WorksheetFunction.VLookup(SourceID, MyRange, 1, False))
Set FoundCell = ActiveSheet.Range("B:B").Find(What:=SourceID3)
If Not FoundCell Is Nothing Then
Index3 = FoundCell.Row
Resource = Cells(Index3, 1)
Windows("Kitap.xlsx").Activate
Set Ws = Wb.Worksheets("Production Source Header")
Sheets("Production Source Header").Select
Else
End If
I = I + 1
Loop
Loop
fullText = fullText & " Location: " & Location & " // Header: " & Product & " // Item: " & Item & " // Resource: " & Resource
Z = Z + 1
ProductID = Item
Set MyRange = Worksheets("Production Source Header").Range("B:C")
SourceID = (Application.WorksheetFunction.VLookup(ProductID, MyRange, 2, False))
T_Error:
If Err.Number = 1004 Then
On Error Resume Next
T = 1
Else
End If
Loop
IbpBomLevel = fullText
Else
MsgBox ("Bom Missing")
End If
End Function

Related

Replacing pos,neg values to another sheet

Screenshot#1
So i have to replace positive & negative numbers in column "A", from sheet "1" to sheet second[positive] and third sheet[negative].
Here is what i tried:
Sub Verify()
Dim row As Long
For row = 1 To 20
If ActiveSheet.Cells(row,1) <> "" Then
If ActiveSheet.Cells(row,1) > 0 Then
ActiveSheet.Cells(row,2) = ActiveSheet.Cells(row,1)
End If
End If
Next
End Sub
Here is what that program do:
Screenshot#2
So as we see i am getting positive values in column "B" sheet 1.
Your code is not currently working because you are only using ActiveSheet, rather than placing data on other worksheets as required. Below is some VBA code that loops column A in your original sheet, and outputs the data to column A in two different sheets as required:
Sub sSplitPositiveNegative()
Dim wsOriginal As Worksheet
Dim wsPositive As Worksheet
Dim wsNegative As Worksheet
Dim lngLastRow As Long
Dim lngPositiveRow As Long
Dim lngNegativeRow As Long
Dim lngLoop1 As Long
Set wsOriginal = ThisWorkbook.Worksheets("Original")
Set wsPositive = ThisWorkbook.Worksheets("Positive")
Set wsNegative = ThisWorkbook.Worksheets("Negative")
lngLastRow = wsOriginal.Cells(wsOriginal.Rows.Count, "A").End(xlUp).Row
lngNegativeRow = 2
lngPositiveRow = 2
For lngLoop1 = 1 To lngLastRow
If wsOriginal.Cells(lngLoop1, 1).Value > 0 Then
wsPositive.Cells(lngPositiveRow, 1) = wsOriginal.Cells(lngLoop1, 1)
lngPositiveRow = lngPositiveRow + 1
Else
wsNegative.Cells(lngNegativeRow, 1) = wsOriginal.Cells(lngLoop1, 1)
lngNegativeRow = lngNegativeRow + 1
End If
Next lngLoop1
Set wsPositive = Nothing
Set wsNegative = Nothing
Set wsOriginal = Nothing
End Sub
You will need to change the names of the worksheets referenced in the code to match those in your workbook.
Regards
Made the code a little reusable for you. Feel free to change sheet names or the last_row variable. The last_pos_val and last_neg_val are used so you won't have empty rows on the second and third sheet. You didn't specify what to do with zero, so it's currently added to the negative sheet.
Sub Verify()
Dim row As Long, last_row As Long, last_pos_val As Long, last_neg_val As Long
Dim ws_source As Worksheet, ws_pos As Worksheet, ws_neg As Worksheet
Dim wb As Workbook
Set wb = ThisWorkbook
Set ws_source = wb.Sheets("Sheet1")
Set ws_pos = wb.Sheets("Sheet2")
Set ws_neg = wb.Sheets("Sheet3")
last_pos_val = 1
last_neg_val = 1
last_row = 20
For row = 1 To last_row
If ws_source.Cells(row,1) <> "" Then
If ws_source.Cells(row,1) > 0 Then
ws_pos.Cells(last_pos_val,1) = ws_source.Cells(row,1)
last_pos_val = last_pos_val + 1
Else
ws_neg.Cells(last_neg_val,1) = ws_source.Cells(row,1)
last_neg_val = last_neg_val + 1
End If
End If
Next
End Sub
Split Positive & Negative
Adjust the values in the constants section.
Both subs are needed. The first sub calls the second one.
The Code
Option Explicit
Sub SplitPN()
Const Source As String = "Sheet1"
Const Positive As String = "Sheet2"
Const Negative As String = "Sheet3"
Const FirstRow As Long = 1
Const SourceColumn As Long = 1
Const PositiveFirstCell As String = "A1"
Const NegativeFirstCell As String = "A1"
Dim rngSource As Range
Dim rngPositive As Range
Dim rngNegative As Range
With ThisWorkbook
With .Worksheets(Source)
Set rngSource = .Columns(SourceColumn).Find(What:="*", _
LookIn:=xlFormulas, SearchDirection:=xlPrevious)
If rngSource Is Nothing Then Exit Sub
If rngSource.Row < FirstRow Then Exit Sub
Set rngSource = .Range(.Cells(FirstRow, SourceColumn), rngSource)
End With
Set rngPositive = .Worksheets(Positive).Range(PositiveFirstCell)
Set rngNegative = .Worksheets(Negative).Range(NegativeFirstCell)
End With
SplitPosNeg rngSource, rngPositive, rngNegative
End Sub
Sub SplitPosNeg(SourceRange As Range, PositiveFirstCell As Range, _
NegativeFirstCell As Range)
Dim Source, Positive, Negative
Dim UB As Long, i As Long
Source = SourceRange
UB = UBound(Source)
ReDim Positive(1 To UB, 1 To 1)
ReDim Negative(1 To UB, 1 To 1)
For i = 1 To UBound(Source)
Select Case Source(i, 1)
Case Is > 0: Positive(i, 1) = Source(i, 1)
Case Is < 0: Negative(i, 1) = Source(i, 1)
End Select
Next
PositiveFirstCell.Resize(UB) = Positive
NegativeFirstCell.Resize(UB) = Negative
End Sub

Using the LinEst function and return values in a column of variable length

I am trying to use the LinEst function to take values from a range of rows of data and input them into a new sheet under some headings. I only want to do this for a particular number of rows (up to row number defined as "c". My VBA skills are very basic.
Sub Button7_Click()
Sheets.Add.Name = "Down Sweep Power Law"
Dim xrng As Range, yrng As Range
Dim i As Long
Dim Rng As Range
Dim l As Long
Dim k As Long
Dim i2 As Long
Dim c As Long
Dim j As Long
Dim drop As Range
Dim drop2 As Range
Dim DownSweep As Chart, UpSweep As Chart, cht As Chart
Dim ws As Worksheet, smallest
Dim dsws As Worksheet
Set ws = Worksheets("Template") '<< use variables for worksheets!
Set dsws = Worksheets("Down Sweep Power Law")
Set Rng = ws.Range(ws.Range("B11"), ws.Range("B11").End(xlDown))
smallest = WorksheetFunction.Small(Rng, 1)
l = Rng.Find(what:=smallest, LookIn:=xlValues, lookat:=xlWhole).Row
k = Rng.Rows.Count
c = l - 10
Set xrng = ws.Range("C11:CP11")
Set yrng = ws.Range("C201:CP201")
Set drop = dsws.Range("A2")
Set x2rng = xrng.Offset(1, 0)
Set y2rng = yrng.Offset(1, 0)
Set drop2 = drop.Offset(1, 0)
dsws.Range("A1").Value = "(n-1) Value"
dsws.Range("B1").Value = "log(k) Value"
dsws.Range("C1").Value = "(n-1) Value"
dsws.Range("D1").Value = "n Value"
dsws.Range("E1").Value = "R Value"
If i < c Then
Set drop = Application.LinEst(Log10(yrng), Log10(xrng), True, False)
i = i + 1
End If
ITERATE:
If i < c Then
Set drop2 = Application.LinEst(Log10(y2rng), Log10(x2rng), True, False)
x2rng = x2rng.Offset(1, 0)
y2rng = y2rng.Offset(1, 0)
drop2 = drop2.Offset(1, 0)
i = i + 1
GoTo ITERATE
End If
End Sub
the code runs but when I go on the created sheet, there is a #NAME error (2029) and no values are present.
Is there a way to fix this?
Any help would be appreciated.
I think you have omitted a step from your plan. LinEst returns an array and you want to assign the values in that array to the range Drop. You can't assign the array directly to the range. Please try this code.
Option Explicit
Sub Button7_Click()
Dim xrng As Range, yrng As Range
Dim Drop As Range
Dim Arr As Variant ' LinEst result array
Dim Rng As Range
Dim R As Long
Dim l As Long
Dim k As Long
Dim i2 As Long
Dim c As Long
Dim j As Long
Dim DownSweep As Chart, UpSweep As Chart, cht As Chart
Dim ws As Worksheet, Smallest As Variant
Dim dsws As Worksheet
Set ws = Worksheets("Template") '<< use variables for worksheets!
Sheets.Add.Name = "Down Sweep Power Law"
Set dsws = Worksheets("Down Sweep Power Law")
Set Rng = ws.Range(ws.Range("B11"), ws.Range("B11").End(xlDown))
Smallest = WorksheetFunction.Small(Rng, 1)
l = Rng.Find(what:=Smallest, LookIn:=xlValues, LookAt:=xlWhole).Row
k = Rng.Rows.Count
c = l - 10
Set xrng = ws.Range("C11:CP11")
Set yrng = ws.Range("C201:CP201")
Set Drop = dsws.Range("C2:CP2").Offset(0, -2)
dsws.Range("A1").Value = "(n-1) Value"
dsws.Range("B1").Value = "log(k) Value"
dsws.Range("C1").Value = "(n-1) Value"
dsws.Range("D1").Value = "n Value"
dsws.Range("E1").Value = "R Value"
Do While R < c
Arr = Application.LinEst(Log10(yrng), Log10(xrng), True, False)
Drop.Value = Arr ' or perhaps: = Application.Transpose(Arr)
Set xrng = xrng.Offset(1, 0)
Set yrng = yrng.Offset(1, 0)
Set Drop = Drop.Offset(1, 0)
R = R + 1
Loop
End Sub
I don't know what kind of array LinEst will return. You may have to transpose the result.
I also tried to improve your management of ranges. However, the code is entirely untried, for lack of data. There may be logical errors in my code as well as typos but the syntax should be sound. It may not take you all the way over the finish line but I hope it will help you in your quest.

Check if value appears in a different worksheet

I have a list. I want to see if it appears in one of the other sheets and return a string dependent on the sheet it is in.
E.g pseudocode:
value = "Hi"
If value in sheet 2 Then
return "Yes"
If value in sheet 3 Then
return "TDB"
Else
return " "
The code I have so far
Public Function Check(product As String) As String
Dim BLRange As Range
Dim xlCell As Range
Dim BL As Worksheet
Dim TBDRange As Range
Dim TBD As Worksheet
Dim result As String
Set BL = ActiveWorkbook.Worksheets("Sheet2")
Set BLRange = BL.Range("A1:A1000")
Set TBD = ActiveWorkbook.Worksheets("Sheet3")
Set TBDRange = TBD.Range("A1:A1000")
For Each xlCell In BLRange
If xlCell.Value = product Then
Check = "Yes"
End If
Next xlCell
For Each xlCell In TBDRange
If xlCell.Value = product Then
Check = "TBD"
End If
Next xlCell
Check = ""
End Function
When I call Check("Hi") I get #VALUE!
Here's a version of the function using find like #urdearboy mentioned...
Public Function Check(product As String) As String
Dim BLRange As Range
Dim TBDRange As Range
Dim fndRng As Range
With ActiveWorkbook
Set BLRange = .Worksheets("Sheet2").Columns("A")
Set TBDRange = .Worksheets("Sheet3").Columns("A")
End With
Set fndRng = BLRange.Find(product)
If Not fndRng is Nothing Then Check = "Yes": Exit Function
Set fndRng = TBDRange.Find(product)
If Not fndRng is Nothing Then Check = "TBD": Exit Function
End Function
Try this code (already tested and works)
Public Function Check(product As String) As String
Dim BLRange As Range
Dim BL As Worksheet
Dim TBDRange As Range
Dim TBD As Worksheet
Dim result As String
Set BL = ActiveWorkbook.Worksheets("Sheet2")
Set BLRange = BL.Range("A1:A1000")
Set TBD = ActiveWorkbook.Worksheets("Sheet3")
Set TBDRange = TBD.Range("A1:A1000")
Check = "none"
For Each xlCell In BLRange
If xlCell.Text = product Then
Check = "Yes"
GoTo a
End If
Next xlCell
For Each xlCell In TBDRange
If xlCell.Text = product Then
Check = "TBD"
GoTo a
End If
Next xlCell
Exit Function
a:
End Function

' Range.Find' and ' Range.FindNext' to loop only over the first match

I am looping through a set of data with VBA Excel. I am trying to find a certain string using a combination the Range.Find and the Range.FindNext methods. I having a hard time figuring out where to place these statements because I need them to be in the scope of each other but at the same time I don't want the first Find statement to keep executing every time, hence only looping over the first match.
Sub AssignGroups()
Dim membership As Worksheet
Dim wb As Workbook
Dim groups As Worksheet
Dim nameRow As Long
Dim fullNameString As String
Dim nameRange As Range
Dim groupRange As Range
Dim nameRange2 As Range
Dim nameIndex As Long
Dim userNameString As String
Dim barIndex As Long
Set wb = ActiveWorkbook
Set membership = Sheets("User Group Membership")
Set groups = Sheets("User Assigned to Groups")
Set nameRange = membership.Range("A:A").Find("user -name", Lookat:=xlPart)
If Not nameRange Is Nothing Then
firstAddress = nameRange.Address
Set nameRange = membership.Range("A:A").Find("user -name", Lookat:=xlPart)
Do
membership.Activate
nameRow = nameRange.Row
MsgBox (nameRow)
fullNameString = membership.Cells(nameRow, "A").Value
MsgBox (fullNameString)
nameIndex = InStr(fullNameString, "user -name")
barIndex = InStr(fullNameString, "|")
MsgBox (nameIndex)
MsgBox (barIndex)
userNameString = Mid(fullNameString, nameIndex + 12, ((barIndex - 4) - (nameIndex + 12)))
groups.Activate
Set nameRange2 = groups.Range("A:CH").Find(userNameString)
nameColumn = nameRange2.Column
membership.Activate
membership.Cells(nameRow, "A").Activate
Do
ActiveCell.Offset(1).Activate
If Not IsEmpty(ActiveCell.Value) Then
cellValue = ActiveCell.Value
groups.Activate
Set groupRange = groups.Range("A:CH").Find(cellValue, , , Lookat:=xlWhole)
groupRow = groupRange.Row
groups.Cells(groupRow, nameColumn).Activate
ActiveCell.Value = "X"
membership.Activate
End If
Loop Until IsEmpty(ActiveCell.Value)
Set nameRange = membership.Range("A:A").FindNext(ActiveCell)
Loop While Not nameRange Is Nothing And nameRange.Address <> firstAddress
End If
End Sub
How could I place these statements so that it would loop over all the matches, one after another?

excel vba macro - extract value from where clause

In columnA of excel sheet 'Input' I have the following (with each line being on a new row in the sheet):
update my_table
set time = sysdate,
randfield1 = 'FAKE',
randfield5 = 'ME',
the_field8 = 'test'
where my_key = '84'
;
update my_table
set time4 = sysdate,
randfield7 = 'FAeKE',
randfield3 = 'MyE',
the_field9 = 'test'
where my_key = '37';
I'm trying to create a new sheet 'output' that only contains the following values in columnA but I don't know how to extract the bit in between the quotes after --> where my_key:
84
37
Some notes: it would be great to be able to specify the fieldname in cell B1 of sheet 'input', in this example it would be my_key.
Previously, I've been doing this manually using filter column where text contains 'where' then stripping out everything after the equals then doing a find/replace on single quotes and ;s. Has anyone been able to achieve this with a single button click macro?
While using Filtering or Find is very efficient I don't think you will see much difference in using a variant array to hold the all values for your Input Sheet, to be tested against a regex using a fieldname in InputB1, with any numeric portions of the match being dumped to Column A Output.
Sub VarExample()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim X
Dim Y
Dim lngRow As Long
Dim objRegex
Dim objRegexMC
Set ws1 = ActiveWorkbook.Sheets("Input")
Set ws2 = ActiveWorkbook.Sheets("Output")
Set objRegex = CreateObject("vbscript.regexp")
objRegex.Pattern = ".+where.+" & ws1.[b1] & ".+\'(\d+)\'.*"
X = ws1.Range(ws1.[a1], ws1.Cells(Rows.Count, "A").End(xlUp)).Value2
ReDim Y(1 To UBound(X, 1), 1 To UBound(X, 2))
For lngRow = 1 To UBound(X, 1)
If objRegex.test(X(lngRow, 1)) Then
Set objRegexMC = objRegex.Execute(X(lngRow, 1))
lngCnt = lngCnt + 1
Y(lngCnt, 1) = objRegexMC(0).submatches(0)
End If
Next
ws2.Columns("A").ClearContents
ws2.[a1].Resize(UBound(Y, 1), 1).Value2 = Y
End Sub
A simple solution but definitely not a good one could be like this:
Sub getWhere()
Dim sRow as Integer
Dim oRow as Integer
Dim curSheet as Worksheet
Dim oSheet as Worksheet
dim words() as String
Set curSheet = ThisWorkbook.Sheets("Input")
Set oSheet = ThisWorkbook.Sheets("Output")
sRow = 1
oRow = 1
Do while curSheet.Range("A" & sRow).Value <> ""
If Instr(lcase(curSheet.Range("A" & sRow).Value), "where") > 0 Then
words = Split(curSheet.Range("A" & sRow).Value, " ")
oSheet.Range("B" & oRow).Value = words(1)
oSheet.Range("C" & oRow).Value = getNumeric(words(3))
oRow = oRow + 1
End If
sRow = sRow +1
Loop
End Sub
Function getNumeric(ByVal num As String) As Long
Dim i As Integer
Dim res As String
For i = 1 To Len(num)
If Asc(Mid(num, i, 1)) >= 48 And Asc(Mid(num, i, 1)) <= 57 Then res = res & Mid(num, i, 1)
Next
getNumeric = CLng(res)
End Function

Resources