当然我意识到没有一种“正确的方法”来设计一个SQL数据库,但我希望在我的特定场景中得到一些关于更好或更差的意见。
目前,我正在设计一个订单输入模块(使用SQL Server 2008的Windows .NET 4.0应用程序),当涉及到可以在多个位置应用的数据时,我在两个设计决策之间徘徊。在这个问题中,我将专门提到地址。
地址可以被各种对象(订单,客户,员工,货运等)使用,它们几乎总是包含相同的数据(地址1/2/3,城市,州,邮政编码,国家等) )。我原本打算将每个字段作为列包含在每个相关表中(例如,订单将包含Address1 / 2/3,City,State等。而Customers也将包含相同的列布局)。但我的一部分想要将DRY / Normalization原则应用于这种情况,即有一个名为“Addresses”的表,它通过相应表中的Foreign Key引用。
CREATE TABLE DB.dbo.Addresses
(
Id INT
NOT NULL
IDENTITY(1, 1)
PRIMARY KEY
CHECK (Id > 0),
Address1 VARCHAR(120)
NOT NULL,
Address2 VARCHAR(120),
Address3 VARCHAR(120),
City VARCHAR(100)
NOT NULL,
State CHAR(2)
NOT NULL,
Country CHAR(2)
NOT NULL,
PostalCode VARCHAR(16)
NOT NULL
)
CREATE TABLE DB.dbo.Orders
(
Id INT
NOT NULL
IDENTITY(1000, 1)
PRIMARY KEY
CHECK (Id > 1000),
Address INT
CONSTRAINT fk_Orders_Address
FOREIGN KEY REFERENCES Addresses(Id)
CHECK (Address > 0)
NOT NULL,
-- other columns....
)
CREATE TABLE DB.dbo.Customers
(
Id INT
NOT NULL
IDENTITY(1000, 1)
PRIMARY KEY
CHECK (Id > 1000),
Address INT
CONSTRAINT fk_Customers_Address
FOREIGN KEY REFERENCES Addresses(Id)
CHECK (Address > 0)
NOT NULL,
-- other columns....
)
从设计的角度来看,我喜欢这种方法,因为它创建了一个易于更改的标准地址格式,即如果我需要添加Address4,我只需将它添加到一个地方而不是每个表中。但是,我可以看到构建查询所需的JOIN数量可能会有点疯狂。
我想我只是想知道是否有任何企业级SQL架构师成功地使用过这种方法,或者这样创建的JOIN数量是否会产生性能问题?
答案 0 :(得分:27)
通过将地址分解到自己的表中,您走在了正确的轨道上。我会添加一些额外的建议。
考虑从Customers / Orders表中取出Address FK列并改为创建联结表。换句话说,现在将客户/地址和订单/地址视为设计中的多对多关系,以便将来轻松支持多个地址。是的,这意味着引入更多的表格和联接,但您获得的灵活性非常值得。
考虑为城市,州和国家/地区实体创建查找表。然后,地址表的city / state / country列由指向这些查找表的FK组成。这使您可以保证所有地址的拼写一致,并为您提供存储其他元数据(例如,城市人口)的地方,如果将来需要的话。
答案 1 :(得分:17)
我只是有一些警告。对于其中的每一种,解决问题的方法不止一种。
首先,规范化并不意味着“用id号替换文本”。
其次,你没有钥匙。我知道,你有一个名为“PRIMARY KEY”的专栏,但这还不够。
insert into Addresses
(Address1, Address2, Address3, City, State, Country, PostalCode)
values
('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500');
select * from Addresses;
1;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
2;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
3;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
4;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
在没有任何其他约束的情况下,您的“主键”标识一行;它不识别地址。识别一行通常不够好。
第三,“Address1”,“Address2”和“Address3”不是地址的属性。它们是邮寄标签的属性。 (邮寄标签上的行。)这种区别对您来说可能并不重要。这对我来说非常重要
。第四,地址有一生。在出生和死亡之间,他们有时会改变。当街道被重新布线,建筑物被分割,建筑物被分开时,它们会改变,有时(我很确定)当一个城市雇员有一个品脱太多时。自然灾害可以消灭整个社区。有时建筑物会重新编号。在我们的数据库中,与大多数人相比,很小,每年约有1%的变化。
当地址死亡时,你必须做两件事。
当地址本身发生变化时,您必须做两件事。
第五,DRY不适用于外键。他们的整个目的是重复的。唯一的问题是关键有多宽? ID号很窄,但需要连接。 (10个id号可能需要10个连接。)地址很宽,但不需要连接。 (我在这里谈的是一个正确的地址,而不是邮寄标签。)
这就是我能想到的全部。
答案 2 :(得分:10)
我认为你不知道存在一个问题,那就是有些数据是时间敏感的。您不希望您的记录显示您向35 State St,Chicago Il发送订单,当您实际将其发送到10 King Street,Martinsburg WV但客户在订单发货两年后搬迁。所以,是的,建立一个地址表来获取该时刻的地址,只要对客户这样的地址的任何更改都会导致新的地址,而不是更改当前地址会破坏订单的历史记录。
答案 3 :(得分:5)
据我所知,拥有一个单独的地址表没有实际价值。它将导致更多的连接和更复杂的代码,并且您的短语“几乎总是包含相同的数据”使我认为您将找到痛苦的例外。
如果地址本身就是实体,那么您希望地址位于单独的表中(意味着它们具有标识,如果两个对象指向同一地址或不同的地址,则它很重要)。如果你的域名是这种情况,我认为这将是非常明显的,你不需要提出这个问题。另一个答案有一个关于地址可变性的有效观点,例如送货地址是订单的一部分,不应该从订单下面更改。因此,地址没有自己的生命周期,将其作为一个单独的实体处理只会导致混淆。
“规范化”特指从数据中删除冗余,因此您没有在不同位置表示相同的项目。这里唯一的冗余是在DDL中,它不在数据中,因此“规范化”与此无关。
答案 4 :(得分:3)
您必须自己回答的问题是,日常用语中的相同地址实际上是否与数据库中的相同的地址。如果有人“改变他的地址”(通俗地说),他真的把自己联系到另一个地址。地址本身仅在重新命名街道,进行邮政编码改革或核弹击中时才会发生变化。这些都是罕见的事件(希望大部分时间)。你的主要利润是:在一个地方换多行(多个表)。
如果您实际上应该更改模型中的地址 - 在表地址UPDATE
的意义上 - 对于链接到它的其他行可能会或可能不会。另外,根据我的经验,即使是完全相同的地址也必须根据不同的目的看起来不同。理解语义差异,您将获得最能代表您现实世界的正确模型。
我有许多数据库,我使用一个街道的公用表(它使用一个城市表(使用国家表,......))。结合街道号码将其视为地理编码(lat / lon),而不是“街道名称”。地址在不同的表(或行)之间共享不。街道名称和邮政编码级联的更改,其他更改不会。
答案 5 :(得分:2)
您通常会尽可能规范化数据,因此请使用“地址”表格。
您可以使用视图对之后使用索引的数据进行反规范化,并且应该提供一种方法来访问具有简单引用的数据,同时使基础结构完全标准化。
连接数不应该是主要问题,基于索引的连接不是太多的开销。
答案 6 :(得分:2)
可以使用拆分地址表。
但是,您必须避免允许多行引用相同地址的诱惑,而没有适当的系统来管理用户的选项,以决定是否以及如何更改地址为新地址更改分割出一行,即您拥有相同的地址用于结算和送货地址。然后用户说他们的地址正在改变。首先,旧订单可能(应该?)需要保留其送货地址,因此您无法就地更改它。但是用户可能还需要说这个我正在改变的地址只会改变交付地址。
答案 7 :(得分:0)
我更喜欢使用一个XREF表,其中包含对人/业务表的FK引用,对地址表的FK引用,以及通常对角色表(HOME,OFFICE等)的FK引用,以描述实际的地址类型。我还包含一个ACTIVE标志,允许我选择忽略旧地址,同时保留维护地址历史记录的能力。
这种方法允许我为每个主要实体维护不同类型的多个地址
答案 8 :(得分:0)
我认为,您应该维护城市,州和国家/地区的一些主表。这样,您可以避免这些实体使用不同的拼写,而这些拼写最终可能会映射到具有不同州/国家/地区的同一城市。
您可以简单地将地址表中的 CityId 映射为外键,如下所示,而不是将地址表本身中的三个字段(城市,州和国家/地区)分别作为纯文本进行映射。
Address: {
CityId
// With other fields
}
City: {
CityId
StateId
// Other fields
}
State: {
StateId
CountryId
// Other fields
}
Country: {
CountryId
// Other fields
}
如果您在地址表中维护所有三个ID(CityId,StateId和CountryId),则最后必须对这些表进行联接。因此,我的建议是只具有cityId,然后通过与上述表结构连接来检索其余所需信息。