我有一个名为“Person”的表,其中包含以下字段
- ID(主键)
- 名
- 姓
- 出生日期
- 城市
- 状态
- 国家
如果像 City、State 或 Country 这样的东西被规范化并分解成它们自己的表,那么这个表就有 CityId 和 StateId 列。我们正在辩论这是一个好还是坏的决定。
补充一下,我确实有一个 City 和 State 表(出于与此 person 表无关的其他原因)。无论有没有这个额外的事实,我都对答案感到好奇。
我有一个名为“Person”的表,其中包含以下字段
如果像 City、State 或 Country 这样的东西被规范化并分解成它们自己的表,那么这个表就有 CityId 和 StateId 列。我们正在辩论这是一个好还是坏的决定。
补充一下,我确实有一个 City 和 State 表(出于与此 person 表无关的其他原因)。无论有没有这个额外的事实,我都对答案感到好奇。
将地址规范化为层次结构是一个有问题的命题。这实际上取决于您对地址数据的处理方式。
规范化以避免更新异常的想法有点可疑。城市、州或国家实际上多久更名一次?此外,如果发生这种情况,大规模改变的可能性有多大?(即旧名称 X 的每个实例都更改为新名称 Y)。我可以告诉你,当 2000 年代发生一系列市政合并时,加拿大实践中发生的事情是重新划定了边界,许多旧名称仍然存在,只是领土比以前更小。
事实是,诸如市镇名称之类的东西可以松散地定义。例如,在我长大的地方,我的地址有三个根据邮政局官方认可的城市名称:WILLOWDALE、NORTH YORK、TORONTO——所有这些都是有效的选项,尽管其中一个比其他的“更官方”。问题是整个 Willowdale 都在北约克,但北约克还包含“Downsview”等。
规范化地址的其他常见论点包括:确保正确的拼写和为区域管理提供基础。鉴于地址数据质量的变幻莫测,这些论点并不令人信服。
确保地址数据质量的最佳方法是将您的地址保持在一个相对平坦、相对简单的结构中,并使用一个或多个地址质量工具,这些工具使用邮政当局数据来匹配和标准化您的地址。 务必将城市、州和邮政编码保存在各自的字段中,但不要将它们保存在不同的表格中。这实际上比标准化结构更灵活,同时总体上产生更可靠的结果。
同样,领土管理最好在比市政当局更细化的层面上进行。一些自治市规模庞大,名称可能含糊不清。而是使用邮政编码或 ZIP+4(取决于司法管辖区)。这更加细化和明确。同样,地址数据质量工具将确保您的地址具有正确的邮政编码。
根据我的经验,是的。
1 城市、州和国家是现实世界中的实体,因此最好将它们作为数据库模型中的实体。正如其他回答者已经提到的那样,它使名称保持一致
2 您可以填充它们并从外部开源或标准机构验证它们。例如,对于国家,它是国际标准 ISO3166
3 在您当前或未来版本的应用程序中,您甚至可以直接连接到外部资源来维护它们。
4 如果您曾经使用多种语言,您将已经拥有可以在一个地方翻译所有名称的名称
5 如果您曾经与其他方或应用程序交换或接口数据,您将需要通用分类
在开始之前,我想指出 {city, state, country} 不是地址。
如果像 City、State 或 Country 这样的东西被规范化并分解成它们自己的表,那么这个表就有 CityId 和 StateId 列。我们正在辩论这是一个好还是坏的决定。
标准化很好。我几乎总是提倡规范化。
但是使用 ID 号而不是文本与规范化无关。将“City”替换为“CityId”,将“State”替换为“StateId”对表格的正常形式没有影响。如果它在该更改之前处于 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 个数据库管理系统和两个 Web 服务器。如果我有更大的桌子并且需要更多时间,我会在维护窗口期间安排更改。
您可以以相同的方式提高状态的数据完整性。
select distinct state, country
into another_new_table
from new_table;
etc., etc.
说了这么多,向 new_table 添加代理键是一个合理的设计决策,但前提是您要花一些时间考虑一下。(不思考永远是站不住脚的。)
将 {city, state, country} 替换为代理键的最直接效果是,您现在需要在以前不需要连接的表的每个查询中进行连接。您可以使用随机样本数据测试对性能的影响。在您拥有数百万行之前,您可能会发现自然键比代理键上的连接要快。这是我测试时发现的。
我会考虑将城市、州和国家分解为一个“地址(或城市)”表,其中包含跨行复制的州和国家。对于世界上独特城市的数量,这并不是真正的数据库查询成本。
它还取决于您期望拥有的记录数量 - 如果总人数总是少于 100,000 人,那么对数据进行标准化真的值得吗?
拥有扁平的数据结构使查询和测试变得非常简单,因此除非存在性能或磁盘空间问题,否则最好“保持简单”。
是的,几乎可以肯定。如果一个国家或城市更改了名称,您只需在一个地方进行更改,所有参考资料都会自动更新。
拆分还允许您将其他属性添加到国家或城市,即它所在的大陆等。如果没有单独的表格,您将无法轻松做到这一点。
最后,如果您想要一个国家/地区列表(例如填充列表框),您只有一个可以参考的地方。(否则你最终会从你的个人表中做一些 SELECT DISTINCT ,这是可疑的。)
如果这是一个相对较小的数据库,并且您打算让用户自己输入地址,您应该让表格保持原样。即使这会增加表大小(以字节为单位),因为城市、州和国家名称的重复存储。
如果这将是一个相对庞大的数据库,并且您希望用户从列表中选择城市、州和国家/地区名称,那么您需要将此列分隔到另一个表中。此外,要使其正常工作,您必须自己填充此表。优点是用户和地址的表格更小。
这取决于您从哪里获得城市、州和国家/地区的数据。
如果您的应用程序允许用户输入这些信息,但强制他们从使用您的主数据填充的下拉列表中选择这些值,那么最好将这三个字段折叠为“locationId”之类的内容并拥有一个表格其中存储了记录(city_id、state_id、country_id)。您不需要在 Person 表中使用这三个 id,因为组合很少会改变。
相反,如果您允许您的用户键入 City、State 和 Country 的值,那么由于同一城市/州/国家/地区的拼写不同,将它们分开到单独的表中会变得很棘手。
{country,state,city} 的问题在于它们似乎是引用表的候选键。在 SQL 中,如果state(或 country)可能缺失或为 NULL ,则{country,state,city} 不能是候选键(甚至是主键) 。(这可以通过为它们允许一个空字符串来避免,这与 NULL 不同,但这将是一个丑陋的黑客,IMO)同样适用于邮政编码,它只能通过添加它来成为候选键。两者都可能丢失、未知或 NULL。country
绕过残缺的候选键的唯一方法是将它们降级为(非唯一)索引,并添加一个代理主键,如:
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
);
WRT {city,state}
:您可以将它们挤出到联结表中(这基本上是一个 BCNF 问题,甚至可能是 4NF 问题,如果所有连接字段都是非 NULLABLE 的话),例如:
--
-- 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
);
您是否真的应该这样做是一个品味问题(请参阅@Joel Brown 的回答)。在大规模重命名操作的情况下,正常化肯定会有所帮助,例如合并 OQ 中的城市。对于小型地址集(最多可能几千个),额外的复杂性可能会花费更多而不是获得。这种复杂性对于用于维护数据的前端应用程序来说尤其昂贵。对于 DBMS,一些连接不会花费太多(对于小尺寸),甚至可以提高性能(对于较大尺寸)。规范化对性能来说还不错。
更新(在 Mike Sherill catcall 的评论之后):
如果我们可以对NOT NULL
{country,state,city}(或那里的 id)施加约束,我们还可以对它们所属的(复合)候选键施加 UNIQUE 约束: -- -- 计划 C: -- CREATE TABLE country3 ( country_id INTEGER NOT NULL PRIMARY KEY - 可以是一个序列..., country_name varchar NOT NULL , country_iso varchar , UNIQUE (country_name) );
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)
);
尽管此NOT NULL
约束将避免 {city,state,country} 中的重复项,但显然它也会将它们强制为 NOT NULL。这在其他国家(除了加拿大或美国)可能是不可能或无效的。在荷兰,我们没有state
or county
; 我们确实有provincie
,几乎不使用(仅在需要时消除歧义) 与法语departements
,IIRC 类似。
我会说是的,但仅适用于城市/州/国家,除非您打算按姓名对人进行分析/分组。
在产生的 id 列以及查找表中的文本列上创建索引。当您的数据库大小增加时,这将导致更轻松地创建表单的下拉选项并加快查找时间。
如果您正在索引城市/州/国家列,这也将加快记录写入时间,因为短数字索引写入比全文索引快得多。
我认为规范化水平确实取决于应用程序的规模。至少我至少会有一个地址表,以便可以在地址上执行 CRUD 而不会与用户耦合。如果 UI 中有计划在下拉列表中列出城市或州或提供 Web 服务,您可能希望对其进行更多分解。如果您需要考虑外国地址和APO/FPO,它会变得有点复杂。维基百科页面上列出的标准化目标可能值得一试,看看是否应该在您的项目中考虑任何场景。在不过度设计的情况下,尽量不要重复数据或努力。
我想提供一些您的团队可能会考虑的其他信息:
Luke W. 有一些关于为地址设计 UI 的重要信息。
如果您通过 Web 进行部署,则有许多 Web 服务 API 已经在管理位置数据。
如果需要在内部维护数据,或者您不喜欢依赖外部服务,请使用开放数据源之一,例如GeoNames。数据是一个制表符分隔的文本文件,但可以很容易地用脚本解析以自动加载数据。