编辑说明:如评论中所提到的,一旦您拥有足够多的地点,将长/纬度值存储起来并基于它们实时计算距离可能更有意义。然而,本文中解决方案仍可能适用于其他应用程序。
处理这个问题的最佳方式是使用数据透视表,每行都有两个位置ID和一个距离值。
现在,由于A-B的距离与B-A的距离相同,我们只需要存储每个配对一次。我们可以通过仅在A的ID小于B时存储距离来实现此目的。
设置
首先需要一个places
表来存储你的地点
id | name
---+---------
1 | Place_A
2 | Place_B
3 | Place_C
4 | Place_D
然后是一个places_distances
透视表:
place_id_1 | place_id_2 | distance
-----------+------------+----------
1 | 2 | 10.0
1 | 3 | 20.0
1 | 4 | 15.0
2 | 3 | 12.0
2 | 4 | 8.0
3 | 4 | 14.0
请注意,数据透视表不需要自己的ID字段(尽管有些人可能会认为有时仍然很有用)。您将按以下方式设置唯一键(您需要查阅
文档以获取正确的用法):
UNIQUE KEY `UNIQUE_placesDistances_primary`(`place_id_1`,`place_id_2`)
这可以确保表中没有相同的位置/地点配对。
您还需要设置外键:
CONSTRAINT FOREIGN KEY `FK_placesDistances_place1` (`place_id_1`)
REFERENCES `places`(`id`),
CONSTRAINT FOREIGN KEY `FK_placesDistances_place2` (`place_id_2`)
REFERENCES `places`(`id`)
这将确保您只能为实际在
places
中定义的地点添加条目。这也意味着(如果您使用默认的外键行为),如果您有距离行引用该地点,则无法删除该地点。
使用示例
查找两个地点之间的距离
(假设有两个变量 @id_1
表示第一个地点的ID,@id_2
表示第二个地点的ID)
SELECT `distance`
FROM `places_distances`
WHERE (`place_id_1` = @id_1 AND `place_id_2` = @id_2)
OR (`place_id_2` = @id_1 AND `place_id_11` = @id_2)
LIMIT 1;
我们使用OR来处理查找距离2到1而不是1到2的情况 - 请记住,我们仅存储第一个位置id小于第二个位置id的值,以避免存储重复值。
插入新的距离
(给定三个变量,@id_1
为第一个地点的 id,@id_2
为第二个地点的 id,@distance
为距离)
INSERT `places_distances`(`place_id_1`,`place_id_2`,`distance`)
VALUES(LEAST(@id_1, @id_2),GREATEST(@id_1, @id_2), @distance)
我们正在使用内置的
比较函数LEAST
和
GREATEST
来帮助维护我们的规则,即仅存储第一个ID小于第二个ID的位置,以避免重复。
展示一个地名列表,按照距离由远及近排序。
为了让原始的地名从“places”表中显示在我们的“places_distances”查询中,我们需要将它们连接在一起。在这种情况下,“LEFT JOIN”是最好的选择,因为我们只关心“places_distances”表中的内容。
有关MySQL连接更多信息,请查看此处。
SELECT
`p_1`.`name` AS `place_1`,
`p_2`.`name` AS `place_2`,
`distance`
FROM `places_distances`
LEFT JOIN `places` AS `p_1`
ON `distances`.`place_id_1` = `p_1`.`id`
LEFT JOIN `places` AS `p_2`
ON `distances`.`place_id_2` = `p_2`.`id`
ORDER BY `distance` DESC
应该返回这样的表格:
place_id_1 | place_id_2 | distance
-----------+------------+----------
Place_A | Place_C | 20.0
Place_A | Place_D | 15.0
Place_C | Place_D | 14.0
Place_B | Place_C | 12.0
Place_A | Place_B | 10.0
Place_B | Place_D | 8.0
展示一个地点表格,其中包含它们与特定给定地点的距离。
这有点棘手,因为我们需要在不是输入地点的行中显示名称,但我们可以使用另一个有用的函数
IF(CONDITION,'TRUE_OUTPUT','FALSE_OUTPUT')
来实现。
(
@place_name
是包含地点名称的变量,在本例中为'Place_B')
SELECT
IF(`p_1`.`name`=@place_name, `p_2`.`name`, `p_1`.`name`) AS `name`,
`distance`
FROM `places_distances`
LEFT JOIN `places` AS `p_1`
ON `distances`.`place_id_1` = `p_1`.`id`
LEFT JOIN `places` AS `p_2`
ON `distances`.`place_id_2` = `p_2`.`id`
WHERE `p_1`.`name` = @place_name OR `p_2`.`name` = @place_name
ORDER BY `distance` DESC
应该返回这样的表格:
name | distance
--------+-----------
Place_C | 12.0
Place_A | 10.0
Place_D | 8.0