#ms-access #vba
#ms-access #vba
Вопрос:
У меня есть (плохо спроектированная) БД, которая содержит таблицу добровольцев. В этой таблице более 70 полей «Да» / «Нет». Каждое поле представляет округ, который каждый доброволец может выбрать для обслуживания. Каждый доброволец может выбрать несколько округов.
У меня есть отчет, который должен разрешать выбор нескольких округов. Затем этот список необходимо сравнить с выбором логических полей по выбору, сохраняя записи только с истинными значениями.
Если вы проверите некоторые из моих недавних вопросов, у меня были похожие проблемы, но разница заключалась в отображении логических выборок в виде текста в отчете. Только сейчас обнаруженный код критериев выбора не выполняет то, что я думал… Вот что было сделано до сих пор:
Dim s As Variant
Dim ctl As Control
Dim t As TCondition 'this is to compile multiple variables into one large "where" clause to run the report. 3 such variables are coded in this function.
Set ctl = Me.Counties
If ctl.ItemsSelected.Count <> 0 Then
If t.WhereCondition = "" Then
For Each s In ctl.ItemsSelected
t.WhereCondition = (ctl.ItemData(s) amp; " = -1")
Next s
Else
For Each s In ctl.ItemsSelected
t.WhereCondition = t.WhereCondition amp; " AND (ctl.ItemData(s) = -1)"
Next s
End If
End If
Где это сломалось, так это если кто-то выбрал более одного округа. Я понял, что они выбрали только последний округ.
Например — Графства: красный, синий, серебристый и зеленый вернут только зеленый.
Аналогичная проблема возникла с позициями добровольцев. Мне нужно было учитывать выбор нескольких
Итак, я нашел следующее:
Public Function listBoxToString(ByRef theListBox As ListBox, Optional ByVal theDelimiter As String = vbNullString)
Dim varTemp As Variant
listBoxToString = vbNullString
For Each varTemp In theListBox.ItemsSelected
If listBoxToString <> vbNullString Then listBoxToString = listBoxToString amp; ","
listBoxToString = listBoxToString amp; theDelimiter amp; theListBox.ItemData(varTemp) amp; theDelimiter
Next varTemp
End Function
Это принимает все выбранные элементы списка и создает их список, разделенный запятыми. Это СРАБОТАЛО для полей позиций (их несколько), потому что эти значения полей являются ТЕКСТОВЫМИ.
Я попытался применить это к округам следующим образом:
Dim s As String
Dim ctl As Control
Dim t As TCondition
Set ctl = Me.Counties
If ctl.ItemsSelected.Count <> 0 Then
s = listBoxToString(Me.Counties, Chr(34))
If t.WhereCondition = "" Then
t.WhereCondition = (ctl.ItemData(s) amp; " = -1")
Else
t.WhereCondition = t.WhereCondition amp; " AND (ctl.ItemData(s) = -1)"
End If
End If
Это НЕ работает для округов, потому что ListBox возвращает ТЕКСТ, который затем необходимо сравнить со значениями в округе, которые являются ЛОГИЧЕСКИМИ. Итак, логически, я получаю ошибку несоответствия типа данных.
У меня есть отдельный фрагмент кода, используемый для отображения списка округов в отчете. Он извлекает записи во время выполнения, принимает идентификатор записи, обнуляет индексы логических полей и преобразует их в имя полей:
Public Function MakeListCounties(intID As Integer) As String
Dim rs As DAO.Recordset
Dim strList As String
Dim x As Integer
Set rs = CurrentDb.OpenRecordset("SELECT * FROM Volunteer WHERE ID=" amp; intID)
For x = 44 To 105
If rs(x).Type = dbBoolean Then
If rs(x) = True Then strList = strList amp; rs(x).Name amp; ", "
End If
Next
If strList <> "" Then MakeListCounties = Left(strList, (Len(strList) - 2))
rs.Close
Set rs = Nothing
End Function
Есть ли способ работать с этим, чтобы получить то, что я хочу? Это может быть излишним, и, если это так, приносим извинения и полностью игнорируем эту часть.
Напомним, что мне нужно выполнить итерацию по списку значений, сравнить их со значениями более 70 логических полей, сохранив только записи, где Boolean = True для имен полей, соответствующих значениям в списке
Комментарии:
1. Обычно мне не нравятся динамические параметры в запросе, но это может понадобиться. Думаю, я бы вызвал функцию из запроса. Функция возвращает True или False. Критерии для построенного поля будут
=True
следующими.2. Я очень новичок в ООП. Я знаю, как вызвать функцию при разработке запросов, но создание функции, которая это делает, ставит меня в тупик.
Ответ №1:
Функция сборки в общем модуле, которая возвращает значение True или False. Вызов из запроса с выражением типа: MC: MatchCounties([ID])
с критериями фильтрации =True
.
Предполагая, что в выпадающем списке перечислены названия округов, точно соответствующие именам полей да / нет, рассмотрим:
Function MatchCounties(intID As Integer) As Boolean
Dim rs As DAO.Recordset
Dim varItem As Variant
With Forms!formname.Counties
If .ItemsSelected.Count <> 0 Then
Set rs = CurrentDb.OpenRecordset("SELECT * FROM Volunteer WHERE ID=" amp; intID)
For Each varItem In .ItemsSelected
If rs(.ItemData(varItem)) Then 'use the listbox value as field name and test for True
MatchCounties = True
Exit For 'only need to match 1 yes/no county field to retrieve record
End If
Next
rs.Close
Set rs = Nothing
End If
End With
End Function
Если в списке не выбрано ни одного элемента, никакие записи не вернутся, потому что функция вернет False . Если вы хотите разрешить отсутствие выбора и по-прежнему возвращать записи, то, вероятно, хотите, чтобы функция возвращала True if .Элементы выбраны.Count = 0. Настройте код с помощью Else.
Комментарии:
1. Прочитайте это сейчас. Прошу прощения за задержку, я занимаюсь этим только днем.
2. Каким бы блестящим это ни было, оно непригодно для использования. Причина в том, что в VBA компилируется условие WHERE, затем в VBA вызывается запрос. Так что это никогда не оценивается во время выполнения.
3. Почему вы говорите, что это не оценивается во время выполнения? Я проверил это. Создал отчет с помощью функции вызова запроса. Применены дополнительные критерии фильтрации с помощью метода OpenReport. Работает идеально. Вы сказали, что хотите отчет. Вы тестировали? Как вы «вызываете» запрос?
4. Да, я проверил это. Я поместил функцию в общий модуль. Я вызвал это в запросе. Я установил точку останова в функции. Это должно было сработать, когда запрос вызвал его. Запрос никогда не вызывал функцию.
5. Ну, не могу этого объяснить. Это определенно работает для меня.
Ответ №2:
Измените первый фрагмент кода на следующий. Причина, по которой вы всегда получали последнее графство, заключается в том, что ваш цикл перезаписывал предыдущие значения предложения WHERE.
Dim s As Variant
Dim ctl As Control
Dim t As TCondition
Set ctl = Me.Counties
If ctl.ItemsSelected.Count <> 0 Then
For Each s In ctl.ItemsSelected
t.WhereCondition = t.whereCondition amp; ctl.ItemData(s) amp; " = -1 OR"
Next s
' trim trailing " OR"
t.WhereCondition = Left(t.WhereCondition, Len(t.WhereCondition)-3)
End If
Комментарии:
1. В конкатенации нет оператора AND или OR. Это должно завершиться неудачей. Использование оператора AND не будет работать, потому что сомневаюсь, что какая-либо запись может соответствовать условию. И до 70 операторов OR могут работать, но будут довольно запутанными в представлении дизайна запроса, особенно если у других полей есть критерии.
2. @June7 Спасибо, что поймали мою ошибку конкатенации
3. Хорошо, но я не совсем понимаю объект TCondition. Никогда не видел этого. Предположим, у OP есть какой-то пользовательский код класса?
4. Да, я предполагаю, что у OP есть пользовательский класс
5. Кроме того, вероятно, следует обрезать завершающий ИЛИ вне цикла.