2011-07-11 32 views
18

我需要表示一些事件的位置,我正在为此应用程序设计数据库模式。 我具备呈现位置两种方法:数据库模式 - 表示位置

方法1: 4个表:

  • 国家
  • 美国
  • 城市
  • 位置(在位置我有外键country_id,state_id和city_id)

方法2: 1表:

  • 位置,并有简单的领域国家,州,城市,存储为文本(没有外国的ID)

你会推荐哪一种方法?第一个将有助于消除可能的不同名称,例如,同一个国家(美国,美国,美国等),并可能有助于在文本框中写入时提供建议,这可能是强制性的。

但是,第二种方法似乎可以让所有事情变得简单,并且应该减少对数据库的查询次数。

你认为哪一个更好?你知道这种情况下的最佳做法吗?例如。它是如何做一些大门户的,他们也需要类似位置的东西(例如foursquare等)。 Afaik的facebook使用第二种方法,但是...我想听听你的意见和可能的原因,为什么你会选择另一种方法。

谢谢!

+0

有什么建议吗? – Bart

+0

什么引擎? MySQL的?甲骨文? DB9? SqlLite? –

+0

会很重要吗?如果是这样,MySQL,但是如果你能指出在例如MySQL中会有什么不同。甲骨文,这可能也有帮助... – Bart

回答

17

方法1:

这是一个很好的解决方案,如果你想有一个良好的normalized database。您可以轻松管理所有表格,但在查询位置时必须有3次左/内连接。我假设所有事情都被正确编制索引,因此这些表格对于城市来说相对较小(国家和州)和中等规模(如果您只希望所有城市仅适用于某个特定国家/地区),您将不会遇到真正的麻烦。如果你想要世界上所有的城市,那么这个表格将是巨大的,如果你没有正确地建立索引或者加入表格,你可能会在某个时候出现性能问题。

因为一切都在数据库中,所以如果您需要添加,更新或删除记录,则不必更改代码。

如果您需要添加,更新或删除任何记录,此解决方案将非常易于维护。如果您需要更新名称(例如城市名称)并且所有记录将一次更新。

如果您按城市或州看起来速度快,查询运行速度会更快,然后通过简单的左连接获取名称就可以实现。

方法2:

我个人不会推荐,因为可维护性它不是最好的解决办法。如果有一天您需要检索基于城市的数据,那么如果您的索引不正确,则查询可能执行缓慢。如果你为国家,州和城市建立索引,那么查找速度会更快(但比第一种方法要慢),因为varchar比索引的int要慢。此外,你增加了名称错误的风险,例如:纽约VS纽约VS新约克。

此外,如果您需要更新城市名称,则必须检索具有该名称的所有记录,然后更新所有这些记录。这可能需要很长时间。

例如:UPDATE locations SET city ='New York'where city ='newyork'; *注:另外,如果你有拼错,你必须验证所有的记录,以确保您更新所有记录

下面是根据您的需要(使用MYSQL)进场#1的骨架:

CREATE TABLE `countries` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(200) NOT NULL DEFAULT '', 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

CREATE TABLE `states` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(200) NOT NULL DEFAULT '', 
    `fk_country_id` int(10) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`id`), 
    KEY `fk_country_id` (`fk_country_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

CREATE TABLE `cities` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(200) NOT NULL DEFAULT '', 
    `fk_state_id` int(10) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`id`), 
    KEY `fk_state_id` (`fk_state_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

CREATE TABLE `locations` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(200) NOT NULL DEFAULT '', 
    `fk_country_id` int(10) NOT NULL DEFAULT '0', 
    `fk_state_id` int(10) NOT NULL DEFAULT '0', 
    `fk_cities_id` int(10) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`id`), 
    KEY `fk_country_id` (`fk_country_id`), 
    KEY `fk_state_id` (`fk_state_id`), 
    KEY `fk_cities_id` (`fk_state_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

/* This table should not have fk_country_id and fk_state_id since they are already in their respective tables. but for this requirement I will not remove them from the table */ 

SELECT locations.name AS location, cities.name AS city, states.name AS state, countries.name AS country from locations INNER JOIN cities ON (cities.id = fk_cities_id) INNER JOIN states ON (states.id = locations.fk_state_id) INNER JOIN countries ON (countries.id = locations.fk_country_id); 
+-------------------+---------------+----------+---------------+ 
| location   | cty   | state | country  | 
+-------------------+---------------+----------+---------------+ 
| Statue of Liberty | New York City | New York | United States | 
+-------------------+---------------+----------+---------------+ 
1 row in set (0.00 sec) 

EXPLAIN: 
+----+-------------+-----------+--------+----------------------------------------+---------+---------+-------+------+-------+ 
| id | select_type | table  | type | possible_keys       | key  | key_len | ref | rows | Extra | 
+----+-------------+-----------+--------+----------------------------------------+---------+---------+-------+------+-------+ 
| 1 | SIMPLE  | locations | system | fk_country_id,fk_state_id,fk_cities_id | NULL | NULL | NULL | 7174 |  | 
| 1 | SIMPLE  | cities | const | PRIMARY        | PRIMARY | 4  | const | 1 |  | 
| 1 | SIMPLE  | states | const | PRIMARY        | PRIMARY | 4  | const | 1 |  | 
| 1 | SIMPLE  | countries | const | PRIMARY        | PRIMARY | 4  | const | 1 |  | 
+----+-------------+-----------+--------+----------------------------------------+---------+---------+-------+------+-------+ 

现在更新:

UPDATE states SET name = 'New York' WHERE ID = 1; //using the primary for update - we only have 1 New York City record in the DB 
Query OK, 0 rows affected (0.00 sec) 
Rows matched: 1 Changed: 1 Warnings: 0 

现在,如果我看我该城市所有地点,所有会说:纽约

对进场#2:

CREATE TABLE `locations` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(200) NOT NULL DEFAULT '', 
    `fk_country_id` varchar(200) NOT NULL default '', 
    `fk_state_id` varchar(200) NOT NULL default '', 
    `fk_cities_id` varchar(200) NOT NULL default '', 
    PRIMARY KEY (`id`), 
    KEY `fk_country_id` (`fk_country_id`), 
    KEY `fk_state_id` (`fk_state_id`), 
    KEY `fk_cities_id` (`fk_state_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 


SELECT location, city, state, country FROM locations; 
+-------------------+---------------+----------+---------------+ 
| location   | city   | state | country  | 
+-------------------+---------------+----------+---------------+ 
| Statue of Liberty | New York City | New York | United States | 
+-------------------+---------------+----------+---------------+ 

现在更新:

UPDATE locations SET name = 'New York' WHERE name = 'New York City'; // can't use the primary key for update since they are varchars 
Query OK, 0 rows affected (1.29 sec) 
Rows matched: 151 Changed: 151 Warnings: 0 

现在,如果我期待我的那个城市所有地点,并非所有会说:纽约

正如你所看到的,它花了1.29秒(是的它很快),但所有有“纽约”的记录都被更新了,但也许有一些拼写错误或者糟糕的名字等等......

结论: 仅出于这个原因,我宁愿采用第一种方法。

注: 国家和国家很少改变。也许你可以在你的代码中使用这些代码,并且不要从数据库中引用它们。这将从查询中节省2个INNER JOIN,并且它们在您的代码中只需检索国家或州的ID(如果您需要创建HTML下拉框,也是同样的事情)。此外,您可以考虑缓存这些国家和州使用像memcached,APC,reddis或任何你喜欢的其他国家。

4

去#1,#2没有标准化,这可能会导致问题。