Схема базы данных - представление местоположения - PullRequest
19 голосов
/ 11 июля 2011

Мне нужно указать местоположение некоторых событий, и я разрабатываю схему базы данных для этого приложения. У меня есть два подхода для представления местоположения:

подход 1: 4 стола:

  • Страны
  • States
  • Город
  • Местоположение (в местоположении у меня есть внешний ключ к country_id, state_id и city_id)

подход 2: 1 стол:

  • Местоположения и просто поля страны, штата, города, которые хранятся в виде текста (без иностранных идентификаторов)

Какой подход вы бы порекомендовали? первый поможет устранить возможные разные имена, например, одна и та же страна (США, США, США и т. д.) и может быть полезна для предоставления предложений при написании текстовых полей, которые, вероятно, будут обязательными.

однако второй подход, похоже, позволит значительно упростить все и уменьшит количество запросов к базе данных.

Какой из них вы считаете лучше? Знаете ли вы, каковы лучшие практики в этом случае? Например. как это делалось на некоторых больших порталах, где им также нужно что-то вроде местоположения (например, foursquare и т. д.). Afaik facebook использует второй подход, но ... я хочу услышать ваше мнение и, возможно, причины, по которым вы бы выбрали один подход вместо другого.

Спасибо!

Ответы [ 2 ]

18 голосов
/ 26 октября 2011

Подход № 1:

Это хорошее решение, если вы хотите нормализованную базу данных . Вы можете легко управлять всеми своими таблицами, но при запросе местоположения вам потребуется 3 левых / внутренних соединения. Я предполагаю, что все проиндексировано правильно, поэтому у вас не будет особых проблем с производительностью, поскольку эти таблицы будут относительно небольшими (страна и штаты) и средними по размеру для городов (если вы хотите, чтобы все города были только для определенной страны). Если вы хотите ВСЕ города мира, эта таблица будет огромной, и в какой-то момент у вас может возникнуть проблема с производительностью, если вы не проиндексировали или не присоединились к таблице правильно.

Поскольку все находится в базе данных, вам не нужно менять код, если вам нужно добавить, обновить или удалить запись.

Если вам нужно добавить, обновить или удалить какие-либо записи, это решение будет очень легко поддерживать. Если вам нужно обновить имя (например, название города) и все записи будут обновлены сразу.

Запросы будут выполняться быстрее, если вы посмотрите по городу или штату, будет быстро, тогда простое объединение влево, чтобы получить имя, поможет.

Подход № 2:

Я лично не рекомендовал бы это, потому что для удобства обслуживания это не лучшее решение. Если когда-нибудь вам понадобится получить данные по городу, ваш запрос может выполняться медленно, если вы не проиндексировали должным образом. Если вы индексируете страну, штат, город, то поиск будет быстрее (но медленнее, чем первый подход, так как varchar медленнее, чем int для индексации). Кроме того, вы повышаете риск ошибок для имен, например: New York VS newyork VS New Yrok.

Кроме того, если вам нужно обновить название города, вам нужно будет восстановить все записи с таким именем, а затем обновить все эти записи. Что может занять много времени.

Например: ОБНОВЛЕНИЕ местоположений SET city = 'Нью-Йорк', где city = 'Нью-Йорк'; * примечание: также, если у вас есть ошибки, вам нужно будет проверить ВСЕ записи, чтобы убедиться, что вы обновили все записи

Вот скелет, основанный на вашем требовании (с использованием 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 из запроса и их в вашем коде вы просто получите идентификатор страны или штата (то же самое, если вам нужно создать раскрывающийся список HTML).

Кроме того, вы можете подумать о кэшировании этих стран и штатов, используя memcached, APC, reddis или любые другие, которые вы предпочитаете.

4 голосов
/ 26 октября 2011

Переходите к # 1, # 2 не нормализовано, что может вызвать проблемы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...