1

I'm assigning an Access 2007 query to a QueryDef in Excel VBA. My query calls a user-defined function, because it performs a calculation on the results of evaluating a field with a regular expression. I'm using a QueryDef because I'm collecting values in a UserForm and want to pass them to the query as parameters.

When I run my VBA code, I get an error: "Run-time error '3085': Undefined function 'regexFunc' in expression."

This question suggests that the problem is that DAO is unable to call Access UDFs from Excel, so I copied my UDF into the Excel VBA module, but I still get the error.

Access query:

select field1 from dataTable where regexFunc(field1)=[regexVal]

Here's the Excel VBA code:

'QueryDef function
Sub makeQueryDef (str As String)

Dim qdf As QueryDef
Dim db As Database

Set db = OpenDatabase(DBpath)
Set qdf = db.QueryDefs("paramQuery")
qdf.Parameters("regexVal") = (str="test")
doSomething qdf

End Sub

'Regex function copied from Access VBA module to Excel VBA module
Function regexFunc(str As String) As Boolean

Dim re As RegExp
Dim matches As MatchCollection

regexFunc = False
Set re = New RegExp
re.Pattern = "\reg[ex](pattern)?"
Set matches = re.Execute(str)
If matches.Count <> 0 Then
    regexFunc = True
End If

End Function
Community
  • 1
  • 1
sigil
  • 9,370
  • 40
  • 119
  • 199
  • You can't do that: your function is in Excel, but your SQL is being executed against your Access db via DAO, which knows nothing of VBA/your function. That's pretty much what Dick K. said in the question you reference. – Tim Williams Aug 17 '11 at 23:09
  • This KB link describes your issue: http://support.microsoft.com/kb/180810 – Tim Williams Aug 17 '11 at 23:19
  • @TimWilliams, Ok. If I use a query without the UDF, i guess I can append a column to its recordset (which I'd get from `qdf.openrecordset`) and populate that column with values calculated using the Excel VBA version of the UDF. Is this the best way, or is there a simpler way to get what I'm after? EDIT: thanks for the link. – sigil Aug 17 '11 at 23:22
  • 2
    it looks like you're using the UDF to filter the returned records, so if you were to use it after the query you'd need to first return all the records, or come up with a plain SQL "first pass" filter to at least limit the results of the query. How important it is to do that would depend on how many records there are in your table: if a manageable number then yes you could just do the regex post-query. – Tim Williams Aug 17 '11 at 23:56
  • Can you query without a RegEx? – David-W-Fenton Aug 19 '11 at 23:30
  • @David-W-Fenton, yes, i could probably construct it as a bunch of string operations using left, mid, etc., but is the regex really the issue here? i could put any boolean UDF in there and DAO would exhibit the same behavior. – sigil Aug 22 '11 at 16:19
  • 1
    But Left(), Mid() and all those string handling functions are available from outside Access, whereas UDFs are not. So, that seems like the most robust solution to me. – David-W-Fenton Aug 24 '11 at 21:54

2 Answers2

1

This is how I would do it... just tested it and it works fine with my UDF:

One thing - are you required to not use New Access.Application?

Sub GetMyDataWithUDF()
    Dim oApp As Access.Application
    Dim qd As QueryDef

    sFileName = "C:\Users\AUser\Desktop\adatabase.mdb"
    Set oApp = New Access.Application
    oApp.OpenCurrentDatabase (sFileName)

    Set qd = oApp.CurrentDb.QueryDefs("Query1")

    If oApp.DCount("*", "MSysObjects", "Name='dataTableResults'") > 0 Then _
        oApp.CurrentDb.TableDefs.Delete "dataTableResults"

    qd.Parameters("avalue") = "4"
    qd.Execute

    oApp.Quit
    Set oApp = Nothing

    Dim oRS As ADODB.Recordset
    sConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & sFileName & ";User Id=admin;Password=;"
    Set oRS = New ADODB.Recordset
    oRS.Open "SELECT * FROM dataTableResults", sConn
    Sheet1.Cells.Clear
    Sheet1.Range("A1").CopyFromRecordset oRS
    oRS.Close
    Set oRS = Nothing
End Sub

Note that I made my underlying query a SELECT ... INTO query that creates a table called 'dataTableResults'

This is my query (QueryDef) in Access:

SELECT dataTable.Field1, dataTable.Field2 INTO dataTableResults
FROM dataTable
WHERE mysqr(dataTable.Field1)=[avalue];

My MS-Access DB has a function called "mysqr", which gets used in the SQL above.

Function mysqr(Num)
        mysqr = Num * Num
    End Function

The table "dataTable" I'm querying against is just a list of numbers, so if my parameter "avalue" is "16", then I get the row "4" back. If I enter "4" (as in my code), I get "2" back.

transistor1
  • 2,915
  • 26
  • 42
  • Also -- your sample code isn't 100% clear to me. Are you trying to open an Access database and create a QueryDef in the database, or trying to connect to a database and query data? If you need to open a database, you want to reference MS-Access and do: `Dim oApp as Access.Application : Set oApp = New Access.Application : oApp.OpenCurrentDatabase(FilePath) : Set qd = oApp.CurrentDb.CreateQueryDef( ... ) : ... etc ...` This is off the top of my head so let me know if you need more specifics. – transistor1 Aug 17 '11 at 23:03
  • I included the VBS regex 5.5 reference. I copied the regex UDF code into the same Excel VBA module as the QueryDef code. and no I don't need to call Access with `access.application`, i can do that with `dim db as database set db=opendatabase("mypath")`, I've tested it. The problem is with Excel calling the UDF. – sigil Aug 17 '11 at 23:05
  • To clarify the code: I'm trying to get the results of a stored Access query and output them to an Excel spreadsheet. – sigil Aug 17 '11 at 23:11
  • You can use DAO from Excel, and some people recommend this if you're querying Access. – Tim Williams Aug 17 '11 at 23:11
  • Sorry - I've got it now. You have a query containing a user-defined function in Access, and you need the results in Excel. – transistor1 Aug 17 '11 at 23:15
  • What about changing your query to a MakeTable query (or wrapping it into an SELECT ... INTO statement), then call db.CurrentDb.Execute "myQuery", and pull the resultant table into Excel? – transistor1 Aug 17 '11 at 23:17
  • @transistor1, using `Select..Into` and `db.execute` gives the same error. – sigil Aug 17 '11 at 23:26
  • @sigil - just edited my answer above -- see if this works for you? – transistor1 Aug 17 '11 at 23:37
  • @transistor1, trying to create an instance of Access.Application gave me "Compile error: User-defined type not defined". Remember I'm calling this from Excel. Is there a reference I have to include? Also, my users won't have Access installed on their machines, just Excel. – sigil Aug 17 '11 at 23:46
  • @sigil - I'm calling it from Excel, too. You would have to have a reference to Microsoft Access #.## Object Library (in my case it's 14.0 as I have access 2010). If your users won't have Access, then Tim is correct - you can't do it. The Jet engine doesn't understand the VBA syntax. Is it possible for you to install the free Microsoft Access Runtime engine - [link](http://www.microsoft.com/download/en/details.aspx?id=4438) - for your users? – transistor1 Aug 17 '11 at 23:50
  • @sigil - your own idea of adding a blank column to the DAO.Recordset would be the least trouble. Personally I don't see anything wrong with that. – transistor1 Aug 17 '11 at 23:58
  • @transistor1 - regarding your answer, what happens when multiple users run this sub in their own copies of the Excel file? It doesn't seem to me that the combination of Excel and Access would guarantee sequential execution and results retrieval. – Yawar Feb 10 '13 at 17:44
  • Hi @Yawar - that's true, but this answer was only designed to demonstrate the _ability_ to call queries with user defined functions, not to guarantee that a multi-user scenario would work. IMHO, that's beyond the scope of this question-- you might consider posting a new one for that. That said, one way of doing it would be to use a unique identifier for the table name, rather than using `dataTableResults` (note also that in the OP's question he's not even creating a table). Hope this is helpful. – transistor1 Feb 11 '13 at 01:28
  • @transistor1 - thanks. I ended up not using Excel as a frontend to the Access DB, instead just used another Access DB as a frontend. – Yawar Feb 12 '13 at 01:22
1

I've solved this. Here's how I did it.

First I change the query into a recordset and pass it to my filtering function:

function filteredQDF(qdf As QueryDef, boolVal As Boolean) As Variant

Dim rs As Recordset
Dim rows_rs As Variant
Dim rs_new As Recordset
Dim filtered As Variant


Set rs = qdf.OpenRecordset

rs.MoveLast
rs.MoveFirst

rows_rs = rs.GetRows(rs.RecordCount)
rows_rs = Application.WorksheetFunction.Transpose(rows_rs)
filtered = filterFunction(rows_rs, boolVal)

filteredQDF = filtered

End Function

And here's the filtering function, which creates a new array, populates it with rows that pass the UDF's boolean check, and returns it:

Function filterFunction(sourceArray As Variant, checkValue As Boolean) As Variant


Dim targetArray As Variant
Dim cols As Long
Dim targetRows As Long
Dim targetCursor As Long


'get # of columns from source array
cols = UBound(sourceArray, 2)

'count total number of target rows because 2D arrays cannot Redim Preserve
'checking sourceArray(r,2) because that's the criterion column
targetRows = 0
For r = 1 To UBound(sourceArray, 1)
    If myUDF(CStr(sourceArray(r, 2))) = checkValue Then
        targetRows = targetRows + 1
    End If
Next

'set minimum target rows to 1 so that function will always return an array
If targetRows = 0 Then
    targetRows = 1
End If

'redim target array with target row count
ReDim targetArray(targetRows, cols)

'set cursor for assigning values to target array
targetCursor = 0


'iterate through sourceArray, collecting UDF-verified rows and updating target cursor to populate target array
For r = 1 To UBound(sourceArray, 1)
    If myUDF(CStr(sourceArray(r, 2))) = checkValue Then
        For c = 1 To cols
            targetArray(targetCursor, c - 1) = sourceArray(r, c)
        Next
        targetCursor = targetCursor + 1
    End If
Next


'assign return value
filterFunction = targetArray

End Function
sigil
  • 9,370
  • 40
  • 119
  • 199