数据库设计:复合键与一列主键

时间:2009-08-12 04:42:58

标签: mysql database-design primary-key normalization composite-key

我正在处理的Web应用程序遇到了意外的“错误” - 应用程序的数据库有两个表(在许多其他表中)称为“States”和“Cities”。

''表格字段:

-------------------------------------------
idStates   |   State   |   Lat   |   Long
-------------------------------------------

' idStates '是一个自动递增的主键。

'城市'表格字段:

----------------------------------------------------------
idAreaCode   |   idStates   |   City   |   Lat   |   Long
----------------------------------------------------------

' idAreaCode '是由国家代码+区号组成的主键(例如91422,其中91是印度的国家代码,422是印度城市的区号)。 ' idStates '是从“ States ”表派生的外键,用于将“ Cities ”表中的每个城市与其对应的State相关联。

我们认为国家代码+区号组合对于每个城市都是唯一的,因此可以安全地用作主键。一切都在运作。但是印度的一个地方在数据库设计中发现了意想不到的“缺陷” - 印度就像美国是联邦民主国家一样,在地理上分为许多州或联邦地区。状态和联合区域数据都存储在“ States ”表中。但是,有一个位置 - Chandigarh - 属于两个州( Haryana Punjab ),并且本身也是一个联盟领域。

显然,目前的数据库设计不允许我们存储多个城市' Chandigarh '的记录。

建议的解决方案之一是创建一个组合列' idAreaCode '和' idStates '的主键。

我想知道这是否是最好的解决方案?

(仅供参考:我们正在使用带有InnoDB引擎的MySQL)。


更多信息:

  • 数据库存储每个城市的气象信息。因此,州和城市是每个查询的起点。
  • 每天使用CSV文件插入每个城市的新鲜数据。 CSV文件包含idStates(用于state)和idAreaCode(用于city)列,用于标识每条记录。
  • 数据库规范化对我们很重要。

注意:不对城市表使用自动递增主键的原因是数据库每天/每小时使用CSV文件(由另一个应用程序生成)进行更新。 CSV文件中的每条记录都由idStates和idAreaCode列标识。因此,优选的是,城市表中使用的主键对于每个城市是相同的,即使该表被删除并再次刷新。邮政编码(或密码)和区号(或STD代码)符合唯一,静态(不经常更改)的标准,并且可以轻松获得这些标准。 (我们现在决定区号,因为印度正在将其密码更新为新格式。)

  

解决方案我们决定在应用程序级别处理此问题,而不是更改数据库设计。在数据库中,我们只存储一个'Chandigarh'的记录。在应用程序中,我们为任何搜索'Chandigarh,Punjab'或'Chandigarh,Haryana'创建了一个标志,以将搜索重定向到此记录。是的,这不是理想的,但是可接受的折衷方案,因为这是迄今为止我们遇到的唯一例外。

8 个答案:

答案 0 :(得分:4)

听起来您正在收集电话簿的数据。你是?为什么州对你很重要?这个问题的答案可能会决定哪种数据库设计最适合您。

你可能会认为城市是显而易见的。不是。这取决于您将如何处理数据。在美国,有一个名为MSA(大都市统计区)的单位。堪萨斯城MSA横跨堪萨斯城堪萨斯城和密苏里州堪萨斯城。 MSA单元是否有意义取决于数据的预期用途。 如果您在美国使用区号来确定城市,那么您最终会得到与MSA截然不同的分组。同样,这取决于您将如何处理数据。

一般情况下,只要政治分支的等级模式破裂,最通用的解决方案就是考虑多对多关系。您解决此问题的方法与解决其他多对多问题的方法相同。通过创建一个带有两个外键的新表。在这种情况下,外键是IdAreacode和IdStates。

现在,您可以在许多州拥有一个iscode,在一个州中拥有多个区域代码。把这个额外的开销用于覆盖一个例外似乎是一种耻辱。你知道你发现的例外是否只是冰山一角,还有很多这样的例外吗?

答案 1 :(得分:2)

如果要引用该表,则使用复合键可能会出现问题,因为引用表必须包含主键所具有的所有列。

如果是这种情况,您可能希望拥有序列主键,并在UNIQUE NOT NULL组中定义idAreaCode和idStates。

答案 2 :(得分:2)

我认为最好添加另一个表格,国家/地区。您的问题是数据库规范化很重要的一个例子。您不能只将不同的键混合和匹配到一列。

所以,我建议你创建这些表:

国家:

+------------+--------------+
| country_id | country_name |
+------------+--------------+

状态:

+------------+----------+------------+
| country_id | state_id | state_name |
+------------+----------+------------+

城市

+------------+----------+---------+-----------+
| country_id | state_id | city_id | city_name |
+------------+----------+---------+-----------+

数据

+------------+----------+---------+---------+----------+
| country_id | state_id | city_id | data_id | your_CSV |
+------------+----------+---------+---------+----------+

粗体字段是主键。输入标准country_id,例如美国为1,印度为91,依此类推。 city_id也应该使用他们的标准ID。

然后,您可以以最小的开销快速找到彼此属于的任何内容。然后,所有数据都可以直接输入到数据表中,从而作为一个入口点,将所有数据存储到单个点。我不知道使用mysql,但是如果你的数据库支持分区,你可以根据country_id或country_id + state_id将数据表分区到几个服务器阵列,这样它也会大大加快你的数据库性能。第一个,第二个和第三个表根本不会对服务器负载造成太大影响,仅作为参考。您将主要处理第四个数据表。您可以根据需要添加数据,而不会再次重复。

如果每个城市只有一个数据,则可以省略数据表并将CSV_data移动到城市表中,如下所示:

城市

+------------+----------+---------+-----------+----------+
| country_id | state_id | city_id | city_name | CSV_data |
+------------+----------+---------+-----------+----------+

答案 3 :(得分:1)

如果您要在密钥中添加其他列,以便可以为给定城市添加其他记录,那么您就无法正常规范化数据。鉴于您现在已经发现一个城市可以是多个州的成员,我建议从Cities表中删除对州的任何引用,然后添加一个StateCity表,允许您将州与城市相关联(创建am:m关系)。

答案 4 :(得分:1)

介绍代理密钥。当区号改变numbets或分裂时你打算做什么?使用业务密钥作为主键几乎总是错误的。

您的上述摘要是另一个原因。

答案 5 :(得分:1)

  

“我们认为国家代码+区号组合对每个城市都是唯一的,因此可以安全地用作主键”

阅读完本文后,我就停下来阅读本主题中的任何内容。 怎么会有人这样想呢? 区域代码,根据定义(我在互联网上找到的第一个):
   - “区号是用于根据北美号码计划识别地理区域的前缀号码。这个3位数字可以分配给北美的任何号码,包括加拿大,美国,墨西哥,拉丁美洲和加勒比地区“[1]

暂且不论它们是可更改的并且仅在北美定义,在其他一些国家/地区的区号不是3位数(在一些国家/地区,3位数字仅仅有数十万个位置.BTW,我母亲的区域代码有5位数)并且它们没有严格链接到固定的地理位置。

区号有迁徙的地方,如漂流的冰北极营地,游牧部落,迁徙的军事单位,甚至是大型海洋船只等。

那么,将几个城市合并为一个城市(反之亦然)呢?

[1]
http://www.successfuloffice.com/articles/answering-service-glossary-area-code.htm

答案 6 :(得分:0)

我建议在Cities表中添加一个新的主键字段,它只是自动增量。 KISS方法(保持简单)。

在我看来,任何其他解决方案都很麻烦且令人困惑。

答案 7 :(得分:0)

  1. 数据库未规范化。它可能部分归一化。因此,您会发现更多可扩展性方面的漏洞和限制。

  2. Country,State然后City的层次结构很好。有些人建议您不需要多对多的附加表。这个城市(以及美国的许多城市)在三个州繁衍。

  3. 通过将CountryCode和AreaCode连接在一个列中,您已经破坏了基本数据库规则,更不用说每次访问时都添加了代码。此外,CountryCode未规范化。

  4. 问题在于CountryCode + AreaCode是城市密钥的不良选择。实际上,它与城市关系不大,适用于大片土地。如果城市的意义改为城镇(例如,您的公司开始收集大城镇的数据),数据库将完全破坏。

  5. 魔术师的唯一答案是接近正确,这样可以避免由于缺乏规范化而导致的当前限制。说魔术师的答案是标准化是不准确的;它是标识符的正确选择,在这种情况下形成层次结构。但我会删除“id”列,因为它们是不必要的,100%冗余列,100%冗余索引。 char()列虽然很好,但对于PK(复合键)来说很好。请记住,无论如何都需要char()列上的索引,以确保它是唯一的。

    • 如果你有这个,带有关系标识符的Relational结构,你的问题将不存在。
    • 并且您的贫困用户不必弄清楚愚蠢的事情或跟踪无意义的标识符。他们只是陈述,自然:State.Name,City.Name,ReadingType,Data ... 。
  6. 当你到达层次结构(城市)的下端时,复合PK变得繁重(3 x CHAR(20)),我不想将它带入数据表(特别是如果每天都有CSV导入,每个城市有很多读数或行数)。因此,对于City,我会添加一个代理键,如PK。

  7. 但是对于发布的DDL,即使它没有规范化数据库并使用关系标识符,是的,城市的PK是不正确的。它应该是(idStates,idAreaCode),而不是相反。这将解决您的问题。

  8. 顺便说一句非常糟糕的命名。