'Неопределенная функция' при использовании DAO QueryDef в VBA

Я назначаю запрос Access 2007 объекту QueryDef в Excel VBA. Мой запрос вызывает пользовательскую функцию, потому что он выполняет вычисление результатов оценки поля с помощью регулярного выражения. Я использую QueryDef, потому что я собираю значения в UserForm и хочу передать их запросу в качестве параметров.

Когда я запускаю свой код VBA, я получаю сообщение об ошибке: «Ошибка выполнения '3085': неопределенная функция 'regexFunc' в выражении».

Этот вопрос предполагает, что проблема в том, что DAO не может вызывать UDF Access из Excel, поэтому я скопировал свой UDF в модуль Excel VBA, но все равно получаю сообщение об ошибке.

Запрос доступа:

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

Вот код Excel VBA:

'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

person sigil    schedule 17.08.2011    source источник
comment
Вы не можете этого сделать: ваша функция находится в Excel, но ваш SQL выполняется для вашей базы данных Access через DAO, которая ничего не знает о VBA / вашей функции. Это в значительной степени то, что сказал Дик К. в вопросе, на который вы ссылаетесь.   -  person Tim Williams    schedule 18.08.2011
comment
Эта ссылка базы знаний описывает вашу проблему: support.microsoft.com/kb/180810   -  person Tim Williams    schedule 18.08.2011
comment
@TimWilliams, хорошо. Если я использую запрос без UDF, я думаю, я могу добавить столбец к его набору записей (который я бы получил от qdf.openrecordset) и заполнить этот столбец значениями, рассчитанными с использованием версии UDF для Excel VBA. Это лучший способ или есть более простой способ получить то, что мне нужно? РЕДАКТИРОВАТЬ: спасибо за ссылку.   -  person sigil    schedule 18.08.2011
comment
похоже, вы используете UDF для фильтрации возвращаемых записей, поэтому, если бы вы использовали его после запроса, вам нужно было бы сначала вернуть все записи или придумать простой фильтр первого прохода SQL, чтобы хотя бы ограничить результаты запроса. Насколько важно это сделать, будет зависеть от того, сколько записей в вашей таблице: если управляемое число, то да, вы могли бы просто выполнить пост-запрос регулярного выражения.   -  person Tim Williams    schedule 18.08.2011
comment
Можете ли вы запросить без RegEx?   -  person David-W-Fenton    schedule 20.08.2011
comment
@ David-W-Fenton, да, я, вероятно, мог бы построить его как набор строковых операций, используя left, mid и т.д., но действительно ли здесь проблема с регулярным выражением? я мог бы поместить туда любой логический UDF, и DAO показал бы то же поведение.   -  person sigil    schedule 22.08.2011
comment
Но Left (), Mid () и все эти функции обработки строк доступны извне Access, тогда как UDF - нет. Так что это кажется мне самым надежным решением.   -  person David-W-Fenton    schedule 25.08.2011


Ответы (2)


Вот как я бы это сделал ... просто протестировал, и он отлично работает с моим UDF:

Одно дело - требуется ли вам не использовать 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

Обратите внимание, что я сделал свой базовый запрос запросом SELECT ... INTO, который создает таблицу с именем dataTableResults.

Это мой запрос (QueryDef) в Access:

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

В моей базе данных MS-Access есть функция под названием «mysqr», которая используется в приведенном выше SQL.

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

Таблица dataTable, к которой я запрашиваю, представляет собой просто список чисел, поэтому, если мой параметр «avalue» равен «16», я верну строку «4». Если я введу «4» (как в моем коде), я верну «2».

person transistor1    schedule 17.08.2011
comment
Кроме того, ваш пример кода мне не на 100% понятен. Вы пытаетесь открыть базу данных Access и создать в ней QueryDef или пытаетесь подключиться к базе данных и запросить данные? Если вам нужно открыть базу данных, вы хотите сослаться на MS-Access и сделать: Dim oApp as Access.Application : Set oApp = New Access.Application : oApp.OpenCurrentDatabase(FilePath) : Set qd = oApp.CurrentDb.CreateQueryDef( ... ) : ... etc ... Это не в моей голове, поэтому дайте мне знать, если вам нужны дополнительные подробности. - person transistor1; 18.08.2011
comment
Я включил ссылку на VBS regex 5.5. Я скопировал код UDF регулярного выражения в тот же модуль Excel VBA, что и код QueryDef. и нет, мне не нужно вызывать Access с access.application, я могу сделать это с dim db as database set db=opendatabase("mypath"), я это тестировал. Проблема в том, что Excel вызывает UDF. - person sigil; 18.08.2011
comment
Чтобы прояснить код: я пытаюсь получить результаты сохраненного запроса Access и вывести их в электронную таблицу Excel. - person sigil; 18.08.2011
comment
Вы можете использовать DAO из Excel, и некоторые люди рекомендуют это, если вы запрашиваете Access. - person Tim Williams; 18.08.2011
comment
Извините - теперь у меня есть. У вас есть запрос, содержащий пользовательскую функцию в Access, и вам нужны результаты в Excel. - person transistor1; 18.08.2011
comment
Как насчет того, чтобы преобразовать ваш запрос в запрос MakeTable (или обернуть его в оператор SELECT ... INTO), затем вызвать db.CurrentDb.Execute myQuery и перенести полученную таблицу в Excel? - person transistor1; 18.08.2011
comment
@ transistor1, использование Select..Into и db.execute дает ту же ошибку. - person sigil; 18.08.2011
comment
@sigil - только что отредактировал мой ответ выше - посмотрите, сработает ли это для вас? - person transistor1; 18.08.2011
comment
@ transistor1 при попытке создать экземпляр Access.Application выдает ошибку компиляции: пользовательский тип не определен. Помните, я звоню из Excel. Есть ли ссылка, которую я должен включить? Кроме того, у моих пользователей не будет установлен Access на их машинах, только Excel. - person sigil; 18.08.2011
comment
@sigil - тоже звоню из Excel. У вас должна быть ссылка на библиотеку объектов Microsoft Access #. ## (в моем случае это 14.0, так как у меня есть доступ к 2010). Если у ваших пользователей не будет доступа, тогда Тим прав - вы не сможете этого сделать. Двигатель Jet не понимает синтаксис VBA. Можно ли установить бесплатную среду выполнения Microsoft Access - ссылка - для ваших пользователей? - person transistor1; 18.08.2011
comment
@sigil - ваша собственная идея добавления пустого столбца в DAO.Recordset была бы наименьшей проблемой. Лично я не вижу в этом ничего плохого. - person transistor1; 18.08.2011
comment
@ transistor1 - что касается вашего ответа, что происходит, когда несколько пользователей запускают эту подпрограмму в своих собственных копиях файла Excel? Мне не кажется, что сочетание Excel и Access гарантирует последовательное выполнение и получение результатов. - person Yawar; 10.02.2013
comment
Привет, @Yawar - это правда, но этот ответ был разработан только для демонстрации способности вызывать запросы с пользовательскими функциями, а не для гарантии того, что многопользовательский сценарий будет работать. ИМХО, это выходит за рамки этого вопроса - вы можете подумать о том, чтобы опубликовать новый для этого. Тем не менее, один из способов сделать это - использовать уникальный идентификатор для имени таблицы, а не использовать dataTableResults (обратите внимание, что в вопросе OP он даже не создает таблицу). Надеюсь, это будет полезно. - person transistor1; 11.02.2013
comment
@ transistor1 - спасибо. Я закончил тем, что не использовал Excel в качестве интерфейса к базе данных Access, вместо этого просто использовал другую базу данных Access в качестве интерфейса. - person Yawar; 12.02.2013

Я решил это. Вот как я это сделал.

Сначала я преобразовываю запрос в набор записей и передаю его своей функции фильтрации:

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

А вот функция фильтрации, которая создает новый массив, заполняет его строками, которые проходят логическую проверку UDF, и возвращает его:

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
person sigil    schedule 18.08.2011