在订单表中存储帐单地址最佳做法


10

有人可以帮助我了解此用户对CustomerLocation表的回答。我真的想要一种在订单表中存储地址的好方法。

我要寻找的是如何设置地址,以便在编辑地址时,订单不会受到客户更新其地址或重新安置地址的影响。

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

 Person           |EntityID|
 EntityAddress    |EntityID|AddressID|
 Address          |AddressID|AddressType|AddressLine1|AddressLine2|
 Order            |OrderID|BillingAddressID|

Answers:


16

从概念上讲,尽管在您的业务环境中,“ 订单”和“ 地址”是紧密相关的想法,但实际上它们是两种不同的实体类型,每种类型都有其自己的一组适用属性(或属性)和约束。

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

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

我将在下面举例说明。

信息库IDEF1X图表

一幅图片价值一千个单词,因此我创建了图1所示的IDEF1X图,以说明我的建议所带来的一些可能性:

图1-客户,订单和地址信息库IDEF1X图表

客户地址及其关联

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

一对多(1:M)客户可以多种方式使用特定地址;例如,可以将其定义为“ 实物”,和/或可以将其设置为“ 装运 ”和/或“ 结算”。也许同样的地址实例可以同时服务于每个上述目的,或者它可以覆盖两个用途而不同的地址发生覆盖剩余的一个。

一个在某些业务环境中,客户可以是个人组织(情况这将意味着一个稍微不同的安排,详见在这个答案大约一个超型亚型结构),但与目标提供了一个简单的例子,我决定这里不包括这种可能性。万一您需要在数据库中解决这种情况,上一个链接的帖子显示了解决上述需求的方法。

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

通常,订单仅需要两种地址,一种用于装运,一种用于开票。这样,同一个Address实例可以填充单个Order的两个Role,但是每个Role由各自的属性(如ShippingAddressIdBillingAddressId)来描绘。

借助两个多属性FOREIGN KEY,通过CustomerAddress关联实体类型将订单地址相连。

  • CustomerNumberShippingAddressId)和(CustomerNumberBillingAddressId),

两者都指向显示为CustomerAddress的多属性PRIMARY KEY,如下所示

  • CustomerNumberAddressId

…有助于表示一个业务规则,该规则规定(a)订单实例必须与(b)先前与发出该订单的特定客户相关联的地址出现,而不是与(c)随机非客户 -相关地址

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

如果要提供修改地址信息的可能性,则必须跟踪所有数据更改。通过这种方式,我将Address描述为维护其自身AddressHistory的“可审核”实体类型。

由于客户地址之间的连接性质也可能遭受一个或多个修改,因此我也描述了借助CustomerAddressHistory实体类型将这种关联作为“可审核的”关联进行处理的可能性。

在这方面,问答中涉及的各种因素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)
);

如果您想看看,我运行于SQL Server 2017的db <>小提琴中对其进行了测试。

History

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

我要寻找的是如何设置地址,以便在编辑地址时,订单不受客户更新其地址或重新安置的事实的影响。

AddressHistoryCustomerAddressHistory在确保一台辅助订单不受地址的变化,所有的“以前”行应在各自保留History表,可查询时需要。应该禁止在这两个表上执行UPDATE和DELETE操作(试图更改历史记录甚至会产生负面的法律影响)。

在其中包含的值之间的间隔AddressHistory.CreatedDateTimeAddressHistory.AuditedDateTime代表整个期间,在此期间,某个“过去” Address行被视为“当前”,“当前”或“有效”。类似的注意事项适用于CustomerAddressHistory行。

CustomerAddress.IsActiveBIT(布尔值)列是为了指出一定是否Address行是由“可用” Customer行与否; 例如,如果将其设置为“ false”,则将传达一个事实,即客户不再使用该地址,因此不能将其用于新Orders


:在另一方面,我也看到了一些系统中,每一个新的时间顺序是effectuated的地址信息必须输入(有时反复),以及地址(ES)用于过去订单从不删除(因此(订单不受地址更改的影响)。

此操作过程肯定会涉及大量的冗余,但是有可能-根据您业务领域的确切信息要求-可能起作用,因此您也可能希望评估其优缺点。


资料检索

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

对于您在注释中提到的情况之一,如果要从中检索单个Address行的“倒数第二个版本” AddressHistory,则必须考虑与当前特定值匹配的MAX(AddressHistory.AuditedDateTime)和。AddressHistory.AddressIdAddress.AddressId

在这方面(至少在构建关系数据库时),首先定义相应的概念模式(基于适用的业务规则),然后声明其后续的逻辑 DDL布置是非常方便的。一旦获得了这些基本元素的稳定和可靠的版本(当然,它们会随着时间的推移而发展),就可以分析和确定最佳的操作方式(通过INSERT,UPDATE,DELETE和SELECT操作或它们的组合)了。关于数据。

最终用户的看法,观点和应用程序帮助

显然,在外部抽象级别上,(最终用户)将地址信息视为Order的一部分,并且这没有错,但这并不意味着建模者必须设计该信息的重要部分。这样的数据库。在这一点上,如果需要例如打印“完整” 订单(非常可行),则可以在一些JOIN运算符和WHERE子句的帮助下按需“复制”该订单(考虑有效期)等)固定在将来的视图中,以将相关结果集发送到相关的应用程序,从而可以根据需要增强其格式。

当然,当执行订单时,应用程序也将非常有帮助;例如,桌面/移动应用程序窗口或网页可以:

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

以这种方式促进了GUI上所有涉及的过程(即,计算机化系统的外部抽象级别)。


建议阅读

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

上述清单中未包括的两项重要著作,确切地说是1981年的ACM图灵奖讲座,题为“ 关系数据库:生产力的实用基础”,其书名为《数据库管理的关系模型:第2版》,已出版。在1990年。

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


1
抱歉,我知道该帖子较旧,但是为什么要在MyOrder中引用(例如:REFERENCES Address(AddressId))?为什么不使用CustomerAddress?
Shadrix

1
不用担心,要抓住:事实上,两者MyOrder.ShippingAddressId和都MyOrder.BillingAddressId必须引用CustomerAddress.AddressId(而不是Address.AddressId);通过这种方式,可以确保订单可以专门与先前与发出该订单客户相关的地址相关联。该图表明了这种安排,因此DDL将更加准确。感谢您要求澄清。
MDCCL

2
@Shadrix我刚刚编辑了帖子,以防万一。
MDCCL

@MDCCL当您在History表上说没有UPDATE和DELETE时,表是否应该相同Address?如果客户订购某些东西,然后仅更改邮政编码或城市仅一个字段,该怎么办?我们应该将现有地址History插入Address表中,然后在表中进行新插入,对吗?
迈克·罗斯

1
OTOH,如果客户想要更改有关给定地址的一条或多条信息,则必须确保(a)在进行Address修改之前“存在” 的相应行已插入AddressHistory表中,并且还必须(b )所讨论的Address行已使用新值更新。将这个过程作为事务中的单个工作单元来执行是有利的。
MDCCL

3

这个答案是从对问题的评论中整理出来的。

一种解决方案是对订单表中的地址表使用FK。这样一来,您就可以查看用于订单的地址,并将地址与用户的当前地址解耦。

为了使此工作有效,您必须插入一个新地址并将该新地址链接到User表。这意味着地址只写一次,而编辑对最终用户是一种幻觉。通过将关联从“用户”表移动到带有时间戳的关联表,可以有效地存储与用户关联的所有地址的历史记录。这将为您提供编辑/地址的历史记录,并在地址表中保持不变的数据。

@MDCCL指出:

[您应该]组织数据库结构,其中一个表用于保留与订单相关的数据,而另一个表用于保留地址信息。而且,是的,您绝对可以有一个表格,代表这两个实体类型之间的多对多关系。如果用户可以更改其Address(es)属性,则必须跟踪此类修改,因此应启用AddressHistory这篇文章与后者有关。

MDCCL还概述了如何在此处查找用户的当前地址:

为了获取您拥有的History表的最新版本,您必须考虑MAX(AuditedDateTime)对应的AddressId。第一步是建模/设计最佳的概念和逻辑安排,第二步是找到正确的方法来插入,更新,删除和选择数据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.