Excel VBA - Formula writing error - vba

I am trying to bring a formula inside my vba code and I am getting an error inside it. Please have a look into the code and kindly share your thoughts.
This is my excel function that was written in VBA Code :
GetUniqueCount(Range,Value)
And here is the VBA Code trying to make use of it :
Sheets("sheet2").Activate
With ThisWorkbook.Sheets("sheet2").UsedRange
lastrow = .Rows(.Rows.Count).Row
End With
For i = 14 To lastrow
check = Range("h" & i).Value
If check <> "" Then
Range("I" & i).Value = WorksheetFunction.GetUniqueCount(sheet1!.Range("A1:B100"), check)
Else
Range("I" & i).Value = ""
Next
The range for the function comes from a different sheet. How do I write it in VBA?
This is the function for it :
Function GetUniqueCount(Rng1 As Range, Lookup As String) As Long
Dim x, dict
Dim i As Long, cnt As Long
Set dict = CreateObject("Scripting.Dictionary")
x = Rng1.Value
For i = 1 To UBound(x, 1)
If x(i, 1) = Lookup Then
dict.Item(x(i, 1) & x(i, 2)) = ""
End If
Next i
GetUniqueCount = dict.Count
End Function

You have other possible errors in your code, unqualified Range, etc.
Since your Function GetUniqueCount is not Excel's built in WorksheetFunction, but your own UDF, you don't need to call it with WorksheetFunction.GetUniqueCount but just GetUniqueCount.
Try the code below:
Option Explicit
Sub Test()
Dim LastRow As Long, i As Long
Dim check As String
With ThisWorkbook.Worksheets("sheet2")
LastRow = .Cells(.Rows.Count, "I").End(xlUp).Row
Dim Rng As Range
Set Rng = Worksheets("sheet1").Range("A1:B100")
For i = 14 To LastRow
check = .Range("H" & i).Value
If check <> "" Then
.Range("I" & i).Value = GetUniqueCount(Rng, check)
Else
.Range("I" & i).Value = ""
End If
Next i
End With
End Sub

There is no worksheet function by the name of GetUniqueCount. If this is a function you have in your code then the way to call it would be like this:-
Range("I" & i).Value = GetUniqueCount("Sheet1".Range("A1:B100"), check)
This code presumes that your function is either on the same code sheet as the calling procedure or declared public. It must take two arguments, the first of which must be a range, the second of the same data type as check. If you didn't declare check (which isn't a good idea) then its data type will be Variant.

Related

Run-time error 1004: Application-defined or object-defined error in VBA

I'm trying to convert a column of values from Decimal to Binary and for some reason I get this error. I'm new to VBA coding so i might be missing on some basic information.
The code I have:
Private Sub CommandButton1_Click()
Dim i As Integer
Dim r As Range
Dim myRange As Range: Set myRange = Range("E2:E2000") 'define your range
Dim rcopy As Range
Dim myCopyRange As Range: Set myCopyRange = Range("P2:P2000") 'range for the converted values
For i = 1 To myRange.Cells.Count
myCopyRange.Cells(i).Value = WorksheetFunction.Dec2Bin(myRange.Cells(i).Value)
Next
End Sub
My E column with the values that must be converted is set on Number format, and my P column is set right now on Text. However I tried changing the format of the columns to Number or General and I keep on getting the same error.
Thank you for the help:)
So a few things:
1). Dec2Bin can't handle numbers larger than 511 and will throw a 1004Error if you try to do so.
2). You might want to use long variable for numbers, there is no point really to use integer.
3). Another way in doing this would be like:
Private Sub CommandButton1_Click()
Dim i As Long, LR as Long
With Sheets(1)
LR = .Range("E" & .Rows.Count).End(xlUp).Row
For i = 1 To LR
If .Cells(i, 5).value < 512 then .Cells(i, 16).Value = WorksheetFunction.Dec2Bin(.Cells(i, 5).value)
Next i
End with
End Sub
EDIT
You could also opt to include a formula in your loop that will work with 32bit like so:
Private Sub CommandButton1_Click()
Dim i As Long, LR As Long
With Sheets(1)
LR = .Range("E" & .Rows.Count).End(xlUp).Row
For i = 1 To LR
.Cells(i, 16).Formula = "=DEC2BIN(E" & i & "/512^3,5) & DEC2BIN(INT(MOD(E" & i & ",512^3)/512^2),9) & DEC2BIN(INT(MOD(E" & i & ",512^2)/512),9) & DEC2BIN(MOD(E" & i & ",512),9)"
Next
End With
End Sub
EDIT2
For a dynamic sized bitnumber, you might want to use a UDF. See below:
Function DecToBin(ByVal DecimalIn As Variant, Optional NumberOfBits As Variant) As String
DecToBin = ""
DecimalIn = CDec(DecimalIn)
Do While DecimalIn <> 0
DecToBin = Trim$(Str$(DecimalIn - 2 * Int(DecimalIn / 2))) & DecToBin
DecimalIn = Int(DecimalIn / 2)
Loop
If Not IsMissing(NumberOfBits) Then
If Len(DecToBin) > NumberOfBits Then
DecToBin = "Error - Number too large for bit size"
Else
DecToBin = Right$(String$(NumberOfBits, "0") & _
DecToBin, NumberOfBits)
End If
End If
End Function
And this is how you incorporate this:
Private Sub CommandButton1_Click()
Dim i As Long, LR as Long
With Sheets(1)
LR = .Range("E" & .Rows.Count).End(xlUp).Row
For i = 1 To LR
.Cells(i, 16).Value = "'" & DecToBin(.Cells(i, 5).value)
Next i
End with
End Sub
Here is a usefull Link
You could also just call the function from your worksheet typing this formule:
=DecToBin(E1)
Hope that helps :)
Try this:
Sub test()
Dim i
For i = 2 To 2000
Cells(i, "P").Value = Application.Dec2Bin(Cells(i, "E").Value)
Next i
End Sub
Dec2Bin only accepts numbers, otherwise it throws 1004. So you should check the type of the value before passing it to Dec2Bin, like this:
If Typename(myRange.Cells(i).Value)="Double" Then ...
Typename returns Double for numbers, other values can be String, Date, Empty.

Different sheet pasting

I have written a code which gives me the errors (if any cell is non numeric) in a separate sheet called "Error_sheet".
But the output is a bit clumsy as it gives me non numeric cell address in a confusing fashion. Like the errors will not be pasted one after another. There will be some blanks in between if there are more than one non Numeric cells.
Sub Test()
Dim LastRow As Long, i As Long
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
For i = 2 To LastRow
If IsNumeric(Range("A" & i).Value) Then
Else
Sheets("Error").Range("A" & Row).Value = "Error in" & i & " row of ColumnNAme"
Row = Row + 1
End If
Next i
End Sub
It gives me output like shown below but can I get the output like Error in 7,14 rows of column name in a desired cell of "Error_sheet".
[![Output][1]][1]
[1]: https://i.stack.imgur.com/JqXwq.png
My understanding of what you've written is that you want something like this.
Option Explicit
Sub Test()
' Unqualified book/sheet below, means code will always run the isnumeric check on the cells of the active sheet. Is that what you want? '
Dim LastRow As Long
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
Dim Index as long
Dim i As Long
Dim NonNumericRows() as string
Redim NonNumericRows(1 to lastrow)
For i = 2 To LastRow
If not(IsNumeric(Range("A" & i).Value)) Then
Index = index + 1
NonNumericRows(Index) = cstr(i)
End if
Next i
Redim preserve NonNumericRows(1 to index)
Sheets("Error").Range("A1").Value = "Error in row(s): " & strings.join(nonnumericrows,", ") & " of ColumnNAme"
End Sub
Hope it works or helps.
Like QHarr suggested, using Option Explicit is normally a good idea, and try not to use VBA operators as variables.
Also when working with more than 1 sheet, its best to define each in the code. I dont know what your first sheet is called, so please change the line: Set shSource = Sheets("Sheet1") to suit:
Option Explicit
Sub SubErrorSheet()
Dim lr As Long, i As Long
Dim shSource As Worksheet, shError As Worksheet
Set shSource = Sheets("Sheet1")
Set shError = Sheets("Error")
lr = shSource.Range("A" & Rows.count).End(xlUp).Row
For i = 2 To lr
If Not IsNumeric(shSource.Range("A" & i).Value) Then
shError.Range("A" & Rows.count).End(xlUp).Offset(1, 0).Value = "Error in row " & i & " of ColumnNAme"
End If
Next i
End Sub

How to combine multiple macros and excel functions into a single macro that executes on button click?

I need to combine multiple macros to a single macro that executes on button click. Kindly excuse me if I write anything wrong since I am completely new to excel macros and vb.
Following is the scenario.
Steps:
Calculate total
Extract reference
Compare total field value for matching reference and mark that as "Complete" if sum of total for matching references calculates to ).
(Explained...)
First i calculate the debit and credit amount to a new column called total, for this, initially I used the SUM function. after that I tried the same using the macro that executes on button click
(old macro)
Private Sub getTotal_Click()
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
For i = 5 To lastRow
Range("K" & i).Value = Range("F" & i).Value + Range("G" & i).Value
Next i
End Sub
This was so much time consuming (took around 2 hrs when executed on 75k records) than when using the formula (which finished in minutes). I am still not able to understand the reason for this. However modifiying to Dy.Lee's answer below, it took only seconds to calculate the total.
(modified based on Dy.Lee's answer)
Private Sub getTotal_Click()
Dim vDB As Variant, vR() As Variant
Dim i As Long, n As Long, lastRow As Long
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
vDB = .Range("R5", "S" & lastRow)
n = UBound(vDB, 1)
ReDim vR(1 To n, 1 To 1)
For i = 1 To n
vR(i, 1) = vDB(i, 1) + vDB(i, 2)
Next i
.Range("AL5").Resize(n) = vR
End With
End Sub
Now moving on to the second macro which I used to extract a pattern from strings in a column D and E.
Function extractReference(cid_No As String, pm_source As String)
Dim regExp As Object, findMatches As Object, match As Object
Dim init_result As String: init_result = ""
Set regExp = CreateObject("vbscript.regexp")
With regExp
.Global = True
.MultiLine = False
.Pattern = "(?:^|\D)(\d{5,6})(?!\d)"
End With
Set findMatches = regExp.Execute(pm_source)
For Each match In findMatches
init_result = init_result + match.SubMatches.Item(0)
Next
If init_result <> "" Then
extractReference = cid_No & " | " & init_result
Else
extractReference = ""
End If
End Function
This macro was working fine.
Finally I used the following function after copying both the extracted reference and total to a new sheet and creating a datatable for that
=IF(ISBLANK([#Reference]), "", (IF((ROUND(SUMIFS([Total],[Reference],[#Reference]),2)=0), "complete", "")))
This also worked fine.
Now what I actually want is I need to avoid creating any new data tables or sheets and preform all this within current sheet on a single button click. Is there anyway that can be done without making the macro a time consuming process? Your help is higly appreciated!
Thanks in Advance
for the first part try:
Private Sub getTotal_Click()
Dim lastRow As Long
Dim sumRange As Range
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
Set sumRange = Range(Range("K5"), Range("K" & lastRow))
sumRange.FormulaR1C1 = "=RC[-5]+RC[-4]"
sumRange.Copy
sumRange.PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
End Sub
also, if you still want to loop notice that calling cell like .Cells(1, 1) is faster than Range("A1")
You need using Variant Array. It is faster.
Private Sub getTotal_Click()
Dim vDB As Variant, vR() As Variant
Dim i As Long, n As Long, lastRow As Long
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
vDB = .Range("f5", "g" & lastRow)
n = UBound(vDB, 1)
ReDim vR(1 To n, 1 To 1)
For i = 1 To n
vR(i, 1) = vDB(i, 1) + vDB(i, 2)
Next i
.Range("k5").Resize(n) = vR
End With
End Sub

VBA Value Error

I'm fairly new to VBA. Basically, my code attempts to output a classification based on the maximum value in an area column, which is has several categories. The logic seems right but I keep on getting a #VALUE! error. Any help would be much appreciated!
Public Function luclass(NAPS As Double) As String
Dim lastrow As Long
Dim c As Range, rng As Range
Dim maxclass As String
Dim maxshape As Double
With ThisWorkbook.Worksheets("LandUseClass2")
lastrow = .Cells(.Rows.Count, "B").End(xlUp).Row
maxclass = "Blank"
maxshape = 0
For Each c In .Range("B2:B650" & lastrow)
If c.Value = NAPS Then
If .Range("F" & c.Row).Value > maxshape Then
.Range("C" & c.Row).Text = maxclass
End If
End If
Next c
End With
luclass = maxclass
End Function
Change .Range("B2:B650" & lastrow) to .Range("B2:B" & lastrow)
Change .Range("C" & c.Row).Text = maxclass to .Range("C" & c.Row).Value = maxclass as .text is a readonly property.
You are getting the #Value error because you are trying to write to a range in a function.
Use a Sub instead of a Function or explain what exactly are you tying to achieve and we will take it form there :)
Public Sub luclass(NAPS As Double)

Trying to Add a Vlookup Piece to my Excel Macro

I'm trying to add a Vlookup piece to a long macro that I'm working on to eliminate some daily data manipulation work.
Essentially everyday I have four new columns of data that I compare to the day befores, using vlookup. The four new columns sit in columns C-F and the old data in columns M-P. I vlookup column D against column M, with the formula in column G.
I'm running into a problem of how to be flexible with the range I give the macro to use each day as I don't want to constantly change it. The amount of rows will fluctuate between 10,000-30,000.
Here is my code- I'm probably thinking about this all wrong.
Sub Lookup()
Dim i, LastRow
Set i = Sheets("data").Range("F5").End(xlUp)
If Cells(i, "F5").Value <> "" Then
Range(i, "G").Value = WorksheetFunction.VLookup(Cells(i, "D"), Range("N").End(xlDown), 1, False)
End If
End Sub
Give this a go
Sub Sheet2_Button1_Click()
Dim Rws As Long, rng As Range, Mrng As Range, x
Rws = Cells(Rows.Count, "D").End(xlUp).Row
Set rng = Range(Cells(1, "G"), Cells(Rws, "G"))
Set Mrng = Range("M1:M" & Rws)
rng = "=IFERROR(VLOOKUP(D1, " & Mrng.Address & ",1,0),""Nope"")"
'----------If you want it to be just values uncomment the below line--------------
' rng.Value=rng.Value
End Sub
You have some backwards range references. I can't speak to the vlookup call, but you can start by looking at this part:
If Cells(i, "F5").Value <> "" Then
Range(i, "G").Value = WorksheetFunction.VLookup(Cells(i, "D"), Range("N").End(xlDown), 1, False)
End If
Try changing it to this to fix the range declarations:
If Range("F" & i).Value <> "" Then
Range("G" & i).Value = WorksheetFunction.VLookup(Range("D" & i), Range("N").End(xlDown), 1, False)
End If