Access 2007 中用单个 Notes 表关联多个表
结论
不要采用 TableName + ID 形式的”多态外键”,也不必增加 NotesLookup。Access 无法为这种关联实施真正的参照完整性。表名拼错,或者目标记录被删除后留下孤立备注,都只能通过 VBA 或查询自行处理。
对于目前固定的四类记录,可以只使用一个 Notes 表,并设置四个允许为空的外键:
Notes
-----
NoteID
NoteText
InjuryID
TreatmentID
InvoiceID
PaymentID
每条备注必须且只能填写一个外键。这样既能保留单一备注表,也可以继续使用 Access 的关系、级联规则和主窗体/子窗体自动保存机制。
推荐的表结构
假设四个主表的主键都是 AutoNumber,Notes 中对应的外键应使用 Long Integer,不能使用 AutoNumber。
Injury
-------
InjuryID AutoNumber, Primary Key
...
Treatment
---------
TreatmentID AutoNumber, Primary Key
...
Invoice
-------
InvoiceID AutoNumber, Primary Key
...
Payment
-------
PaymentID AutoNumber, Primary Key
...
Notes
-----
NoteID AutoNumber, Primary Key
NoteText Long Text
CreatedAt Date/Time
InjuryID Long Integer, Null allowed
TreatmentID Long Integer, Null allowed
InvoiceID Long Integer, Null allowed
PaymentID Long Integer, Null allowed
备注内容字段建议命名为 NoteText,不要使用含义过于宽泛的 Note。这样查询和 VBA 表达式也更容易读懂。
在 Access 的”数据库工具 → 关系”中建立以下一对多关系:
Injury.InjuryID 1 ─── ∞ Notes.InjuryID
Treatment.TreatmentID 1 ─── ∞ Notes.TreatmentID
Invoice.InvoiceID 1 ─── ∞ Notes.InvoiceID
Payment.PaymentID 1 ─── ∞ Notes.PaymentID
这些关系都可以启用”实施参照完整性”。“级联删除相关记录”是否启用,则取决于具体的业务规则。如果删除发票时必须一并删除相关备注,可以启用级联删除。如果备注需要保留以供审计,就不应启用,而应禁止删除主记录或采用逻辑删除。
限制每条备注只能关联一种记录
四个外键都可以为空,但一条有效备注必须恰好有一个外键不为空。可以在 Notes 表的表级”有效性规则”中填写:
IIf([InjuryID] Is Null,0,1)
+ IIf([TreatmentID] Is Null,0,1)
+ IIf([InvoiceID] Is Null,0,1)
+ IIf([PaymentID] Is Null,0,1)
= 1
对应的”有效性文本”可以设为:
每条备注必须且只能关联 Injury、Treatment、Invoice 或 Payment 中的一条记录。
这条规则会阻止两种错误:
- 四个外键全部为空;
- 同一条备注填写了两个或更多外键。
如果业务上允许先保存草稿备注,之后再建立关联,可以把规则改为 <= 1。这样做会允许暂时没有归属的备注存在。
在窗体中自动保存关联
Access 的主窗体/子窗体机制可以自动填写外键,不需要编写 VBA。
以 Injury 窗体为例:
- 将主窗体的记录源设为
Injury。 - 创建一个以
Notes为记录源的连续窗体或数据表窗体。 - 把这个备注窗体作为子窗体放入
Injury主窗体。 - 设置子窗体控件的关联字段:
Link Master Fields: InjuryID
Link Child Fields: InjuryID
当用户在某条 Injury 记录下新增备注时,Access 会自动把当前 InjuryID 写入 Notes.InjuryID。
其余三个窗体采用相同的设置:
Treatment form:
Link Master Fields: TreatmentID
Link Child Fields: TreatmentID
Invoice form:
Link Master Fields: InvoiceID
Link Child Fields: InvoiceID
Payment form:
Link Master Fields: PaymentID
Link Child Fields: PaymentID
可以为四种场景分别创建备注子窗体,也可以复用同一个子窗体,再由外层窗体设置不同的链接字段。无论采用哪种方式,都应隐藏四个外键控件,避免用户手工修改关联。
查询全部备注
如果需要生成跨类型的统一备注列表,可以使用 UNION ALL 查询。下面的 RecordLabel 只是示例字段,需要替换成各表中实际用于显示记录的字段:
SELECT
N.NoteID,
N.NoteText,
N.CreatedAt,
'Injury' AS RecordType,
N.InjuryID AS RecordID,
I.InjuryNumber AS RecordLabel
FROM Notes AS N
INNER JOIN Injury AS I
ON N.InjuryID = I.InjuryID
UNION ALL
SELECT
N.NoteID,
N.NoteText,
N.CreatedAt,
'Treatment' AS RecordType,
N.TreatmentID AS RecordID,
T.TreatmentNumber AS RecordLabel
FROM Notes AS N
INNER JOIN Treatment AS T
ON N.TreatmentID = T.TreatmentID
UNION ALL
SELECT
N.NoteID,
N.NoteText,
N.CreatedAt,
'Invoice' AS RecordType,
N.InvoiceID AS RecordID,
I.InvoiceNumber AS RecordLabel
FROM Notes AS N
INNER JOIN Invoice AS I
ON N.InvoiceID = I.InvoiceID
UNION ALL
SELECT
N.NoteID,
N.NoteText,
N.CreatedAt,
'Payment' AS RecordType,
N.PaymentID AS RecordID,
P.PaymentNumber AS RecordLabel
FROM Notes AS N
INNER JOIN Payment AS P
ON N.PaymentID = P.PaymentID;
表级规则保证每条备注只有一个外键有值,因此一条备注只会出现在一个查询分支中。
为什么不推荐 TableName + ID
下面这种结构看起来很灵活:
Notes
-----
NoteID
TableName
RecordID
NoteText
但 RecordID 可能指向四张不同的表,Access 无法为它建立普通外键。数据库也就无法自动保证:
TableName是合法的表名;RecordID存在于指定的表中;- 删除主记录时能同步处理备注;
- 表名变更后能自动更新备注;
RecordID的类型与目标主键一致。
查询备注时,还必须根据 TableName 动态选择目标表。普通关系查询、窗体链接字段和级联操作都无法直接完成这种解引用。
把这些字段移到 NotesLookup 也解决不了问题,只是把无法验证的多态引用放到了另一张表中。只有在同一条备注确实需要同时关联多个业务记录时,中间表才有意义。即使使用中间表,TableName + ID 仍然无法获得数据库级的外键保护。
可选的超类型设计
如果 Injury、Treatment、Invoice 和 Payment 在业务上都属于同一种实体,例如 Record 或 CaseItem,可以引入一个公共父表:
Record
------
RecordID
RecordType
Injury
------
RecordID Primary Key, Foreign Key
...
Treatment
---------
RecordID Primary Key, Foreign Key
...
Notes
-----
NoteID
RecordID Foreign Key
NoteText
从关系模型看,这种设计更规整,因为 Notes.RecordID 始终引用同一张父表。但在创建任何 Injury、Treatment、Invoice 或 Payment 记录时,都要先创建对应的 Record,再创建子类型记录。Access 窗体也需要同时维护父表和子表,设计复杂度通常高于使用四个可空外键,部分新增流程还可能需要宏或 VBA。
如果这四张表只是现有系统中固定的四类独立业务对象,并没有共享父实体的实际需求,就不值得只为备注功能引入超类型。
在不额外编写 VBA、同时又要保留一个 Notes 表的情况下,使用四个可空外键,并通过表级有效性规则保证”恰好一个非空”,是 Access 2007 中最直接、可靠的方案。
备注:内容仅供参考。
