2011-07-11 29 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沒有標準化,這可能會導致問題。