在订单表中存储账单地址最佳实践

13 database-design best-practices

有人能帮我理解这个用户对CustomerLocation表的回答吗?我真的想要一个在订单表中存储地址的好方法。

我正在寻找的是如何设置我的地址,以便在我编辑它们时,订单不受客户更新地址或搬迁这一事实的影响。

就目前而言,我的架构看起来类似于:

 Person           |EntityID|
 EntityAddress    |EntityID|AddressID|
 Address          |AddressID|AddressType|AddressLine1|AddressLine2|
 Order            |OrderID|BillingAddressID|
Run Code Online (Sandbox Code Playgroud)

MDC*_*CCL 20

从概念上讲,虽然在您的业务环境中OrderAddress是密切相关的概念,但它们实际上是两种独立的实体类型,每种类型都有自己的一组适用的属性(或属性)和约束。

因此,正如之前在评论中所述,我同意@Erik,您应该组织数据库的逻辑布局,并在其他元素中声明:

  • 一张离散表来保存地址信息;
  • 一张表格以保留客户特定的详细信息;
  • 一张表来包含订单数据点;和
  • 一张表,包含有关客户地址之间关联的事实;

我将在下面举例说明。

说明性IDEF1X图

一张图值一千字,所以我创建了图 1所示的 IDEF1X 图来说明我的建议开启的一些可能性:

图 1 - 客户、订单和地址说明性 IDEF1X 图

客户地址及其关联

如图所示,我描绘了实体类型Customer aAddress之间多对多 (M:N) 基数比的关联;这种方法将提供未来的灵活性,因为如您所知,一个客户可以随着时间的推移甚至同时保留多个地址,并且多个客户可以共享同一个地址

一对多 (1:M)客户可以通过多种方式使用特定地址;例如,它可以定义为Physical,和/或可以设置为Shipping和/或Billing。也许,同一个Address实例可以同时满足上述每个目的,或者它可能涵盖两种用途,而不同的Address发生涵盖其余用途。


a在某些业务环境中,客户可以是个人组织(这种情况意味着安排略有不同,如有关超类型 - 子类型结构的答案中所述)但为了提供一个简化的示例,我决定不要在这里包括这种可能性。如果您需要在数据库中涵盖这种情况,之前链接的帖子显示了解决所述需求的方法。


订单地址客户地址地址角色

通常,一个Order只需要两种Addresses,一种用于Shipping,一种用于Billing。这样,同一个Address实例可以为单个Order填充两个Roles,但每个Role由各自的属性(即ShippingAddressIdBillingAddressId)描绘。

订单通过CustomerAddress关联实体类型在两个多属性 FOREIGN KEY 的帮助下与Address连接,即,

  • ( CustomerNumber , ShippingAddressId ), 和 ( CustomerNumber , BillingAddressId ),

都指向CustomerAddress多属性 PRIMARY KEY 显示为

  • ( CustomerNumber , AddressId )

... 这有助于表示一个业务规则,该规则规定 (a) 一个订单实例必须专门与 (b)先前与发出该订单的特定客户相关联的地址事件相关联,并且永远不要与 (c) 随机的非客户相关联-相关地址

(1) Address和 (2) CustomerAddress关联的历史记录

如果您想提供修改地址信息的可能性,那么您必须跟踪所有数据更改。通过这种方式,我将Address描述为一个“可审计”的实体类型,它维护自己的AddressHistory

由于CustomerAddress之间的连接的性质也可能受到一个或多个修改,因此我还描述了通过CustomerAddressHistory实体类型将这种关联处理为“可审计”关联的可能性。

在这方面,Q&A没有处理的各种因素1问答号。2,——两者都是关于在数据库中启用时间能力——真的很重要。

说明性 SQL-DDL 逻辑布局

因此,根据上面显示和解释的图表,我声明了以下逻辑级别的安排(您可以精确地进行调整以满足您的需求):

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also, you should make accurate tests to define the 
-- most convenient INDEX strategies based on the exact 
-- data manipulation tendencies of your business domain.

-- As one would expect, you are free to utilize 
-- your preferred (or required) naming conventions. 

CREATE TABLE Customer (
    CustomerNumber      INT      NOT NULL,
    SpecificAttribute   CHAR(30) NOT NULL,
    ParticularAttribute CHAR(30) NOT NULL,  
    CreatedDateTime     DATETIME NOT NULL,
    -- 
    CONSTRAINT Customer_PK PRIMARY KEY (CustomerNumber)
);

CREATE TABLE Address (
    AddressId           INT      NOT NULL,
    SpecificAttribute   CHAR(30) NOT NULL,
    ParticularAttribute CHAR(30) NOT NULL,  
    CreatedDateTime     DATETIME NOT NULL,  
    -- 
    CONSTRAINT Address_PK PRIMARY KEY (AddressId)
);

CREATE TABLE CustomerAddress (
    CustomerNumber  INT      NOT NULL,  
    AddressId       INT      NOT NULL,
    IsPhysical      BIT      NOT NULL,
    IsShipping      BIT      NOT NULL,  
    IsBilling       BIT      NOT NULL,
    IsActive        BIT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,  
    -- 
    CONSTRAINT CustomerAddress_PK           PRIMARY KEY (CustomerNumber, AddressId),
    CONSTRAINT CustomerAddressToCustomer_FK FOREIGN KEY (CustomerNumber)
        REFERENCES Customer (CustomerNumber),
    CONSTRAINT CustomerAddressToAddress_FK  FOREIGN KEY (AddressId)
        REFERENCES Address  (AddressId)  
);

CREATE TABLE MyOrder (
    CustomerNumber      INT      NOT NULL,  
    OrderNumber         INT      NOT NULL,
    ShippingAddressId   INT      NOT NULL,
    BillingAddressId    INT      NOT NULL,    
    SpecificAttribute   CHAR(30) NOT NULL,
    ParticularAttribute CHAR(30) NOT NULL,  
    OrderDate           DATE     NOT NULL,
    CreatedDateTime     DATETIME NOT NULL,  
    -- 
    CONSTRAINT Order_PK                  PRIMARY KEY (CustomerNumber, OrderNumber),
    CONSTRAINT OrderToCustomer_FK        FOREIGN KEY (CustomerNumber)
        REFERENCES Customer        (CustomerNumber),
    CONSTRAINT OrderToShippingAddress_FK FOREIGN KEY (CustomerNumber, ShippingAddressId)
        REFERENCES CustomerAddress (CustomerNumber, AddressId),
    CONSTRAINT OrderToBillingAddress_FK  FOREIGN KEY (CustomerNumber, BillingAddressId)
        REFERENCES CustomerAddress (CustomerNumber, AddressId)          
);

CREATE TABLE AddressHistory (
    AddressId           INT      NOT NULL,
    AuditedDateTime     DATETIME NOT NULL,
    SpecificAttribute   CHAR(30) NOT NULL,
    ParticularAttribute CHAR(30) NOT NULL,  
    CreatedDateTime     DATETIME NOT NULL,  
    -- 
    CONSTRAINT AddressHistory_PK          PRIMARY KEY (AddressId, AuditedDateTime),
    CONSTRAINT AddressHistoryToAddress_FK FOREIGN KEY (AddressId)
        REFERENCES Address  (AddressId)    
);

CREATE TABLE CustomerAddressHistory (
    CustomerNumber  INT      NOT NULL,  
    AddressId       INT      NOT NULL,
    AuditedDateTime DATETIME NOT NULL,    
    IsPhysical      BIT      NOT NULL,
    IsShipping      BIT      NOT NULL,  
    IsBilling       BIT      NOT NULL,
    IsActive        BIT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,  
    -- 
    CONSTRAINT CustomerAddressHistory_PK                  PRIMARY KEY (CustomerNumber, AddressId, AuditedDateTime),
    CONSTRAINT CustomerAddressHistoryToCustomerAddress_FK FOREIGN KEY (CustomerNumber, AddressId)
        REFERENCES CustomerAddress (CustomerNumber, AddressId)
);
Run Code Online (Sandbox Code Playgroud)

如果你想看一看,我在这个在 SQL Server 2017 上运行的db<>fiddle中测试了它。

History

您问题的以下摘录非常重要:

我正在寻找的是如何设置我的地址,以便在我编辑它们时,订单不会受到客户更新其地址或搬迁这一事实的影响。

AddressHistoryCustomerAddressHistory在确保一台辅助订单不受地址的变化,所有的“以前”行应在各自保留History表,可以在SELECT查询涉及必要时。

间隔包围在值之间所包含AddressHistory.CreatedDateTimeAddressHistory.AuditedDateTime表示在整个时期期间一定的“过去”的Address行被认为是“本”,“当前”或“有效”。类似的考虑适用于CustomerAddressHistory行。

反过来,CustomerAddress.IsActiveBIT(布尔值)列旨在指出某Address行是否可以被某行“使用” Customer;例如,如果它设置为“false”,它将传达这样一种情况,即客户不再使用该地址,因此它不能用于新订单

数据操作因素

修改和擦除

如果客户想要更改有关给定“当前”地址的一条或多条信息,则必须确保 (a)Address在修改发生之前“存在”的相应行被插入到AddressHistory表中,并且 ( b) 有Address问题的行用新值更新。我建议您将此过程作为单个ACID TRANSACTION 中的单个工作单元来执行。

History应该禁止对这两个表进行UPDATE 操作(试图“改变”历史甚至会产生负面的法律含义),因为保留在那种“种类”表中的每一行都代表过去发生的一个事实,因此它不能被修改和关于它的一行不应该被改变。

至于这些表上的 DELETE,在某些非常特殊的情况下,必须启用它们才能遵守保密法律/政策——但是,像往常一样,这完全取决于重要业务环境的确切要求——。

检索和物理级调优

地址出现的“当前”、“当前”或“有效”版本必须作为Address表中的一行包含,但是从(或来自)表中选择地址的先前“状态”很容易,并且可能是一个有趣的练习,以提高您的 SQL 编码技能。AddressHistoryCustomerAddressHistory

关于您在评论中提到的一种情况,如果您想Address从 its检索单个行的“倒数第二个版本” AddressHistory,您必须考虑与手头的特定值匹配的theMAX(AddressHistory.AuditedDateTime)和 the 。AddressHistory.AddressIdAddress.AddressId

在这方面——至少在构建关系数据库时——首先定义相应的概念模式(基于适用的业务规则),然后声明其后续的逻辑DDL 安排是非常方便的。一旦您获得了这些基本元素的稳定可靠版本(当然,它们会随着时间的推移而演变),就该分析和确定操作(通过 INSERT、UPDATE、DELETE 和 SELECT 操作或它们的组合)的最佳方法的时候了。关于数据,负责相应物理级处理的优化(通过例如索引调整、硬件升级、适当的软件设置等)。

最终用户感知、视图和应用程序帮助

显然,在外部抽象级别,地址信息被(最终用户)认为是订单的一部分——这并没有错——,但这并不一定意味着建模者必须设计重要的部分有问题的数据库与所述感知相对应。在这一点上,如果有需要,例如打印一个“完整”的订单(非常可行),您可以借助一些 JOIN 运算符和 WHERE 子句(考虑到相关的有效期)按需“复制”它等)可能会固定在视图中以供将来使用,将相关数据集发送到相关的应用程序,这些应用程序反过来可以根据需要增强其格式。

当然,当订单生效时,应用程序也会非常有用;例如,桌面/移动应用程序窗口或网页可以:

  • 仅显示地址(ES)的是所涉及的客户已经确立为“可用”(通过CustomerAddress.IsActive);
  • 名单在一起的所有地址客户已经为计费服务使能(通CustomerAddress.IsBilling); 和
  • 客户为运输服务定义的所有地址(通过)分组;CustomerAddress.IsShipping

以这种方式促进 GUI 上所有涉及的过程(这自然是计算机化系统外部抽象级别的一部分)。


选择

另一方面,我看到一些系统每次执行新订单时都必须输入相关地址信息(有时重复),并且永远不会删除用于过去订单地址,因此订单不受地址更改的影响。

此操作过程肯定会涉及大量数据复制,但有可能(取决于您的业务领域的确切信息要求)可行,因此您可能还想评估其优缺点。


评论互动

你能解释一下我们需要一个Address实体的原因吗?仅仅拥有还不够CustomerAddress吗?– @Cristiano3 月 11 日 19:38:05Z

在这种特定情况下,原始发布者(带有现已删除的帐户)展示了一个涉及Address实体类型的场景,该实体类型包含四个感兴趣的离散属性,即AddressIDAddressTypeAddressLine1AddressLine2 — 如问题本身所示—,每个其中 (a) 具有一组特定的有效值,(b) 涉及一组潜在的排他性约束,以及 (c) 暗示必须单独操作的值。此外,(d) 任何给定的Address实例——即适当的Address实体——可能与n次出现的其他类型的实体相关联;因此, (e)地址 本身就是一种实体类型。

很可能在其他情况下,根据确切的业务需求,应将Customer.Address视为单个属性,在这种情况下,必须在抽象的逻辑级别以原子方式处理其值,即,您始终操作每个地址值作为一个整体,永远不要仅对某些值部分执行操作(插入、更新、删除、选择)。当然,如果需要,首先应该为相应的原子Customer.Address列设置约束,因为这些对于列“分数”不是必需的。其中一种情况可能是前一节中讨论的题为“替代方案”的场景。

建议阅读

您请求(在现已删除的评论中)一些关于声音数据库文献的提示;因此,对于理论的材料,我强烈建议你阅读了书面工作EF科德博士,一个图灵奖收件人和,当然还有独家发起的的数据的关系模型(也许现在比以往任何时候都更相关)。这份名单包括他的一些极具影响力的文章和论文。

没有列入上述名单的两部重要著作,正是他于 1981 年发表的题为Relational Database: A Practical Foundation for Productivity 的ACM 图灵奖讲座,以及他出版的名为The Relational Model for Database Management: Version 2 的书1990 年。

概念设计方面,信息建模集成定义(IDEF1X) 是一项值得高度推荐的技术,美国国家标准与技术研究院(NIST)于 1993 年 12 月将其定义为标准。

  • @Shadrix 我刚刚编辑了帖子,以防你想看一看。 (2认同)