Подход № 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 или любые другие, которые вы предпочитаете.