5

I have a database containing places, and I need to show distances from any place to other ones in my webpage. Having the distances stored somewhere will save lots of work (loading them should be easier than computing them anew). But how to save the square matrix of the distances? Creating a new column every time I insert a new row doesn't seem to be a good solution, but I didn't find any better solution (though I can think of workarounds such as computing some 10 or 20 nearest distances and assuming I will rarely need more).

What is optimal way to save square tables of variable (and growing) size in PHP/MySQL? Or is there no good solution and my (or some other) workaround is better?

4

6 回答 6

5

编辑注意:正如评论中提到的,一旦你得到足够多的地方,只存储长/纬度值并根据这些值动态计算距离可能更有意义。然而,这里解释的解决方案可能仍然与其他应用程序相关。


处理此问题的最佳方法是使用数据透视表,其中每行有两个地点 id 和一个距离值。

现在,由于距离 AB 与 BA 相同,我们只需将每个配对存储一次。我们可以通过仅在 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_2的id和第二名的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-11记住2,我们只存储第一个位置的 id 小于第二个位置的值以避免存储重复项。


插入新距离

(给定三个变量@id_1作为第一名@id_2的id和第二名的id,以及@distance距离)

INSERT `places_distances`(`place_id_1`,`place_id_2`,`distance`)
    VALUES(LEAST(@id_1, @id_2),GREATEST(@id_1, @id_2), @distance)

我们使用内置的比较函数 LEASTGREATEST帮助维护我们的规则,即我们只存储第一个 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
于 2013-09-13T13:59:14.180 回答
3

我会存储所有地方的纬度/经度,并编写一个函数来计算它们与纬度/经度信息之间的距离。

这样,无需计算要添加到数据库中的新地点的距离。

此外,如果你有很多地方,使用数据透视表只存储距离,你必须知道这个表可以增长得非常快。因为您需要涵盖所有地方的组合。

例如:对于 1000 个地方,您的表中将有 1000 * 1000 - 1000 = 999000 行。计算更大的数字,但此表可能包含很多行,具体取决于您有多少个地方。

于 2013-09-13T12:24:15.270 回答
3

将其分解为另一个与原始“地点”表相关的名为“距离”的表:

创建表距离(place_id_1 int,place_id_2 int,distance int);

也就是说,对于每个地方,计算另一个地方的距离并将其保存在这个新表中。

于 2013-09-13T12:32:12.540 回答
2

您可以创建一个新表,其中两列作为位置的外键,一列作为它们之间的距离。

 |place1 | place2 | distance
-+-------|--------|---------
 |....   |.....   | ..... 

取决于您拥有多少个位置,此表可能会增长得非常快。

于 2013-09-13T12:34:51.993 回答
0

最简单的方法是制作另一个包含两个地方 id 和距离的表格

place1    place2    distance
a         b          20
c         d          30

在获取数据时,只需将其与位置表连接即可。

于 2013-09-13T12:37:56.807 回答
-1

我认为这样的事情可以完成这项工作。

         ORIGIN     | CITY 1 | CITY 2 | CITY 3 | CITY 4 | CITY 5
         +++++++++++++++++++++++++++++++++++++++++++++++++++++++
         CITY 1        0        20                 40      20
         CITY 5        10       50       20                0
         CITY 3        10                0         10      40

您可以轻松获取到其他地方的距离,并且您不需要为您知道的每个距离存储城市名称。

SELECT 'CITY 2' FROM DISTANCES WHERE ORIGIN='CITY 5'
于 2013-09-13T12:31:53.450 回答