I have a simple function using Excel VBA for calculating volatility. It takes as inputs a column of numbers (Zeros) and two dates. The code is:
Function EWMA(Zeros As Range, Lambda As Double, MarkDate As Date, MaturityDate As Date) As Double
Dim vZeros() As Variant
Dim Price1 As Double, Price2 As Double
Dim SumWtdRtn As Double
Dim I As Long
Dim m As Double
Dim LogRtn As Double, RtnSQ As Double, WT As Double, WtdRtn As Double
vZeros = Zeros
m = Month(MaturityDate) - Month(MarkDate)
For I = 2 To UBound(vZeros, 1)
Price1 = Exp(-vZeros(I - 1, 1) * (m / 12))
Price2 = Exp(-vZeros(I, 1) * (m / 12))
LogRtn = Log(Price1 / Price2)
RtnSQ = LogRtn ^ 2
WT = (1 - Lambda) * Lambda ^ (I - 2)
WtdRtn = WT * RtnSQ
SumWtdRtn = SumWtdRtn + WtdRtn
Next I
EWMA = SumWtdRtn ^ (1 / 2)
End Function
The main feature enabling the function to work is the For loop. I want to re-create this in Access VBA using recordset objects. The recordset has the same fields as the Excel spreadsheet. I'm not exactly sure how to convert the code over, though. Here is what I have so far:
Function EWMA(rsCurve As Recordset, InterpRate As Double, Lambda As Double) As Double
Dim vZeros() As Variant
Dim Price1 As Double, Price2 As Double
Dim SumWtdRtn As Double
Dim I As Long
Dim mat As Date
Dim mark As Date
Dim LogRtn As Double, RtnSQ As Double, WT As Double, WtdRtn As Double
CurveInterpolateRecordset = Rnd()
If rsCurve.RecordCount <> 0 Then
vZeros = CVar(rsCurve.Fields("CurveInterpolateRecordset"))
mat = CDate(rsCurve.Fields("MaturityDate"))
mark = CDate(rsCurve.Fields("MarkDate"))
m = Month(mat) - Month(mark)
For I = 2 To UBound(vZeros, 1)
Price1 = Exp(-vZeros(I - 1, 1) * (m / 12))
Price2 = Exp(-vZeros(I, 1) * (m / 12))
LogRtn = Log(Price1 / Price2)
RtnSQ = LogRtn ^ 2
WT = (1 - Lambda) * Lambda ^ (I - 2)
WtdRtn = WT * RtnSQ
SumWtdRtn = SumWtdRtn + WtdRtn
Next I
EWMA = SumWtdRtn ^ (1 / 2)
End If
Debug.Print EWMA
End Function
The function is called in an earlier subroutine in Access. What am I missing in order to move through the recordset in Access, similar to looping through the spreadsheet in Excel VBA?
The easiest method would be to use GetRows to pull an array from your recordset:
Recordset.GetRows Method
Then the new code would be nearly a copy-n-paste of your proven code starting with basically this:
vZeros = rsCurve.GetRows(rsCurve.RecordCount)
As a side note you wouldn't need CDate here:
mat = rsCurve.Fields("MaturityDate").Value
Here are some basics about using a recordset.
Dim rs As New ADODB.Recordset
'Add fields to your recordset for storing data.
With rs
.Fields.Append "Row", adInteger
.Fields.Append "ColumnName2", adChar, 30
.Fields.Append "ColumnName3", adInteger
.Open
End With
Add records to it manually
rs.AddNew
rs.Fields("Row").Value = 1
rs.Fields("ColumnName2").Value = "Put some value in"
rs.Update
rs.AddNew
rs.Fields("Row").Value = 2
rs.Fields("ColumnName2").Value = "Put another value in"
rs.Update
You can also populate it with a query of a table.
Move to the begining of the recordset
If rs.EOF = False Then
rs.MoveFirst
End If
Loop through the recordset
Do While rs.EOF = False
msgbox(rs.Fields("ColumnName2").Value)
rs.MoveNext
Loop
Related
I developed some code for an Access Database that manipulates a string with a statement like:
myString = Left(myString, somePosition) & Right(myString, someOtherPosition)
the above is part of a loop that has thousands of iterations and the variable myString is thousand of characters long.
I know the above code is bad practice in Java and a StringBuffer should be used instead of a string.
My code is taking a lot of time to run (about 7 minutes) and I suspect the problem might be related to the heavy string manipulation that is going on. Can you please confirm if there is anything similar to StringBuffer in VBA that could improve the efficiency of my code?
Update: full code with StringBuilder
Function SelectColumns2(str As String, columns As String, separator As String) As String
'column_number is the number of the column we are reading when we loop through a line
'z is the counter of the field (a portion of str between two separators)
'i is the counter of the str (the position of the modified string)
Dim column_number As Integer, i As Double, z As Integer, leftPosition As Double
'stringbuilder that stores the string that will represent the final file
Dim sb As StringBuilder, leftStr As StringBuilder, rightStr As StringBuilder
Set sb = New StringBuilder
Set leftStr = New StringBuilder
Set rightStr = New StringBuilder
sb.Append str
column_number = 1
i = 1 ' full str
z = 0 ' full field
While sb.Length >= i
z = z + 1
If Mid(sb.Text, i, 1) = separator Then
If InStr(1, columns, "/" & column_number & "/") = 0 Then
leftStr.Append left(sb.Text, i - z)
rightStr.Append right(sb.Text, sb.Length - i)
sb.Clear
sb.Append leftStr.Text
sb.Append rightStr.Text
leftStr.Clear
rightStr.Clear
i = i - z
End If
column_number = column_number + 1
z = 0
ElseIf Mid(sb.Text, i, 1) = Chr(10) Then
If InStr(1, columns, "/" & column_number & "/") = 0 Then
leftPosition = max((i - z - 1), 0)
If leftPosition = 0 Then
leftStr.Append left(sb.Text, leftPosition)
rightStr.Append right(sb.Text, sb.Length - i)
sb.Clear
sb.Append leftStr.Text
sb.Append rightStr.Text
Else
leftStr.Append left(sb.Text, leftPosition)
rightStr.Append right(sb.Text, sb.Length - i + 1)
sb.Clear
sb.Append leftStr.Text
sb.Append rightStr.Text
End If
leftStr.Clear
rightStr.Clear
i = i - z
End If
column_number = 1
z = 0
End If
i = i + 1
Wend
SelectColumns2 = left(sb.Text, sb.Length - 1)
End Function
You can use CreateObject to create the .Net stringbuilder class. Note that you will have to have the relevant .Net library installed, and VBA does not support overloading, so it will handle a little differently than in VB.Net.
Sample code:
Public Sub TestSB()
Dim sb As Object
Set sb = CreateObject("System.Text.StringBuilder")
sb.Append_3 "Hello"
sb.Append_3 " "
sb.Append_3 "World"
sb.Append_3 "!"
Debug.Print sb.ToString
End Sub
Alternatively, you can build your own stringbuilder. This answer provides a stringbuilder class, and this question also shows some sample code.
You can - for an extremely simple implementation - use Mid.
For example, this code runs in about 0.1 ms for the quite large strings entered:
Public Function ChopString() As String
Dim Source As String
Dim LeftPart As Long
Dim RightPart As Long
Dim Result As String
Source = String(100000, "x")
LeftPart = 30000
RightPart = 40000
Result = Space(LeftPart + RightPart)
Mid(Result, 1) = Left(Source, LeftPart)
Mid(Result, 1 + LeftPart) = Right(Source, RightPart)
ChopString = Result
End Function
For smaller strings of a few K, it runs way faster.
The task: Create a report for Part Numbers that shows several types (On Hand, On Order, etc) in date buckets with each type totaled for the specific range.
For example:
Item 1 => (could be over 2000)
2/5/2017 2/19/2017 2/28/2017 (30 weeks)
On Hand 20 42 33
On Order 0 5 4
Each item is shown on it's own page with related metadata about the item. Each date bucket is based on a user-entered start date with a calculation running against the data set to determine what goes in which bucket and what the totals are.
I have this report fully working for one item. User types one item, selects a date, and the report is created using the following:
Inventory Meta general information and description of the item
Inventory Detail gets all the detailed information
Inventory Totals gets totals for each Types
GetInventory() VBA sets up the buckets and populates the totals
Using a query to get the date buckets would perhaps be easier to get the data into the report. Creating a query with 210 calculated columns (7 types, 30 weeks) wasn't a reasonable approach.
Naturally, selecting one item at a time is not what's wanted.
I have a select box that gets whatever Part Numbers are selected and creates a query on the fly for the Inventory Meta (main report). I have similar code working that runs with the Inventory Totals (sub report) to create a query on the fly for that.
But, as with the Inventory Totals query, each date is a unique value and is it's own row. What I need to be able to do is run the code to build the buckets for each item selected.
I'm stuck.
I have created an array of item numbers (whatever was selected). I can see what's in the array.
What I can't seem to figure out is how to feed each to the code that runs the date comparisons and calculations so that I get a full set of data for each Part Number.
With one number it was easy.... "this one"
vItem = [Forms]![fOptions]![ItemNumber]
Set db = CurrentDb
strSelect = "Select * FROM qInventoryTotals WHERE qInventoryTotals.ItemNumber = [this_one]"
Set qdf = db.CreateQueryDef(vbNullString, strSelect)
qdf.Parameters("this_one").Value = vItem
Set inv = qdf.OpenRecordset
The closest I've come is getting the report to show the same set of data for all part numbers. I suspect there is some small but critical thing, like where a particular loop starts or a variable I've missed or something.
The result of the following is a message box that repeats the same total for each of the part numbers.
Private Sub CreateOne_Click()
On Error GoTo Err_cmdOpenQuery_Click
'----------- Selection box check for dates -------------
If IsNull(Forms!fFish1!Week1) Then
MsgBox "A Sunday date must be selected", , "Please select a date"
ElseIf Weekday(Forms!fFish1!Week1) = 1 Then
'MsgBox "That is Sunday"
Forms!fFish1!Week1 = Forms!fFish1!Week1
Else
MsgBox "Starting Week needs to be a Sunday date" _
, , "Sorry, that's not Sunday"
' clears the 'not Sunday' selection
Forms!fFish1!Week1 = ""
Exit Sub
End If
'-------------------------------------------------
' Declarations =====================================
Dim db As DAO.Database
Dim iMeta As DAO.QueryDef
Dim i As Integer
Dim strSQL As String
Dim strWhere As String
Dim strIN As String
Dim flgSelectAll As Boolean
Dim varItem As Variant
Dim strSlect As String
Dim vItem As Variant
' Setup -------------------------------------
Set db = CurrentDb()
strSQL = "SELECT * FROM qInventoryTotals2"
'----------------------------------------------------------------------
' Get whatever is selected and loop through the selections
' This defines which numbers are in the list
'----------------------------------------------------------------------
For i = 0 To Forms!fFish1.box4.ListCount - 1
If Forms!fFish1.box4.Selected(i) Then
If Forms!fFish1.box4.Column(0, i) = "All" Then
flgSelectAll = True
End If
strIN = strIN & "'" & Forms!fFish1.box4.Column(0, i) & "',"
End If
Next i
'Create the WHERE string, and strip off the last comma of the IN string
strWhere = " WHERE [ItemNumber] in " & _
"(" & Left(strIN, Len(strIN) - 1) & ")"
'If "All" was selected in the listbox, don't add the WHERE condition
If Not flgSelectAll Then
strSQL = strSQL & strWhere
End If
'-------------------------------------------------------
' Create a query that has all the selected item numbers
db.QueryDefs.Delete "qInventoryTotals3"
Set iMeta = db.CreateQueryDef("qInventoryTotals3", strSQL)
Set inv = iMeta.OpenRecordset
'==========================================================================
' Create an array to pull out each of the Item numbers one at a time
Dim Count As Integer, r As Integer
Count = 0
For i = 0 To Forms!fFish1.box4.ListCount - 1
If Forms!fFish1.box4.Selected(i) Then
vItem = Forms!fFish1.box4.Column(0, i)
'vItemFilter = Forms!fFish1.box4.Column(0, i)
'MsgBox (vItem), , "one by one"
Count = Count + 1
End If
Next i
''MsgBox (Count), , "count how many items are in the set"
' Get the count for how many items are in the currently selected list
' Displays one item at a time -
' Set up the array ------------------------------
'------------------------------------------------
ReDim vItem(Count)
r = 0
For i = 0 To Forms!fFish1.box4.ListCount - 1
If Forms!fFish1.box4.Selected(i) Then
vItem(r) = Forms!fFish1.box4.Column(0, i)
r = r + 1
End If
Next i
'Check the values stored in array
''For i = 0 To Count - 1
''MsgBox vItem(i), , "show all values from the array"
''Next
' have all values from the array. Each in it's own message box
'===============================================================================
' Set up the item numbers ---------------------------
Dim part As Variant
part = vItem
With vItem
For i = LBound(vItem) To UBound(vItem) - 1
MsgBox ("There are" & " " & (vItem(i)) & " " & "fishies"), , "Whatcha' got now?"
' cycles through each number
' Past Due ============================================
Dim tPOPast As Double
Dim tBCPast As Double
Dim tBPast As Double
Dim tEPast As Double
If inv!ItemNumber = part(i) And inv.Fields("RequiredDate") < Forms!fFish1!Week1 Then
'displays the first part number with it's value, then the remaining numbers with no value
' If inv.Fields("RequiredDate") < Forms!fFish1!Week1 Then
'displays each of the part numbers with the same value
tBPast = inv.Fields("TotalOnHand")
tPOPast = tPOPast + inv.Fields("SumOfSupply")
tBCPast = tBCPast + inv.Fields("SumOfDemand")
' Calculate ending inventory for the week ===================
tEPast = tBPast + tPOPast + tBCPast
' Show something for testing ==============================
MsgBox (tBPast & " " & part(i)), , "show Me the money" ' displays same total for each part number
End If
'end this condition, next condition follows
'----------------- do it again -------------------------------
Next
' Finished with the weekly buckets =====================================
End With
'=========================================================================
'-------------------- error management for the selection box ------------------
Exit_cmdOpenQuery_Click:
Exit Sub
Err_cmdOpenQuery_Click:
If Err.Number = 5 Then
MsgBox "Pick one, or more, item numbers from the list" _
, , "Gotta pick something!"
Resume Exit_cmdOpenQuery_Click
Else
'Write out the error and exit the sub
MsgBox Err.Description
Resume Exit_cmdOpenQuery_Click
End If
'---------------------------------------------------------------------------
End Sub
The solution I found was to set variables for the values from the array and use them to dynamically update a table. From that, I created a query to sum the values and used that as the basis for the report. The key was GetRows()
Get the unique items and read the the rows of data into the first array
Dim rNum As Integer
rNum = myItems.RecordCount
Dim varItem As Variant
Dim intRi As Integer 'rows of unique items
Dim intCi As Integer 'columns of unique items
Dim intNCi As Integer
Dim intRCi As Integer
varItem = myItems.GetRows(rNum)
intNRi = UBound(varItem, 2) + 1
intNCi = UBound(varItem, 1) + 1
For intRi = 0 To intNRi - 1
For intCi = 0 To intNCi - 1
vItem = varItem(intCi, intRi)
Use vItem to dynamically create a new recordset to set up the weekly buckets
strSelect = "Select * FROM qInventoryTotals2 WHERE qInventoryTotals2.ItemNumber = [this_one]"
Set qdf = db.CreateQueryDef(vbNullString, strSelect)
qdf.Parameters("this_one").Value = vItem
Set inv = qdf.OpenRecordset
Count the records, create a second array
Dim invNum As Integer
invNum = inv.RecordCount
Dim varRec As Variant
Dim intR As Integer
Dim intC As Integer
Dim intNC As Integer
Dim intRC As Integer
Dim cItem As String
Dim cRequired As Date
Dim cPO As Double
Dim cBC As Double
Dim cOnHand As Double
varRec = inv.GetRows(invNum)
intNR = UBound(varRec, 2) + 1
intNC = UBound(varRec, 1) + 1
For intR = 0 To intNR - 1
For intC = 0 To intNC - 1
cItem = varRec(0, intR)
cRequired = varRec(1, intR)
cOnHand = varRec(2, intR)
cPO = varRec(3, intR)
cBC = varRec(4, intR)
cSO = varRec(5, intR)
cPD = varRec(6, intR)
cIN = varRec(7, intR)
cJT = varRec(8, intR)
cWO = varRec(9, intR)
'------------- finish getting inventory columns --------------------
Next intC
And then set up the buckets for each week
If cRequired < Week1 Then
recOut.AddNew
recOut.Fields("ItemNumber") = cItem
recOut.Fields("tB") = cOnHand
recOut.Fields("tPO") = cPO
recOut.Fields("tBC") = cBC
recOut.Fields("tSO") = cSO
recOut.Fields("tPD") = cPD
recOut.Fields("tIN") = cIN
recOut.Fields("tJT") = cJT
recOut.Fields("tWO") = cWO
recOut.Fields("tE") = cOnHand + cPO + cBC + cSO + cPD + cIN + cJT + cWO
recOut.Fields("RequiredDate") = cRequired
recOut.Fields("GroupDate") = Week1
recOut.Update
' tE0 = cOnHand + cPO + cBC + cSO + cPD + cIN + cJT + cWO
Dim tryme As Double
tryme = DLookup("teMe", "qBuckets", "GroupDate = Week1")
tE0 = tryme
End If
I have a function called "CurveInterpolateRecordset", which is as follows:
Function CurveInterpolateRecordset(rsCurve As Recordset, InterpDate As Date) As Double
Dim I As Long
Dim x1 As Date, x2 As Date, y1 As Double, y2 As Double, x As Date
CurveInterpolateRecordset = Rnd()
If rsCurve.RecordCount <> 0 Then
I = 1
rsCurve.MoveFirst
x1 = CDate(rsCurve.Fields("MaturityDate"))
y1 = CDbl(rsCurve.Fields("ZeroRate"))
If InterpDate = CDate(rsCurve.Fields("MaturityDate")) Then CurveInterpolateRecordset = CDbl(rsCurve.Fields("ZeroRate")): Exit Function
'Do While Not rsCurve.EOF
rsCurve.MoveNext
Do While (CDate(rsCurve.Fields("MaturityDate")) <= InterpDate)
If rsCurve.EOF Then CurveInterpolateRecordset = CDbl(rsCurve.Fields("ZeroRate")): Exit Function
If InterpDate = CDate(rsCurve.Fields("MaturityDate")) Then CurveInterpolateRecordset = CDbl(rsCurve.Fields("ZeroRate")): Exit Function
If InterpDate > CDate(rsCurve.Fields("MaturityDate")) Then
x1 = CDate(rsCurve.Fields("MaturityDate"))
y1 = CDbl(rsCurve.Fields("ZeroRate"))
End If
rsCurve.MoveNext
If rsCurve.EOF Then CurveInterpolateRecordset = y1: Exit Function
Loop
x2 = CDate(rsCurve.Fields("MaturityDate"))
y2 = CDbl(rsCurve.Fields("ZeroRate"))
CurveInterpolateRecordset = y1 + (y2 - y1) * CDate((InterpDate - x1) / (x2 - x1))
End If
Debug.Print I, InterpDate, x1, x2, y1, y2
End Function
This loop will interpolate a missing value for a specific date by interpolating using the values for the nearest dates.
I have a table of dates, some of which need interpolating, so I am using another function to iterate through the recordset and pass the function through each record's corresponding date in order to interpolate the value.
Sub SampleReadCurve()
Dim rs As Recordset
Dim iRow As Long, iField As Long
Dim strSQL As String
Dim CurveID As Long
Dim MarkRunID As Long
Dim ZeroCurveID As String
CurveID = 124
MarkRunID = 10167
ZeroCurveID = "'" & CurveID & "-" & MarkRunID & "'"
'strSQL = "SELECT * FROM dbo_VolatilityInput WHERE ZeroCurveID='124-10167'"
strSQL = "SELECT * FROM dbo_VolatilityInput WHERE ZeroCurveID=" & ZeroCurveID & " ORDER BY MaturityDate"
Set rs = CurrentDb.OpenRecordset(strSQL, Type:=dbOpenDynaset, Options:=dbSeeChanges)
If rs.RecordCount <> 0 Then
Do While Not rs.EOF
rs.MoveFirst
Debug.Print vbCrLf
Debug.Print "First", rs!ZeroCurveID, rs!MaturityDate, rs!ZeroRate, rs!DiscountFactor
rs.MoveLast
Debug.Print "Last", rs!ZeroCurveID, rs!MaturityDate, rs!ZeroRate, rs!DiscountFactor
Debug.Print "There are " & rs.RecordCount & " records and " _
& rs.Fields.Count & " fields."
Dim BucketTermAmt As Long
Dim BucketTermUnit As String
Dim BucketDate As Date
Dim MarkAsOfDate As Date
Dim InterpRate As Double
MarkAsOfDate = rs!MarkAsOfDate
BucketTermAmt = 3
BucketTermUnit = "m"
BucketDate = DateAdd(BucketTermUnit, BucketTermAmt, MarkAsOfDate)
InterpRate = CurveInterpolateRecordset(rs, BucketDate)
Debug.Print BucketDate, InterpRate
rs.MoveNext
Loop
End If
End Sub
For one individual record and date, the first function works fine. However, when I execute the second function, the loop keeps repeating infinitely and the program crashes. I don't understand why this happens because there is clearly an end condition in the second loop. The recordset is only 76 records so not extremely large.
Remove the block that starts with rs.MoveFirst and ends with rs.MoveLast from inside your while loop. They should be inside the if but before the while.
I have a stupid question, I always got the error type mismatch when I created a function which return a array. here are two simple example :
if I don't declare the type when declaration: It will be compiled, but got the error after the function result
Function aa(c As Integer)
Dim arr(10)
Dim i As Integer
Dim k As Double
For i = 0 To 10
k = i ^ 2 / c + 1
arr(i) = CStr(k)
Debug.Print k
Next i
aa = arr
End Function
if i declare the type: it can't be compiled and will get the error directly
Function aa(c As Integer) as string()
Dim arr(10) as string
Dim i As Integer
Dim k As Double
For i = 0 To 10
k = i ^ 2 / c + 1
arr(i) = CStr(k)
Debug.Print k
Next i
aa = arr
End Function
Your second version will work if you call it this way, using the same type:
Sub Testaa()
Dim result() As String
result = aa(4)
End Sub
Your first version will return a Variant - any function (or variable) that isn't given a specific type will default to Variant. So you need to store the return result in a Variant as well:
Sub Testaa()
Dim result As Variant
result = aa(4)
End Sub
It is preferable to use explicit types wherever possible.
Anybody have some VBA code that will store a byte array into a MySQL blob column?
Here is some code. Requires a reference to Microsoft Active Data Objects 2.x Library. It uses the OLE DB provider for MySQL (Might need to install that on the client machine).
Sub StoreBLOB(data() As Byte, key As Double)
'stores the BLOB byte array into the row identified by the key
'requires reference to Microsoft Active Data Objects 2.x Library
On Error GoTo handler:
Dim con As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim conStr As String
Dim strSQL As String
'have it return only the record you want to store your blob
strSQL = strSQL & "SELECT * FROM YOURTABLE WHERE KEY = " & key
'setup connection
conStr = conStr & "Provider=MySQLProv;"
conStr = conStr & "Data Source=mydb;"
conStr = conStr & "User Id=myUsername;"
conStr = conStr & "Password=myPassword;"
con.ConnectionString = conStr
con.Open
rs.Open strSQL, con, adOpenDynamic, adLockOptimistic
If rs.RecordCount > 1 Then
Err.Raise 1001, "StoreBLOB", "Too many records returned from dataset. Check to make sure you have the right key value"
Else
Err.Raise 1002, "StoreBLOB", "No Records found that match the key"
End If
rs.Fields("BLOBFIELDNAME").Value = data
rs.Update 'store the contents to the database
rs.Close
con.Close
Set rs = Nothing
Set con = Nothing
Exit Sub
handler:
Err.Raise 1003, "StoreBLOB", "Unexpected Error in StoreBLOB. Check that server is running"
End Sub
Assuming you are using ADO to access mysql, there's a KB article on the subject.
I have some code, I replicated the mysql_real_escape_string_quote C function in VBA so that one can escape the necessary characters and build your SQL as you would for regular text:
Function mysql_real_escape_string_quote(toStr() As Byte, fromStr() As Byte, length As Long, quote As String) As Long
mysql_real_escape_string_quote = 0
Dim CharMap() As Byte: CharMap = StrConv(String(256, 0), vbFromUnicode)
CharMap(0) = Asc("0"): CharMap(39) = Asc("'"): CharMap(34) = Asc(""""): CharMap(8) = Asc("b"): CharMap(10) = Asc("n"): CharMap(13) = Asc("r"):
CharMap(9) = Asc("t"): CharMap(26) = Asc("z"): CharMap(92) = Asc("\"): CharMap(37) = Asc("%"): CharMap(95) = Asc("_"):
Dim i As Long: Dim n As Long: n = 0
If length > UBound(fromStr) + 1 Then Exit Function
For i = 0 To length - 1 '---count escapable chars before redim---
n = n + 1
If CharMap(fromStr(i)) <> 0 Then n = n + 1
Next i
ReDim toStr(n - 1) As Byte
n = 0
For i = 0 To length - 1 '---test chars---
If CharMap(fromStr(i)) = 0 Then
toStr(n) = fromStr(i)
Else '---escape char---
toStr(n) = Asc(quote): n = n + 1
toStr(n) = CharMap(fromStr(i))
End If
n = n + 1
Next i
mysql_real_escape_string_quote = n
End Function
Function mysql_real_escape_string(InputString As String) As String
mysql_real_escape_string = ""
Dim toStr() As Byte: Dim fromStr() As Byte
fromStr = StrToChar(InputString)
If mysql_real_escape_string_quote(toStr, fromStr, UBound(fromStr) + 1, "\") = 0 Then Exit Function
mysql_real_escape_string = StrConv(toStr(), vbUnicode)
End Function
Function StrToChar(str As String) As Byte()
Dim ans() As Byte
ans = StrConv(str, vbFromUnicode)
ReDim Preserve ans(Len(str)) As Byte
ans(Len(str)) = 0
StrToChar = ans
End Function
Sub testit()
Dim toStr() As Byte: Dim fromStr() As Byte
fromStr = StrToChar("hello world's")
MsgBox (mysql_real_escape_string_quote(toStr, fromStr, UBound(fromStr) + 1, "\"))
MsgBox (mysql_real_escape_string("hello world's"))
For i = 0 To UBound(toStr)
Debug.Print i & " " & toStr(i)
Next i
End Sub
It's been optimized for large amounts of data without a ridiculous amount of conditionals (ifs).