MS-ACCESS VBA Multiple Search Criteria - sql

In my GUI, I have several ways to filter a database. Due to my lack of knowledge, my VBA programming has exploded with nested IF statements. I am getting better at using ACCESS now, and would like to find a more succinct way to perform multiple filters. My form is continuous.
Is there a simple way to do the following task (I made a toy model example):
I have a combo box SITE where I can filter by work sites A, B, C. After filtering by SITE, I have three check boxes where the user can then filter by item number 1-10, 11-20, 21-30, depending on what the user selects.
Is there a way to append multiple filters (or filter filtered data)? For example, filter by SITE A, then filter A by item number 1-10?
Currently, for EACH check box, I then have an IF statement for each site. Which I then use Form.Filter = . . . And . . . and Form.FilterOn = True.
Can I utilize SQL on the property sheet to filter as opposed to using the VBA?

What I do for these types of filters is to construct a SQL statement whenever one of the filter controls is changed. All of them reference the same subroutine to save on code duplication.
What you do with this SQL statement depends on what you're trying to do. Access is pretty versatile with it; use it as a RecordSource, straight execute it, and use the results for something else, even just printing it to a label.
To try to modularize the process, here's an example of how I do it:
Dim str As String
str = "SELECT * FROM " & Me.cListBoxRowSource
Me.Field1.SetFocus
If Me.Field1.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField1 LIKE '*" & Me.Field1.Text & "*'"
End If
Me.Field2.SetFocus
If Me.Field2.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField2 LIKE '*" & Me.Field2.Text & "*'"
End If
Me.Field3.SetFocus
If Me.Field3.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField3 LIKE '*" & Me.Field3.Text & "*'"
End If
Me.cListBox.RowSource = str
Variables edited to protect the guilty.
My AppendNextFilter method just checks to see if WHERE exists in the SQL statement already. If it does, append AND. Otherwise, append WHERE.

Making quite a few assumptions (since you left out a lot of info in your question), you can do something like this:
Dim sSql as String
sSql = "Select * from MyTable"
Set W = Me.cboSite.Value
sSql = sSql & " WHERE MySite = " & W & ""
Set X = Me.Chk1
Set Y = Me.Chk2
Set Z = Me.Chk3
If X = True Then
sSql = sSql & " And MyItem between 1 and 10"
If Y = True Then
sSql = sSql & " And MyItem between 11 and 20"
If Z = True Then
sSql = sSql & " And MyItem between 21 and 30"
End If
DoCmd.ExecuteSQL sSql
Again, this is entirely "air code", unchecked and probably needing some edits as I haven't touched Access in some time and my VBA is likely rusty. But it should put you on the right track.

The way i use combobox filtering in access is first I design a Query that contains all the data to be filtered. The Query must contain fields to be used for filtering. QueryAllData => "SELECT Table.Site, Table.ItemNumber, FROM Table;" Then make a copy of the query and Name it QueryFilteredData and Design the report to display the data using QueryFilteredData.
Then create a form with a Site ComboBox, ItemNumber Combo Box, and Sub Report Object and Assign SourceObject the Report Name. Use Value List as the combo box Row Source type and type in the values for Row Source to get it working. To get the report to update I always unassign the SubReport.SourceOject update the QueryFilteredData and then Reassign the SubReport.SourceObject
Combobox_Site_AfterUpdate()
Combobox_ItemNumber_AfterUpdate
End Sub
Combobox_ItemNumber_AfterUpdate()
Select Case Combobox_ItemNumber.value
Case Is = "1-10"
Store_Filters 1,10
Case Is = "11-20"
Store_Filters 11,20
Case Is = "21-30"
Store_Filters 21,30
Case Else
Store_Filters 1,10
End Sub
Private Sub Store_Filters(Lowest as integer, Highest as integer)
Dim SRpt_Recset As Object
Dim Temp_Query As Variant
Dim Temp_SourceObject as Variant
Temp_SourceObject = SubReport.SourceObject
SubReport.SourceObject =""
Set SRpt_Recset = CurrentDb.QueryDefs("QueryFilteredData")
Filter_Combo_Box1 = " ((QueryAllData.[Sites])= " & Chr(39) & Combo_Box1 & Chr(39) & ") "
Filter_Combo_Box2 = (Filter_Combo_Box1 AND (QueryAllData.ItemNumber <= Highest)) OR (Filter_Combo_Box1 AND (QueryAllData.ItemNumber >= Lowest));"
Temp_Query = " SELECT " & Query_Name & ".* " & _
"FROM " & Query_Name & " " & _
"WHERE (" & Filter_Combo_Box2 & ") ORDER BY [Field_Name_For_Sorting];"
SRpt_Recset.SQL = Temp_Query
'Debug.print Temp_Query
SubReport.SourceObject = Temp_SourceObject
End Sub
After the Combo Boxes Work if the Data is going to Change like Site and Item Number then you might want to change the Row Source of the combo boxes to Use a Query that uses Select Distinct Site From QueryAllData. I don't know if Filter_Combo_Box2 step so it may need some correction. Hope this helps.

Related

Access: Saving then Loading (and selecting) SelectedItems from ListBoxes

I have a form with multiple listboxes with the MultiSelect property enabled (and a single option group). For row source, each listbox reads two columns (sysID, sysName) from a table (Systems) filtered by a third column (sysType) which corresponds to the type of systems managed in that listbox (e.g. row source is SELECT Systems.sysID, Systems.sysName FROM Systems WHERE Systems.sysType=3 ORDER BY Systems.sysName; for one of them).
I have a save button that executes the following to store a CfgID to the CfgSys table with each sysID (Systems.sysID = CfgSys.sysID) for later recall. It works like this (varSys is an array of Ints):
Save_Config:
i = 0
For Each ctl In frm.Controls
If ctl.ControlType = acListBox Then
For Each varItm In ctl.ItemsSelected
varSys(i) = ctl.ItemData(varItm)
i = i + 1
Next varItm
ElseIf ctl.ControlType = acOptionGroup Then
varSys(i) = ctl.Value
i = i + 1
End If
Next ctl
For i = LBound(varSys) To UBound(varSys)
If (Not IsNull(varSys(i))) And (varSys(i) <> 0) Then
strSQLIns = "INSERT INTO CfgSys (CfgID, SysID) VALUES (" & varCfgID & "," & varSys(i) & ");"
DoCmd.RunSQL (strSQLIns)
End If
Next
Some preamble and wrap-up omitted there for brevity. That part works great, for CfgID 1 I have rows in CfgSys corresponding to each of the 20 or so entries spread among the various listboxes.
I have a load button I would like to read those rows and select the various entries previously stored for each listbox. That part is driving me nuts. So far I have:
Load_Config:
strSQL = "SELECT CfgSys.CfgID, CfgSys.SysID, Systems.sysType, SysTypes.sysTypeName FROM " & _
"(SysTypes RIGHT JOIN Systems ON SysTypes.[SysType] = Systems.[SysType]) " & _
"RIGHT JOIN CfgSys ON Systems.[sysID] = CfgSys.[SysID] WHERE CfgSys.[CfgID] =" & varCfgID & ";"
Set rs = db.OpenRecordset(strSQL)
With rs
If Not .BOF And Not .EOF Then
.MoveLast
.MoveFirst
While (Not .EOF)
If rs!sysTypeName = "Electrical" Then
strCtl = "optElectrical"
frm.Controls(strCtl).Value = rs!sysID
Debug.Print strCtl & ": " & rs!sysID
Else
strCtl = "lst" & rs!sysTypeName
frm.Controls(strCtl).Selected(rs!sysID) = True
Debug.Print strCtl & ": " & rs!sysID
End If
.MoveNext
Wend
End If
End With
Response = MsgBox("Configuration loaded.", vbOKOnly Or vbInformation, "Load Successful")
But I can't figure out how to translate the SysID's/.itemData values from the Save part of the form into indexed line/row #'s for the .Selected collection of the listboxes, so right now it just selects one item in the longest of the listboxes, purely because it has a bunch of rows. Google hasn't helped me nor has Microsoft's reference on ListBox.ItemsSelected. The Debug.Print statement in there successfully prints the name of each listbox control and the sysID corresponding to the row I want to set, but I've been stuck at this last bit for 2 days - anyone able to lend some insight? Is this even possible?
Per June7 above needed to iterate through .ItemData and match that way. Can't seem to mark their comment as the answer, but with their guidance ended up with this:
Else
strCtl = "lst" & rs!sysTypeName
For i = 0 To frm.Controls(strCtl).ListCount - 1
If CStr(rs!sysID) = frm.Controls(strCtl).ItemData(i) Then
frm.Controls(strCtl).Selected(i) = True
End If
Next i
Since ItemData returns strings the CStr was needed to get it to match up.

String builder SELECT query when a variable has an apostrophe

A colleague of mine has created a program that reads a text file and assigns various values from it to variables that are used in SQL statements.
One of these variables, gsAccounts is a string variable.
Using a string builder, a SELECT statement is being built up with sql.append. At the end of the string, there is the following line:
sql.Append(" WHERE L.Account_Code IN(" & gsAccounts & ")"
The problem that I'm having is that sometimes, not always, gsAccounts (a list of account codes) may contain an account code with an apostrophe, so the query becomes
"WHERE L.Account_Code IN('test'123')"
when the account code is test'123
I have tried using double quotes to get around it in a "WHERE L.Account_Code IN("""" & gsAccounts & """")" way (using 4 and 6 " next to each other, but neither worked)
How can I get around this? The account_Code is the Primary Key in the table, so I can't just remove it as there are years worth of transactions and data connected to it.
I posted the following example here 10 years ago, almost to the day. (Oops! thought it was Jun 5 but it was Jan 5. 10.5 years then.)
Dim connection As New SqlConnection("connection string here")
Dim command As New SqlCommand
Dim query As New StringBuilder("SELECT * FROM MyTable")
Select Case Me.ListBox1.SelectedItems.Count
Case 1
'Only one item is selected so we only need one parameter.
query.Append(" WHERE MyColumn = #MyColumn")
command.Parameters.AddWithValue("#MyColumn", Me.ListBox1.SelectedItem)
Case Is > 1
'Multiple items are selected so include a parameter for each.
query.Append(" WHERE MyColumn IN (")
Dim paramName As String
For index As Integer = 0 To Me.ListBox1.SelectedItems.Count - 1 Step 1
'Name all parameters for the column with a numeric suffix.
paramName = "#MyColumn" & index
'Add a comma before all but the first value.
If index > 0 Then
query.Append(", ")
End If
'Append the placeholder to the SQL and add the parameter to the command
query.Append(paramName)
command.Parameters.AddWithValue(paramName, Me.ListBox1.SelectedItems(index))
Next index
query.Append(")")
End Select
command.CommandText = query.ToString()
command.Connection = connection
Single quotes can be "escaped" by making them double single quotes. E.g. ' becomes ''.
However this approach is generally not recommended due to the high risk of SQL injection - a very dangerous and prevalent issues. See: https://www.owasp.org/index.php/SQL_Injection
To avoid this most libraries will include some type of escaping mechanism including the use of things like prepared statements in the Java world. In the .net world this may be of use: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.prepare(v=vs.110).aspx
If you only have one with a field, this is the easiest solution
Private Function gsAccountsConvert(ByVal gsAccounts As String)
Dim gsAccountsString As String = ""
Dim StringTemp
StringTemp = gsAccounts.Split(",")
Dim i As Integer
For i = 0 To UBound(StringTemp)
StringTemp(i) = StringTemp(i).ToString.Trim
If StringTemp(i) <> "" Then
If StringTemp(i).ToString.Substring(0, 1) = "'" Then
StringTemp(i) = """" & StringTemp(i).ToString.Substring(1, Len(StringTemp(i).ToString) - 2) & """"
End If
End If
If i <> UBound(StringTemp) Then
gsAccountsString = gsAccountsString & StringTemp(i).ToString.Replace("'", "''") & ","
Else
gsAccountsString = gsAccountsString & StringTemp(i).ToString.Replace("'", "''") & ""
End If
Next
gsAccountsString = gsAccountsString.Replace("""", "'")
Return gsAccountsString
End Function

Using a form control as a field selector in SQL query

I am attempting to build a form ,called UI, that users will select a dimension parameter from a combobox "cmbFilter" and then add a +/- tolerance in a text box "txtTolerance". After selection a part number from a list this should return results for similar part numbers in the the tolorence range for that parameter. The field names in the table are the dimension parameters and are .AddItem to the combobox in the form load code.
Example. Part#1 OD is 5, so I select "OD" as the search parameter then I set a tolerance to +/- 1. The results should show Part#2 with a OD of 6 but not Part#3 with a OD of 7.
I have set a listboxs row source to the query but
no matter what I change in the syntax in this code I get operation or syntax errors. So I assume Im not referencing the form control right, or my logic isn't right?
I have tired the following code in the SQL design view in access.
SQL
SELECT Part_Matrix.Part_Number, Part_Matrix.Customer, Part_Matrix.Large_OD, Part_Matrix.Vent_Opening, & _
Part_Matrix.BPT, Part_Matrix.MFT, Part_Matrix.PD, Part_Matrix.Hat_ID, Part_Matrix.Microfinish, & _
Part_Matrix.Turn_Operations, Part_Matrix.Stud_Holes, Part_Matrix.SH_Dimensions, Part_Matrix.Manufacturer_Holes, & _
Part_Matrix.MH_Dimensions, Part_Matrix.Other_Holes, Part_Matrix.Other_Dimension
FROM Part_Matrix
WHERE [Forms]![UI]![cmbFilter]
BETWEEN (((SELECT [Forms]![UI]![cmbFilter] FROM Part_Matrix WHERE Part_Number = [Forms]![UI]![lbSelected]) - [Forms]![UI]![txtTolerance])
AND ((SELECT [Forms]![UI]![cmbFilter] FROM Part_Matrix WHERE Part_Number = [Forms]![UI]![lbSelected]) + [Forms]![UI]![txtTolerance]))
ORDER BY [Forms]![UI]![cmbFilter] DESC;
I have also tried to write the SQL code in access vba still no luck, the code below was just a simple text, I know its now the same logic as above.
Private Sub btnSearch_Click()
Dim SQL As String
If txtTolerance = "" Then
MsgBox ("No Tolerance Entered")
Exit Sub
ElseIf cmbFilter = "" Then
MsgBox ("No Filter Criteria Entered")
Exit Sub
Else
SQL = "SELECT Part_Matrix.[Part_Number], " & Me.cmbFilter & " " & _
"FROM Part_Matrix" & _
"ORDER BY " & Me.cmbFilter & " DESC;"
Debug.Print SQL
DoCmd.RunSQL SQL
lbFilterResults.RowSource = SQL
lbFilterResults.Requery
End If
End Sub
Try this, using a dlookup instead of SELECT to return the values you want in the BETWEEN statement. I believe the dlookup should return the value for whatever field you select in the combo box. Also, I've simplified to remove the forms!UI statement with a "me" assuming you are running code from the same form. Let me know if this works for ya.
intTarget = dlookup(me!CmbFilter, "PartMatrix", "Part_Number = " & me!LbSelected)
intLower = intTarget - me!txtTolerance
intUpper = intTarget + me!txtTolerance
strSQL = "SELECT * FROM Part_Matrix WHERE " & me!cmbFilter & " " & _
"BETWEEN " & intLower & " AND " & intUpper
In your BETWEEN statement, reference the table's [Large_OD] field and not the form'S.
i.e
WHERE Large_OD
BETWEEN (((SELECT Large_OD FROM Part_Matrix WHERE Part_Number = [Forms]![UI]![lbSelected]) - [Forms]![UI]![txtTolerance])
AND ((SELECT Large_OD FROM Part_Matrix WHERE Part_Number = [Forms]![UI]![lbSelected]) + [Forms]![UI]![txtTolerance]))
ORDER BY " & Me.cmbFilter & " DESC;

VBA ACCESS Comparing String as they are integer

I am trying to prompt the user to input a range and display all the instruments that are within that range in a subform.
Problem: The upper and lower range is a text field (because some of the range cannot be expressed in integer). As seen in the screenshot, the comparison only compare the first character of the field.
User's input: 5 - 3
On the subform: 36 - 4
It compares 5 and 3 instead of 36
I know vba is doing what it has been told but how can I achieve the result I want?
Here is my code for requering the subform:
Dim Up As Integer
Dim Low As Integer
If Me.Text_L = "" Or IsNull(Me.Text_L) Or Me.Text_U = "" Or IsNull(Me.Text_U) Then
MsgBox ("Please choose a valid range!")
Else
Up = Me.Text_U
Low = Me.Text_L
SQL = SQL_Origin & " WHERE [qry_View_Search].[Upper_Range] <= '" & Up & "' " _
& "AND [qry_View_Search].[Lower_Range] >= '" & Low & "';"
subform_View_Search.Form.RecordSource = SQL
subform_View_Search.Form.Requery
End If
so what i did is made a new column in the query for
IIf(IsNumeric([Upper]), Val([Upper]), Null)
to get all the numeric result.
Then in the vba, I re query the subform as below
SQL = SQL_Origin & " WHERE [qry_View_Search].[Upper] <= cint(Forms![frm_View_Search]![Text_U]) " _
& "AND [qry_View_Search].[Lower] >= cint(Forms![frm_View_Search]![Text_L]);"
Thanks #HansUp !
I have successfully for those cases used Val only:
Value: Val([FieldName])
or:
Value: Val(Nz([FieldName]))

How do I automate the built in "find and replace" function in MS-Access?

Is there a way to automate the find and replace function for MS Access?
I've got a lot of data I need to obscure (names and addresses), in a non-reversible way. It's going to an outside contractor that can't see the information (no NDA, etc. will do). But otherwise, I want the data to look as real as possible.
My plan right now is to do a find-n-replace on each character a-z and replace it with a random character. I recognise that chances are, I'll likely end up mapping two or more characters to the same value (which is not a bad thing in my books).
Ideally I'd like to have some kind of function that looks something like:
autoFindNReplace ("table name", "field name", _
"search char", random_alpha_generator(), _
DO_ALL_RECORDS)
And then I can run loop that on each field on each table that I have obscure.
My alternate methods are to:
walk each table and obscure each field individually.
try to come up with some sql statement that will do the same as the mythical autoFindNReplace I describe above.
You can just write a quick hash function in a VBA module and call it from a SQL Update query. Here's an example with table "Table1", and the field "address". The hashField code was taken from here.
Sub MaskAddress()
'Change 1234 to whatever key you'd like.
DoCmd.RunSQL "UPDATE Table1 SET address = hashField(address, 1234)"
End Sub
Public Function hashField(strIn As String, lngKey As Long) as String
Dim i As Integer
Dim strchr As String
For i = 1 To Len(strIn)
strchr = strchr & CStr(Asc(Mid(strIn, i, 1)) Xor lngKey)
Next i
hashField = strchr
End Function
Here was my solution:
Sub autoFindAndReplace(TableName As String, _
FieldName As String, _
Search As String, _
Replace As String)
Dim UpdateString As String
UpdateString = ("update " & TableName & _
" set " & FieldName & _
" = replace (" & FieldName & ", " & _
"""" & Search & """, """ & Replace & """)")
CurrentDb.Execute (UpdateString)
End Sub
Then I loop on autoFindAndReplace with my random character generator, once for alphas and once for numerics.
Yes, I could have done it with multiple Update statements - however, I had a lot of tables and fields to deal with, and this made it look cleaner.