21

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

  • ID(主键)
  • 出生日期
  • 城市
  • 状态
  • 国家

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

补充一下,我确实有一个 City 和 State 表(出于与此 person 表无关的其他原因)。无论有没有这个额外的事实,我都对答案感到好奇。

4

10 回答 10

24

将地址规范化为层次结构是一个有问题的命题。这实际上取决于您对地址数据的处理方式。

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

事实是,诸如市镇名称之类的东西可以松散地定义。例如,在我长大的地方,我的地址有三个根据邮政局官方认可的城市名称:WILLOWDALE、NORTH YORK、TORONTO——所有这些都是有效的选项,尽管其中一个比其他的“更官方”。问题是整个 Willowdale 都在北约克,但北约克还包含“Downsview”等。

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

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

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

于 2013-06-21T11:13:11.757 回答
9

根据我的经验,是的。

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

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

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

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

5 如果您曾经与其他方或应用程序交换或接口数据,您将需要通用分类

于 2013-06-24T04:53:18.173 回答
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} 替换为代理键的最直接效果是,您现在需要在以前不需要连接的表的每个查询中进行连接。您可以使用随机样本数据测试对性能的影响。在您拥有数百万行之前,您可能会发现自然键比代理键上的连接要快。这是我测试时发现的。

于 2013-06-29T10:25:24.113 回答
2

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

它还取决于您期望拥有的记录数量 - 如果总人数总是少于 100,000 人,那么对数据进行标准化真的值得吗?

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

于 2013-06-24T14:06:04.817 回答
2

是的,几乎可以肯定。如果一个国家或城市更改了名称,您只需在一个地方进行更改,所有参考资料都会自动更新。

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

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

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

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

于 2013-06-28T09:12:48.193 回答
1

这取决于您从哪里获得城市、州和国家/地区的数据。

如果您的应用程序允许用户输入这些信息,但强制他们从使用您的主数据填充的下拉列表中选择这些值,那么最好将这三个字段折叠为“locationId”之类的内容并拥有一个表格其中存储了记录(city_id、state_id、country_id)。您不需要在 Person 表中使用这三个 id,因为组合很少会改变。

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

于 2013-06-29T09:32:47.523 回答
1

{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。这在其他国家(除了加拿大或美国)可能是不可能或无效的。在荷兰,我们没有stateor county; 我们确实有provincie,几乎不使用(仅在需要时消除歧义) 与法语departements,IIRC 类似。

于 2013-06-29T11:59:31.613 回答
0

我会说是的,但仅适用于城市/州/国家,除非您打算按姓名对人进行分析/分组。

在产生的 id 列以及查找表中的文本列上创建索引。当您的数据库大小增加时,这将导致更轻松地创建表单的下拉选项并加快查找时间。

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

于 2013-06-30T23:14:50.583 回答
0

我认为规范化水平确实取决于应用程序的规模。至少我至少会有一个地址表,以便可以在地址上执行 CRUD 而不会与用户耦合。如果 UI 中有计划在下拉列表中列出城市或州或提供 Web 服务,您可能希望对其进行更多分解。如果您需要考虑外国地址和APO/FPO,它会变得有点复杂。维基百科页面上列出的标准化目标可能值得一试,看看是否应该在您的项目中考虑任何场景。在不过度设计的情况下,尽量不要重复数据或努力。

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

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

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

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

于 2013-06-28T14:40:35.513 回答