【问题标题】:Query on distance距离查询
【发布时间】:2012-09-27 16:29:34
【问题描述】:

不确定如何构建它,因此向专家开放:

我在 db 中有一个客户列表,他们正在寻找他们所在位置半径内的事件。我可以存储他们的邮政编码(或纬度/经度)以及他们参加活动的最大距离。所以列 lat、lng、距离(例如:lat = '22.7447858'、lng = '-82.1398589'、distance = 25)。

全天发布活动,并存储其邮政编码/纬度/经度。

我想运行一个查询(每天一次),以获取某项活动的客户。我正在查看 Cyber​​Junkies 的帖子Mysql within distance query,但问题是我正在反方向运行查询。我需要找到“圆圈距离”涵盖当前事件的客户,而不是相反。不确定如何存储圆距离(上面的 3 列是否足够好,或者是否有更好的方法来存储此类查询的数据)?不确定如何根据事件查询客户。

提前致谢!

【问题讨论】:

    标签: php mysql sql geospatial spatial


    【解决方案1】:

    我认为有两种主要方法可以做到这一点:即时计算距离,以及预先计算一次距离,然后将它们存储在查找表中。

    选项 1,即时计算。 Tom van der Woerdt 的回答很好地解释了你将如何做到这一点。伪代码查询是这样的:

    SELECT * FROM customer, event WHERE (<calc distance>) < customer.distance
    

    选项2,预先计算所有距离。您将创建一个表(在本例中称为distance)来存储每个客户和每个事件之间的距离。它将包含三列:customerideventidmiles(或您想要的任何距离度量)。遍历每个客户,计算到每个事件的距离并将每个事件存储在distance 中。每次添加新客户或事件时,都会将相应的记录添加到 distance 表中。一旦这个结构就位,查找事件就很简单了:

    SELECT * FROM distance WHERE miles < [[some number you pick]]
    

    那么哪个更好?这是 CPU 时间和磁盘空间之间的权衡,因此答案取决于您的资源。选项 1(即时计算)将需要 DBMS 进行更多工作(更多 CPU 时间)。随着人员和事件数量的增加,该查询将需要更长的时间来运行。选项 2(预先计算距离)将使查找速度非常快,但代价是您必须将所有预先计算的距离存储在磁盘上。您还需要努力确保您的查找表是最新的。每次添加、删除客户或事件或更改其纬度/经度时,您都需要相应地更新查找表。 Triggers 可以帮助您使这个过程自动化;只需确保您尝试测试每个场景(添加、删除、移动),以确保查找表按预期更新。

    简短回答:如果您的数据库负载非常小和/或磁盘空间有限,请选择选项 1(即时计算)。如果负载很重但磁盘空间充足,请选择选项 2。选项 2 的可能性更大,而且可扩展性更高。

    【讨论】:

    • 2 的第二个缺点是,每当添加新事件/客户时,您都必须维护或重新生成该列表。
    • @xQbert 你说得对,我说得不够清楚。我再补充一点。
    【解决方案2】:

    你想要的是:

    SELECT * FROM customer, event WHERE (<calc distance>) < customer.distance
    

    这将简单地获取所有客户和所有事件,将它们组合在一起以获得所有可能的组合(100 个客户和 10 个事件产生 1000 个组合),然后检查它们是否在范围内。 *

    我个人建议创建一个 DISTANCE(customer,event) 函数来为您计算它。以这种方式管理查询更容易,并且您可以重复使用它。

    *不一定是这个顺序

    【讨论】:

      【解决方案3】:

      从 A 点到 B 点的距离将与从 B 点到 A 点的距离相同(除非您处理的是道路方向和不同的路径)。

      基本上你会做(在 sql 伪代码中)

      SELECT distance(event_loc, user_loc) <= user_max_distance
      

      【讨论】:

        【解决方案4】:

        如果您的距离计算与this solution 中的类似,那么您可以执行以下操作:

        select id1 from Distances 
            join EventTable on id2=EventTable.eventid 
            join UserTable on id1=UserTable.userid 
        where type2=<EVENT_TYPE> and type1=<USER_TYPE> 
            and geodistance_km_by_obj(id1,<USER_TYPE>,id2,<EVENT_TYPE>) < UserTable.max_distance
        

        【讨论】:

          【解决方案5】:

          除了其他答案之外,您可能会考虑使用笛卡尔坐标(x、y 和 z)而不是 lat/lng 进行 db 存储,因为生成的查询表达式在 db 服务器上的负载/时间方面比可能的更简单查询纬度/经度距离。

          可以在以下位置找到 PHP 实现的示例:

          http://headers-already-sent.com/geodistance/

          “getCartesian”方法将 lat/lng 转换为笛卡尔坐标,“getDistanceByCartesian”方法显示如何计算实际距离。您需要做的是将这个距离计算从 PHP 转移到 SQL 查询(这不应该那么复杂)。

          编辑,因为我有时间给出一个更实际的例子

          根据您可以在上述链接下找到的课程,我为我的公司位置和附近的所有 MC Donalds 餐厅设置了 2 个演示表,并将 lat/lng 从 Google 地图转换为笛卡尔 x、y、z:

          CREATE TABLE `locations` (
            `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
            `title` varchar(255) NOT NULL DEFAULT '',
            `lat` double NOT NULL,
            `lng` double NOT NULL,
            `x` double NOT NULL,
            `y` double NOT NULL,
            `z` double NOT NULL,
            PRIMARY KEY (`id`)
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
          
          INSERT INTO `locations` (`id`, `title`, `lat`, `lng`, `x`, `y`, `z`)
          VALUES
              (1,'Ida-Ehre-Platz 10, 20095 Hamburg',53.55053,9.99949,3727600.05477,657242.251356,5124712.81705),
              (2,'Kieler Straße 191-193, 22525 Hamburg',53.57731,9.93686,3725956.4981,652753.812254,5126481.40905),
              (3,'Reeperbahn 42, 20359 Hamburg',53.549951,9.964937,3728046.74189,655003.113578,5124674.56664),
              (4,'Theodor-Heuss-Platz 3, 20354 Hamburg',53.56083,9.99038,3726797.15378,656489.722425,5125393.17725),
              (5,'Mundsburger Damm 67, 22087 Hamburg',53.57028,10.02642,3725550.98379,658686.623655,5126017.24553),
              (6,'Paul-Nevermann-Platz 1, 22765 Hamburg',53.552602,9.936678,3728135.78521,653123.397726,5124849.69505),
              (7,'Friedrich-Ebert-Damm 101, 22047 Hamburg',53.58753,10.08958,3723303.02881,662522.688778,5127156.05819),
              (8,'Amsinckstraße 73, 20097 Hamburg',53.54271,10.02654,3727978.07563,659123.791421,5124196.16112),
              (9,'Eiffestraße 440, 20537 Hamburg',53.55214,10.04638,3726919.13256,660267.521487,5124819.17553);
          
          
          CREATE TABLE `user` (
            `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
            `name` varchar(255) NOT NULL DEFAULT '',
            `lat` double NOT NULL,
            `lng` double NOT NULL,
            `x` double NOT NULL,
            `y` double NOT NULL,
            `z` double NOT NULL,
            PRIMARY KEY (`id`)
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
          
          INSERT INTO `user` (`id`, `name`, `lat`, `lng`, `x`, `y`, `z`)
          VALUES
              (1,'Ministry.BBS, Cremon 36, 20457 Hamburg',53.545943,9.988761,3728127.10678,656615.385203,5124409.77226),
              (2,'BBS, Dorotheenstraße 60, 22301 Hamburg',53.583231,10.008315,3724617.80169,657307.963226,5126872.28974);
          

          基于这两个表,用于查找到每个用户(我们公司办公室)一定距离(2000 年,在本例中以米为单位)内的所有位置(餐厅)的 SQL 查询将是:

          SELECT locations.*,
              2 * 6371000.785 *
                  asin(
                      sqrt(
                          pow(locations.x - user.x, 2)
                          + pow(locations.y - user.y, 2)
                          + pow(locations.z - user.z, 2)
                      ) / (2 * 6371000.785)
                  ) AS distance
              FROM locations, user
              HAVING distance < 2000 
              ORDER BY distance ASC
          

          如果您需要“米”以外的其他内容,则必须将地球半径更改为大约。 6371000.785(以米为单位)更改为您需要的任何值,并将所需距离 2000 更改为您喜欢的任何值或存储在每个用户的用户表中。

          【讨论】:

            猜你喜欢
            • 2015-04-17
            • 2011-09-28
            • 1970-01-01
            • 2015-09-18
            • 2017-04-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多