数据库架构 - 表示位置

时间:2011-07-11 07:11:41

标签: mysql database database-schema

我需要表示某些事件的位置,我正在为此应用程序设计数据库架构。 我有两种方法来展示这个位置:

接近1: 4桌:

  • 国家
  • 城市
  • 地点(位置我有country_id,state_id和city_id的外键)

接近2: 1表:

  • 位置,只有字段country,state,city作为文本存储(没有外国id)

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

然而,第二种方法似乎可以让一切变得更简单,并且应该减少对数据库的查询次数。

您认为哪一个更好?你知道这种情况下的最佳做法是什么吗?例如。它是如何做一些大型门户网站,他们也需要像位置一样的东西(例如foursquare等)。 Afaik facebook使用第二种方法,但是......我想听听你的意见以及为什么你会选择一种方法而不是另一种方法的理由。

谢谢!

2 个答案:

答案 0 :(得分:18)

方法#1:

如果你想要一个好的normalized database,这是一个很好的解决方案。您可以轻松管理所有表格,但在查询位置时必须有3个左/内连接。我假设所有内容都已正确编入索引,因此您不会遇到真正的性能问题,因为这些表格相对较小(国家/地区)和中等规模的城市(如果您只想要特定国家/地区的所有城市)。如果您希望世界上所有城市的表格都很庞大,并且如果您没有正确索引或加入表格,则可能会在某些时候出现性能问题。

由于所有内容都在数据库中,因此如果您需要添加,更新或删除记录,则无需更改代码。

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

如果您按城市或州看起来会很快,查询会更快运行,然后一个简单的左连接来获取名称就可以了。

方法#2:

我个人不建议这样做,因为为了可维护性,它不是最好的解决方案。如果有一天您需要根据城市检索数据,如果您没有正确索引,您的查询可能会很慢执行。如果您为country,state,city编制索引,那么查找速度会更快(但比第一种方法慢,因为varchar比int慢于int)。此外,你增加了名字错误的风险,例如:New York VS newyork VS New Yrok。

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

例如:UPDATE位置SET city ='New York',其中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或您喜欢的任何其他国家/地区来缓存这些国家/地区。

答案 1 :(得分:4)

使用#1,#2未标准化,这可能会导致问题。