Access technique

One lookup table, many drop-downs

A typical Access file grows a pile of tiny lookup tables. This pattern keeps those lists in one table, tlkpInfo. The combo still stores the ID and shows the name.

Try it in the browser · How the lookup system works · Blog

Download the Access file (Access 2007+, sample data only).

LookupCombo.zip

What you get

Beside each combo, Edit opens that list only. Hide Me takes a value out of the drop-down without deleting the row -- so old records still have an ID to show. Do not delete lookup rows in the product. There is no referential integrity here; a delete leaves orphan IDs and broken queries. Physical delete is an admin job later.

If you hide a value that a record still uses, a naive combo goes blank. The Row Source below keeps the current ID in the list and labels it (hidden).

Combo Row Source

Status combo on frmDemoItem. Tag on the combo is 2 (the group id). Category uses [CategoryID] and Tag 3; Assigned uses [AssignedToID] and Tag 4.

The listings are coloured like Visual Studio on a white sheet. Switch the site to Light (top right) if names look faint — keywords stay blue either way.

SQL · Row Source
SELECT tlkpInfo.tlkpInfoID,
       IIf([tlkpInfoDel],[tlkpInfoDesc] & " (hidden)",[tlkpInfoDesc])
FROM tlkpInfo
WHERE (([tlkpInfoGrp]=2 AND [tlkpInfoDel]=False)
    OR [tlkpInfoID]=[StatusID])
ORDER BY IIf([tlkpInfoDel],1,0), [tlkpInfoOrder];

Form events

Requery on Current so the extra hidden row follows the record. Not In List on Assigned only -- one displayed field, current Access acDataErrAdded (Access requeries; do not Requery yourself).

VBA · Form events
Private Sub Form_Current()
    On Error Resume Next
    Me.cboStatus.Requery
    Me.cboCategory.Requery
    Me.cboAssigned.Requery
End Sub

Private Sub btnEditStatus_Click()
    Call fEditComboData(Me.cboStatus)
End Sub

Private Sub cboAssigned_NotInList(NewData As String, Response As Integer)
    Response = fNotInListLookup(Me.cboAssigned, NewData)
End Sub

basConstants

VBA · basConstants
Option Compare Database
Option Explicit
Public Const conQuote As String = """"""
Public Const conOPEN As String = "Open"
Public Const conAppName As String = "Nifty Lookup Demo"

basLookupInfo

Edit opens frmTlkpInfo for that combo's group. Not In List uses DAO AddNew (or unhides a Hide Me row). Group id comes from the combo Tag first; parsing RowSource is only a fallback.

VBA · basLookupInfo
Option Compare Database
Option Explicit

' Combo+Edit for tlkpInfo. Prefer Combo.Tag = group id (e.g. "2").
' fGroupNumber still parses RowSource if Tag is blank (Access scar; web passes groupId).
'
' NotInList (current Access): LimitToList Yes; DAO AddNew (or unhide);
' Response = acDataErrAdded so Access requeries -- do not Requery yourself.
' On No: Undo + acDataErrContinue.
' Access 2007+ ListItemsEditForm is for a normal lookup table, not a grouped
' tlkpInfo list (it cannot pass Grp). Use the Edit button + fEditComboData.

Public Function fEditComboData(oComboToUpdate As ComboBox) As String
    On Error GoTo Error_Handler
    Dim strFrmName As String
    strFrmName = "frmTlkpInfo"
    DoCmd.OpenForm strFrmName, acNormal
    With Forms(strFrmName)
        .prpGrpNumber = fGroupNumber(oComboToUpdate)
        .fSetUp
        Set .prpComboBox = oComboToUpdate
    End With
    Exit Function
Error_Handler:
    MsgBox "fEditComboData: " & Err.Number & " " & Err.Description, vbExclamation, conAppName
End Function

Public Function fAppendToLookUpTableGroup(ByVal lngInfoOrder As Long, ByVal strInfoDesc As String, ByVal blnInfoDel As Boolean, ByVal lngInfoGrp As Long)
    Dim rst As DAO.Recordset
    On Error GoTo Error_Handler
    Set rst = CurrentDb.OpenRecordset("tlkpInfo")
    rst.AddNew
    rst!tlkpInfoOrder = lngInfoOrder
    rst!tlkpInfoDesc = strInfoDesc
    rst!tlkpInfoDel = blnInfoDel
    rst!tlkpInfoGrp = lngInfoGrp
    rst.Update
    rst.Close
    Set rst = Nothing
    Exit Function
Error_Handler:
    On Error Resume Next
    If Not rst Is Nothing Then
        rst.Close
        Set rst = Nothing
    End If
    MsgBox "fAppendToLookUpTableGroup: " & Err.Number & " " & Err.Description, vbExclamation, conAppName
End Function

Public Function fNotInListLookup(oCombo As ComboBox, ByVal strNewData As String) As Integer
    ' Usage in the combo NotInList event:
    '     Response = fNotInListLookup(Me.cboAssigned, NewData)
    Dim lngGrp As Long
    Dim lngOrder As Long
    Dim rst As DAO.Recordset
    Dim strCrit As String
    On Error GoTo Error_Handler
    fNotInListLookup = acDataErrContinue
    strNewData = Trim$(strNewData)
    If Len(strNewData) = 0 Then
        oCombo.Undo
        Exit Function
    End If
    If MsgBox("'" & strNewData & "' is not in the list. Add it?", vbYesNo + vbQuestion, conAppName) <> vbYes Then
        oCombo.Undo
        Exit Function
    End If
    lngGrp = fGroupNumber(oCombo)
    If lngGrp = 0 Then
        MsgBox "This combo has no tlkpInfo group (set Tag to the group id).", vbExclamation, conAppName
        oCombo.Undo
        Exit Function
    End If
    strCrit = "tlkpInfoGrp=" & lngGrp & " AND tlkpInfoDesc='" & Replace(strNewData, "'", "''") & "'"
    Set rst = CurrentDb.OpenRecordset("tlkpInfo", dbOpenDynaset)
    rst.FindFirst strCrit
    If Not rst.NoMatch Then
        If rst!tlkpInfoDel Then
            rst.Edit
            rst!tlkpInfoDel = False
            rst.Update
        End If
    Else
        lngOrder = Nz(DMax("tlkpInfoOrder", "tlkpInfo", "tlkpInfoGrp=" & lngGrp), 0) + 1
        rst.AddNew
        rst!tlkpInfoOrder = lngOrder
        rst!tlkpInfoDesc = strNewData
        rst!tlkpInfoDel = False
        rst!tlkpInfoGrp = lngGrp
        rst.Update
    End If
    rst.Close
    Set rst = Nothing
    fNotInListLookup = acDataErrAdded
    Exit Function
Error_Handler:
    On Error Resume Next
    If Not rst Is Nothing Then
        rst.Close
        Set rst = Nothing
    End If
    fNotInListLookup = acDataErrContinue
    MsgBox "fNotInListLookup: " & Err.Number & " " & Err.Description, vbExclamation, conAppName
End Function

Public Function fGroupNumber(oCombo As ComboBox) As Long
    Dim strRow As String
    Dim strPrefix As String
    Dim x As Integer
    Dim intPlaces As Integer
    Dim strCh As String
    On Error GoTo Error_Handler
    If Len(Trim$(Nz(oCombo.Tag, ""))) > 0 And IsNumeric(oCombo.Tag) Then
        fGroupNumber = CLng(oCombo.Tag)
        Exit Function
    End If
    strRow = oCombo.RowSource & ""
    strPrefix = "SELECT tlkpInfo.tlkpInfoID, tlkpInfo.tlkpInfoDesc FROM tlkpInfo WHERE (((tlkpInfo.tlkpInfoDel)=False) AND ((tlkpInfo.tlkpInfoGrp)="
    For x = 1 To 50
        strCh = Mid$(strRow, Len(strPrefix) + x, 1)
        If strCh = ")" Then
            intPlaces = x - 1
            Exit For
        End If
    Next x
    If intPlaces < 1 Then
        fGroupNumber = 0
        Exit Function
    End If
    fGroupNumber = CLng(Mid$(strRow, Len(strPrefix) + 1, intPlaces))
    Exit Function
Error_Handler:
    fGroupNumber = 0
    MsgBox "fGroupNumber: " & Err.Number & " " & Err.Description, vbExclamation, conAppName
End Function

What this is not

It is not a value list typed into the combo. It is not a replacement for a lookup that needs extra columns (billable, tax). Those stay their own table. The web demo is sample data in this browser only.