Ken Ismert
KIsmert at texassystems.com
Thu Sep 15 18:01:46 CDT 2005
>> Do tell, Do tell OK, OK. A sample database is available. See below. ================= AutoFilter Basics ================= I call this solution the AutoFilter. AutoFilter doesn't build custom SQL statements on the fly. Instead, it uses a standard query with a fixed WHERE clause. This query allows you to return all rows, or restrict the results to any combination of field values that you have elected to filter on. Even though the WHERE clause is more complex than the custom one you would build in code, it often queries significantly faster than a form using custom SQL. This is because the query is pre-parsed, and only needs to be initialized once, when the form loads. Custom SQL must be parsed and initialized every time you change it. Because the magic all happens in the query, no form-level filter string is needed, either. Depending on how you implement it, you can even allow the user to use the built-in Access Form Filter functions, on top of the current AutoFilter. You can put the filter controls and the query results on a single form. I usually put the filter controls in a parent form, and move the results to a sub form. If your filter needs are simple (just a few filter fields), you can get away with two lines of code to implement. I usually add a Clear Filter function, which resets all the controls to Null, adding a few extra lines of code. Implementing the Form Filter on top of AutoFilter capability requires significantly more support code, with a small setup stub in the parent form. ===================== The Filter Expression ===================== All right, but how does it _work_? AutoFilter relies on a simple convention: for a given field, Null returns all rows, and a Value returns only rows matching that field value. This makes it easy to use: select a value from a combobox, and it restricts to that value. Delete the value, and it returns all rows. The trick is how this convention is encoded in the WHERE clause. Remember that annoying property of Nulls -- how if any part of an expression is Null, the whole expression becomes Null? Well, this WHERE clause leverages that behavior. Specifically: (Field = Null) returns Null (always) (Field = Value) returns True or False (depending on match) All you have to do is wrap this in a Nz() function, and you have the basic form: Nz((Field = FilterValue), True) = True This table shows how this expression works: FilterValue Null 3 0 =========================================== Field | 1 | True false false Value | 2 | True false false | 3 | True True false | 4 | True false false You can see how Null matches all rows, and values match any rows whose field contains that value. For an actual control filtering an actual field, the expression becomes: Nz(([MyTable].[Field] = [Forms]![frmFilter]![cboField]), True) = True Here, the current value of cboField filters matching values of MyTable.Field. String multiple filter expressions together with ANDs, and you have a WHERE clause that can filter any number of fields simultaneously. How many, do you ask? I've gone up to about 8 or 9, with little noticeable degradation in query speed. ========== An Example ========== So, how is AutoFilter typically used? The classic case is filtering a Primary table with a number of Foreign key fields: tblOrders tblCustomers (CustomerID) tblItems (ItemID) tblSuppliers (SupplierID) This data is ideal, because the fields being filtered on are Primary Keys, and therefore can't be Null. AutoFilter can work on fields containing Nulls, but you have to build a slightly more complex filter expression to handle that case. Building The Query ------------------ First, build a Result query that includes tblOrders, joined to tblCustomers, tblItems, and tblSuppliers. Include the key fields you are going to filter on, CustomerID, ItemID, and SupplierID. Include enough fields to identify both the primary and foreign tables to the user. Save it as qryFilterOrdersSub. Don't worry about the WHERE clause; we will add it later. Building The Forms ------------------ Build a Result subform, frmFilterOrdersSub. Set it to continuous forms or datasheet view. Assign its RecordSource to qryFilterOrdersSub. Finish by inserting the desired fields from the query, and arranging them so suit your needs. Save the form. For fastest performance, do not use comboboxes. The form should have Allow Filters, Edits, Deletions, and Additions set to No. Save the subform. Next, create your Filter form, frmFilterOrders. Insert a subform control, subFilterOrders, that holds frmFilterOrdersSub. In the frmFilterOrders' module, insert this code: Property Get RequeryMe() As Boolean subFilterOrders.Requery RequeryMe = True End Property Add three combo boxes: cboCustomerID, cboItemID, and cboSupplierID. These simply query their respective tables, and are bound to their respective key fields, CustomerID, ItemID, and SupplierID. In the Combo Box properties dialog for each of your combos, insert this expression in the After Update property: =[RequeryMe] This calls the property RequeryMe, without having to build an event handling routine. Save the form. Finishing The Query ------------------- Re-open qryFilterOrdersSub. Add this WHERE clause to the query: (Nz(([tblCustomers].[CustomerID] = [Forms]![frmFilterOrders]![cboCustomerID]), True) = True) AND (Nz(([tblItems].[ItemID] = [Forms]![frmFilterOrders]![cboItemID]), True) = True) AND (Nz(([tblSuppliers].[SupplierID] = [Forms]![frmFilterOrders]![cboSupplierID]), True) = True) Save the query. You are now finished. Looking At The Results ---------------------- Open the AutoFilter form. The results subform should show all rows, since the initial values of the filter combos are Null. Select a value from one of the combos. The subform should requery, returning only matching rows. Delete the value of the combo, and observe that the restriction drops off. You can easily filter any combination of values. Notes ----- * You can't do Form Filtering on this Result form. I got weird behavior when I tried it. If you can get this to work, I'd like to know how you did it. * I use AutoFilter forms for lookup only -- no adding or editing data. For that, I use a stand-alone form keyed to the selected record in the AutoFilter form. ================ Sample Available ================ I have a sample database that implements the simple form of AutoFilter (no user Form Filter). Contact me off-list for a copy (Rich, yours will be on the way tonight). I'm working up the coding for the more sophisticated form, too. Let me know if you're interested. In return for sharing, all I ask is that my fellow list members not publish this idea first. If you can't tell already, I'm thinking of writing this up as an article. -Ken