Alternative to multi-valued fields in MS Access

2019-03-30 06:14发布

问题:

Related question: Multivalued Fields a Good Idea?

I know that multi-valued fields are similar to many-to-many relationship. What is the best way to replace multi-valued fields in an MS Access application? I have an application that has multi-valued fields. I am not sure how exactly to do away with those and implement exactly same logic in the form of fields that are single-valued?

What would be the implementation in terms of table-relationships when I want to move a multi-valued relationship to single-valued one.

Thank you.

回答1:

The following is probably far more detailed than you need, but it is intended for a beginner. Let us say you have a table, MainTable:

ID -> Numeric, primary key
Title -> Text
Surname -> Text
Address -> Text
Country -> Numeric

You will probably want a list of titles and countries from which to select.

In the case of Title, it would not be the worst thing to store the information in a field in a table, because you have a single column and the data is unlikely to change, and you probably will not be creating a query using the data.

Country is a different story, conventionally you would store a number and have a look-up table. It is the kind of situation where people are tempted to use a multi-value field.

However, convention is a lot easier. Add another table for country:

ID -> Numeric, primary key
Country -> Text

You might like to call the related field in the main table CountryID. You can now create a relationship in the relationship window showing how Country relates to MainTable:

You can see that Enforce Referential Integrity is selected, which means that you must have null or a country from the countries table in the CountryID field.

To view the data, you can create a query:

SELECT 
    MainTable.ID, 
    MainTable.Title, 
    MainTable.Surname, 
    MainTable.Address, 
    Country.Country
FROM Country 
INNER JOIN MainTable 
ON Country.ID = MainTable.CountryID;

But the main point is to have a form that allows data entry. You can create a form using the wizards, but after that, you either need to right-click CountryID and change it to a combobox or add a combobox or listbox using the wizard. Option 2 is probably the easiest. Here are most of the steps from the wizard:

You now have a dropdown list of countries on your form.

See also: create form to add records in multiple tables

In Access 2010, there are new ways of adding values to combos when the user enters data that does not exist in the table of possible values. In previous versions (although I am not sure about 2007), you would use the Not In List event to add items to a look-up table, in 2010, you have a choice of adding a List Items Edit form to the property sheet.



回答2:

There is no replacement for MVFs in an Access database. Some query techniques can mimic MVFs but you may find MVF functionality to be superior. 1. It is fast and very easy to implement. No code and no SQL. 2. It is visual and therefore it is intuitive for the user. There are some things that you cannot do with an MVF so you really need to decide what is more important.



回答3:

The question is rather odd since it asks about a single-value field, but also asks about table-relationships. In a very strict interpretation, a multi-value field (MVF) could be replaced with a single TextBox filled with comma-separated items... no table-relationships required. Instead, I assume by "single-value" field that the question means standard fields in a multi-table relationship, in which each field of each related row has a single value. But each primary record can still be related to multiple rows in the related value table, which preserves the whole purpose of the MVF.

Consider the database outline below to illustrate a possible replacement for the MVF. I'm not including every possible property or how to create basic object, just what's necessary for creating desired behavior--assuming enough knowledge of Access to "fill in the blanks" and no fear of basic code or SQL.

The basic structure consists of three tables: 1) Primary table, 2) "value list" table, 3) junction table used to define many-to-many relationship.

  • Customer Table
    CustomerID: autonumber, primary
    CustomerName: short text
  • Codes Table
    Code: short text, length 5, primary key
    Description: short text
  • [Customer Codes] Table
    CustomerID: long
    Code: short text

Create relationships between the tables. This will require appropriate indexes defined on the tables (not detailed here).

  • Customer Table to [Customer Codes] Table
    CustomerID -> CustomerID fields (with enforce integrity enabled)
  • Code Table to [Customer Codes] Table
    Code -> Code fields (with enforce integrity enabled)

(A separate ID field could also be created for the values table [e.g. Codes Table] table, but that just complicates later queries and controls, etc. In such a case, the junction table would contain another ID field and not the value directly.)

In a standard VBA module, create a function like

Public Function GetCodeList(ByVal CustomerID As Integer) As String
   Dim sSQL As String
   Dim qry As QueryDef
   Dim rs As Recordset2

   sSQL = "PARAMETERS [CustID] LONG;" & _
       " SELECT * FROM [Customer Codes] WHERE [CustomerID] = [CustID]"
   Set qry = CurrentDb.CreateQueryDef("", sSQL)
   qry.Parameters("CustID") = CustomerID
   Set rs = qry.OpenRecordset(dbOpenForwardOnly, dbReadOnly)

   Dim sCodes As String
   sCodes = ""
   Dim bFirst As Boolean
   bFirst = True

   Do Until rs.EOF
      sCodes = sCodes & IIf(bFirst, "", ",") & rs("Code")
      bFirst = False
      rs.MoveNext
   Loop

   rs.Close
   qry.Close

   GetCodeList = sCodes
End Function

Useful queries of the primary table without duplicate rows will require creating some sort of aggregate queries (i.e. Group By, Count, etc.). Simple selection could be done in a single query, for example

SELECT Customer.CustomerID, Customer.[CustomerName], GetCodeList([CustomerID]) AS Codes, Count(Customer.CustomerID) AS CountOfID
FROM Customer LEFT JOIN [Customer Codes] ON Customer.ID = [Customer Codes].CustomerID
WHERE ((([Customer Codes].Code)="Current" Or ([Customer Codes].Code)="Free"))
GROUP BY Customer.ID, Customer.[CustomerName], GetCodeList([ID]);

More complicated selection may require multiple queries, one to first select the proper records, then another to join the primary table to the first query. But honestly, these types of queries are no more complicated than what can be required to select on Multi-Valued Fields. In fact, the query syntax for MVF is non-standard and can get rather complicated and confusing, even more than having a junction table and many-to-many relationships. Behind the scenes, Access is essentially doing the same thing as I've outlined, but because it hides so much detail it make some queries even more difficult.

Regarding multi-value presentation and selection on a form, mimicking the multi-valued ComboBox exactly is not possible--mainly because the basic Access Combobox does not have a multi-selection option with the ability to show checkboxes. However, one can populate a non-bound ListBox with property [Multi Select] = Simple. On the Form_Load event, add available values (e.g. Code from the example table) to the listbox using ListBox.AddItem method. Then in the Form_Current, Form_AfterUpdate, Form_Undo events, one can add code to show and/or save the selected values. This requires more code which is probably beyond the scope here.

Technically the question asked about "moving" a MVF to another implementation. The gist is to populate the values table (e.g. Code table in the example) with the same values in the MVF list. This could be a manual process, but depends on how the MVF's ComboBox is populated. Then write a query which copies each MFV into the junction table (e.g. [Customer Code]) for the same primary record, something like

INSERT INTO [Customer Codes] ( CustomerID, Code )
SELECT Customer.CustomerID, Customer.TestMVF.Value
FROM Customers
WHERE (((Customers.TestMVF.Value) Is Not Null));

A full implementation is definitely not a simple task overall, but if you find too many problems with MVFs or if you are wanting to migrate to another database, this kind of change will be necessary to understand.