是否值得将地址信息分成单独的数据库表?

时间:2013-06-21 01:04:42

标签: database database-design database-normalization

我有一个名为“Person”的表,其中包含以下字段

  • Id(主键)
  • 名字
  • DATEOFBIRTH
  • 国家
  • 国家

如果像City,State或Country这样的东西被规范化并分解到他们自己的表中,那么这个表有CityId和StateId列。我们正在讨论这是一个好的还是坏的决定。

要添加,我确实有一个City和一个State表(由于与此person表无关的其他原因)。无论有没有这个额外的事实,我都很好奇。

10 个答案:

答案 0 :(得分:23)

将地址规范化为层次结构是一个值得怀疑的命题。这实际上取决于您对地址数据的意义。

规范化以避免更新异常的想法有点可疑。城市,州或国家实际更改名称的频率如何?此外,如果发生这种情况,这种变化有多大可能是批发的? (即旧名称X的每个实例都更改为新名称Y)。我可以告诉你在加拿大的实践中发生了什么,当时在2000年发生了一系列市政合并,重新划定了边界,并且许多旧名称被困在周围,只有比以前更小的领土。

事实上,像市政名称这样的东西可以松散地定义。例如,在我长大的地方,根据邮政当局的说法,我的地址有三个官方认可的市政名称:WILLOWDALE,NORTH YORK,TORONTO - 所有这些都是有效的选择,尽管其中一个比其他人更“官方”。问题是所有Willowdale都在北约克,但北约克也包含“Downsview”等。

规范地址的其他常见理由包括:确保正确拼写并为领土管理提供依据。鉴于地址数据质量的变幻莫测,这些论点并不令人信服。

确保地址数据质量的最佳方法是将地址保持在相对平坦,相对简单的结构中,并使用一个或多个地址质量工具,使用邮政授权数据来匹配和标准化您的地址。通过各种方式将城市,州和邮政编码保存在自己的字段中,但不要将它们保存在不同的表中。这实际上比标准化结构更灵活,同时总体上产生更可靠的结果。

同样,区域管理最好在比市政当局更精细的层面上进行。一些城市是巨大的,名称可能含糊不清。而是使用邮政编码或ZIP + 4(取决于司法管辖区)。这更加细化和明确。同样,地址数据质量工具将确保您对地址进行正确的邮政编码。

答案 1 :(得分:9)

根据我的经验,是的。

1城市,州和国家是现实世界中的实体,因此最好将它们作为数据库模型中的实体。它使名称保持一致,正如其他回答者已经提到的那样

2您可以填充它们并从外部开源或标准组织验证它们。例如,对于国家而言,它是国际标准ISO3166

3在您当前或未来版本的应用中,您甚至可以直接连接到外部来源进行维护。

4如果您使用多种语言,您将拥有可在一个地方翻译的名称

5如果您与其他方或应用交换或界面数据,则需要通用分类

答案 2 :(得分:3)

在开始之前,我想指出{city,state,country}不是地址。

  

像城市,州或国家这样的事情应该正常化和破坏   进入他们自己的表然后这个表有CityId和StateId   列。我们正在讨论这是好事还是坏事   决定。

标准化很好。我几乎总是提倡正常化。

但是使用ID号而不是文本 nothing 与规范化有关。用“CityId”代替“City”和“StateId”代替“State”对表格的正常形式没有影响。如果在更改之前它在3NF,那么在更改之后它仍将是3NF。

您可以使用外键引用增加数据完整性。数据完整性也很好。但是,与许多其他数据库设计决策一样,这与规范化没有任何关系。

提高城市数据完整性的最简单方法是将不同的城市选择为新表。 (PostgreSQL语法。)

select distinct city, state, country
into new_table
from person;

您需要城市,州和国家来代表城市的“全名”。你还需要一把钥匙。

alter table new_table
add primary key (city, state, country);

现在您可以声明一个外键约束,以保证{city,state,country}始终引用该新表中的一行。

alter table Person
add constraint city_state_country_from_new_table
foreign key (city, state, country)
references new_table (city, state, country)
on update cascade;

我不担心这种表的级联更新的性能。 (除非我使用的是Oracle; Oracle不支持级联更新。)这些类型的名称很少变化,我知道PostgreSQL可以在不到3秒的时间内在桌面上的5000万行表中将更新级联到300万行。我的桌面并不特别,它运行着3个数据库管理系统和2个Web服务器。如果我有更大的桌子并需要更多时间,我会在维护窗口安排更改。

您可以以相同的方式提高状态的数据完整性。

select distinct state, country
into another_new_table
from new_table;
etc., etc.

说了这么多,为new_table添加一个代理键是一个可辩护的设计决定,但前提是你花了一些时间思考它。 (不思考永远不会是可辩护的。)

使用代理键替换{city,state,country}的最直接影响是,您现在需要在表中的每个查询中加入一个需要 no 加入的表。您可以使用随机样本数据测试对性能的影响。在你拥有数百万行之前,你可能会发现自然键比代理键上的连接更快。这就是我测试时发现的。

答案 3 :(得分:2)

是的,几乎可以肯定。如果国家或城市更改名称,您可以在一个位置更改名称,并自动更新所有引用。

拆分还允许您将其他属性添加到国家或城市,即其所在的大陆等。如果没有单独的表格,您将无法轻松完成此操作。

最后,如果您想要一个国家/地区列表(例如填充一个列表框),您可以选择一个地方。 (否则你最终会从你的人员表中做一些SELECT DISTINCT,这是可疑的。)

答案 4 :(得分:2)

我会考虑将城市,州和国家分成一个“地址(或城市)”表,其中包含跨行复制的州和国家/地区。对于世界上唯一城市的数量,这不是真正的数据库查询成本。

它还取决于您期望拥有的记录数量 - 如果总人数总是小于100,000,那么归类化数据真的值得吗?

拥有扁平的数据结构使查询和测试变得更加简单,因此除非存在性能或磁盘空间问题,否则最好“保持简单”。

答案 5 :(得分:1)

  • 如果这是一个相对较小的数据库并且您打算让用户自己输入地址,那么您应该让表格保持原样。即使这会增加表格大小(以字节为单位),因为城市,州和国家名称的重复存储。

  • 如果这是一个相对庞大的数据库,并且您希望用户从列表中选择城市,州和国家/地区名称,则需要将此列分隔到另一个表。此外,为此,您必须自己填充此表。优点是用户和地址的表格也较小。

答案 6 :(得分:1)

这取决于您从哪里获取城市,州和国家的数据。

如果您的应用程序允许用户输入这些信息但强制他们从使用您的主数据填充的下拉列表中选择这些值,则最好将这三个字段折叠为“locationId”和有一个存储记录的表(city_id,state_id,country_id)。您在Person表中不需要这三个ID,因为组合很少会发生变化。

相反,如果您允许用户键入城市,州和国家/地区的值,那么,由于同一城市/州的拼写不同的值,将它们分成单独的表会变得棘手/国家

答案 7 :(得分:0)

我认为规范化的程度确实取决于应用程序的大小。至少我至少会有一个地址表,以便可以在地址上执行CRUD而无需耦合到用户。如果UI中有计划列出城市或州或提供Web服务的下拉列表,您可能希望更多地分解它。如果你需要考虑外部地址和APO/FPO,它会变得有点复杂。维基百科页面上列出的objectives of normalization可能值得一试,看看是否应该在项目中考虑任何方案。在没有过度设计的情况下,尽量不要重复数据或工作。

我想提供一些您的团队可能会考虑的其他信息:

Luke W.有一些关于为地址设计UI的很好的信息。

如果您通过网络部署,则有许多Web服务API已经在管理位置数据。

如果需要在内部维护数据,或者您不喜欢依赖外部服务,请使用open data来源之一GeoNames。数据是一个制表符分隔的文本文件,但可以使用脚本轻松解析以自动加载数据。

答案 8 :(得分:0)

{country,state,city}的问题在于它们出现作为引用表的候选键。在SQL中,{country,state,city} 不能成为候选键(甚至是主键),如果状态(或国家/地区)可能丢失或为NULL。 (这可以通过为它们允许一个空字符串来避免,这与NULL不同,但这将是一个丑陋的黑客,IMO)同样适用于zipcode,只能通过添加{{1来制作候选键它。两者都可能丢失,未知或为空。

围绕残缺候选键的唯一方法是将它们降级为(非唯一)索引,并添加代理主键,如:

country

WRT CREATE TABLE cities ( city_id INTEGER NOT NULL PRIMARY KEY -- could be a serial ... , country_name varchar -- you _could_ squeeze this out into a separate "countries" table , state_name varchar -- you could even squeeze this out, but it would need a composite FK , city_name varchar NOT NULL ); CREATE TABLE adresses ( person_id INTEGER NOT NULL PRIMARY KEY -- could be a serial , last_name varchar NOT NULL , first_first_name varchar , gender CHAR(1) , dob DATE , city_id INTEGER references cities(city_id) -- could be NOT NULL ); :你可以将这些挤出到一个联结表中(这基本上是一个BCNF问题,如果所有连接字段都是非NULL的话,甚至可能是4NF问题)喜欢在:

{city,state}

你是否真的应该这样做是一个品味问题(参见@Joel Brown的回答)。在大规模重命名操作的情况下,规范化肯定会有所帮助,例如在OQ中合并市政当局。对于小型地址集(最多可能有几千个),额外的复杂性可能会比它获得的成本更高。 对于用于维护数据的前端应用程序而言,这种复杂性特别昂贵。对于DBMS,一些连接不会花费那么多(对于小尺寸),甚至可以帮助性能(对于更大的尺寸)。规范化对性能也不错。

更新(在Mike Sherill catcall的评论之后):

如果我们可以对{country,state,city}(或其他ID)施加 -- -- Plan B: -- CREATE TABLE country2 ( country_id INTEGER NOT NULL PRIMARY KEY -- could be a serial ... , country_name varchar NOT NULL , country_iso varchar -- ... , UNIQUE (country_name) ); CREATE TABLE country_state2 ( cs_id INTEGER NOT NULL PRIMARY KEY -- could be a serial ... , country_id INTEGER NOT NULL REFERENCES country2(country_id) , state_name varchar ); CREATE TABLE cities2 ( city_id INTEGER NOT NULL PRIMARY KEY -- could be a serial ... , cs_id INTEGER REFERENCES country_state2(cs_id) , city_name varchar NOT NULL ); CREATE TABLE adresses2 ( person_id INTEGER NOT NULL PRIMARY KEY -- could be a serial , last_name varchar NOT NULL , first_first_name varchar , gender CHAR(1) , dob DATE , city_id INTEGER references cities2(city_id) -- could be NOT NULL ); 限制, 我们还可以对它们所属的(复合)候选键强加UNIQUE约束:          -          - 计划C:          -     CREATE TABLE country3         (country_id INTEGER NOT NULL PRIMARY KEY - 可能是一个序列...         ,country_name varchar NOT NULL         ,country_iso varchar         ,UNIQUE(country_name)         );

NOT NULL

虽然这个CREATE TABLE country_state3 ( cs_id INTEGER NOT NULL PRIMARY KEY -- could be a serial ... , country_id INTEGER NOT NULL REFERENCES country3(country_id) , state_name varchar NOT NULL , UNIQUE (country_id,state_name) ); CREATE TABLE cities3 ( city_id INTEGER NOT NULL PRIMARY KEY -- could be a serial ... , cs_id INTEGER NOT NULL REFERENCES country_state3(cs_id) , city_name varchar NOT NULL , UNIQUE (cs_id,city_name) ); CREATE TABLE adresses3 ( person_id INTEGER NOT NULL PRIMARY KEY -- could be a serial , last_name varchar NOT NULL , first_first_name varchar , gender CHAR(1) , dob DATE -- allowing NULL here allows for 'embryonic' records without city/state/country info. , city_id INTEGER references cities3(city_id) ); 约束可以避免{city,state,country}中的重复,但显然还会强加它们为NOT NULL。这在其他国家(比加拿大或美国)可能是不可能或无效的。在荷兰,我们没有NOT NULLstate;我们确实有county,很难使用(仅在需要时消除歧义)类似于法语provincie,IIRC。

答案 9 :(得分:0)

我会说是,但仅限于城市/州/国家/地区,除非您计划按名称分析/分组人员。

在结果的id列上创建索引,并在查找表的文本列上创建索引。这样可以更轻松地创建表单的下拉选项,并在数据库大小增加时更快地查找时间。

如果要将城市/州/国家/地区列编入索引,这也会加快记录写入时间,因为短数字索引写入比全文索引快得多。