SQL数据库设计最佳实践(地址)

Vin*_*hak 31 sql database-design

当然我意识到没有一种"正确的方法"来设计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....
    )
Run Code Online (Sandbox Code Playgroud)

从设计的角度来看,我喜欢这种方法,因为它创建了一个易于更改的标准地址格式,即如果我需要添加Address4,我只需将它添加到一个地方而不是每个表中.但是,我可以看到构建查询所需的JOIN数量可能会有点疯狂.

我想我只是想知道是否有任何企业级SQL架构师成功地使用过这种方法,或者这种创建的JOIN数量是否会产生性能问题?

Joe*_*lli 30

通过将地址分解到自己的表中,您处于正确的轨道上.我会添加一些额外的建议.

  1. 考虑从Customers/Orders表中取出Address FK列并改为创建联结表.换句话说,现在将客户/地址和订单/地址视为设计中的多对多关系,以便将来轻松支持多个地址.是的,这意味着引入更多表和连接,但您获得的灵活性非常值得.

  2. 考虑为城市,州和国家/地区实体创建查找表.然后,地址表的city/state/country列由指向这些查找表的FK组成.这使您可以保证所有地址的拼写一致,并为您提供存储其他元数据(例如,城市人口)的地方,以备将来使用.


Mik*_*ll' 18

我只是有一些警告.对于其中的每一种,解决问题的方法不止一种.

首先,规范化并不意味着"用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
Run Code Online (Sandbox Code Playgroud)

在没有任何其他约束的情况下,您的"主键"标识一行; 它不识别地址.识别行通常不够好.

第三,"Address1","Address2"和"Address3"不是地址的属性.它们是邮寄标签的属性.(邮寄标签上的行.)这种区别对您来说可能并不重要.这对我来说非常重要.

第四,地址有一生.在出生和死亡之间,他们有时会改变.当街道被重新布线,建筑物被分割,建筑物被分开时,它们会改变,有时(我很确定)当一个城市雇员有一个品脱太多时.自然灾害可以消灭整个社区.有时建筑物会重新编号.在我们的数据库中,与大多数数据库相比很小,每年约有1%的变化.

当地址死亡时,你必须做两件事.

  • 确保没有人使用该地址邮寄,运送或其他任何东西.
  • 确保其死亡不会影响历史数据.

当地址本身发生变化时,您必须做两件事.

  • 有些数据必须反映出这种变化.确保它确实如此.
  • 有些数据不能反映这种变化.确保没有.

第五,DRY不适用于外键.他们的整个目的是重复的.唯一的问题是关键有多宽?ID号很窄,但需要连接.(10个id号可能需要10个连接.)地址很宽,但不需要连接.(我在这里谈的是一个正确的地址,而不是邮寄标签.)

这就是我能想到的全部.

  • 你*是*重复外键; 你只是重复一个整数而不是文本.当外键是有意义的文本时,`ON UPDATE CASCADE`允许您在一个地方更新值,除非您的dbms不支持该功能.(Oracle没有.)但是当你处理地址时,你通常需要特别小心*避免*更新某些类型的历史数据,比如账单和发票上的地址.无论您的外键是文本还是整数,都是如此. (2认同)

HLG*_*GEM 10

我认为你不知道有一个问题,那就是有些数据是时间敏感的.您不希望您的记录显示您向35 State St,Chicago Il发送订单,当您实际将其发送到10 King Street,Martinsburg WV但客户在订单发货两年后搬迁.所以,是的,建立一个地址表来获取该时刻的地址,只要对客户这样的地址的任何更改都会导致新的地址,而不是更改当前地址会破坏订单的历史记录.


Nat*_*hes 8

据我所知,拥有一个单独的地址表没有实际价值.它将导致更多的连接和更复杂的代码,并且您的短语"几乎总是包含相同的数据"使我认为您将找到痛苦的例外.

如果它们本身就是实体,那么您希望地址位于单独的表中(这意味着它们具有标识,如果两个对象指向相同的地址或不同的地址,则它很重要).如果你的域名是这种情况,我认为这将是非常明显的,你不需要提出这个问题.另一个答案有一个关于地址可变性的有效观点,例如送货地址是订单的一部分,不应该从订单下面更改.因此,地址没有自己的生命周期,将其作为一个单独的实体处理只会导致混淆.

"规范化"特指从数据中删除冗余,因此您没有在不同位置表示相同的项目.这里唯一的冗余是在DDL中,它不在数据中,因此"规范化"与此无关.