如何将地理位置查询与其他条件相结合

时间:2014-11-21 16:56:17

标签: mysql sql join geolocation inner-join

我有这两个问题:

SELECT
          (ACOS(least(1,COS(0.4878295615756141)*COS(-1.4391492410217162)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+
          COS(0.4878295615756141)*SIN(-1.4391492410217162)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+
          SIN(0.4878295615756141)*SIN(RADIANS(places.lat))))*3963.1899999999996)
          AS distance, places.* 
FROM `places`  
WHERE ((
          (ACOS(least(1,COS(0.4878295615756141)*COS(-1.4391492410217162)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+
          COS(0.4878295615756141)*SIN(-1.4391492410217162)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+
          SIN(0.4878295615756141)*SIN(RADIANS(places.lat))))*3963.1899999999996)
          <= 200.0))

SELECT `companies`.* 
FROM `companies` 
INNER JOIN `service_areas` ON `service_areas`.`company_id` = `companies`.`id` 
WHERE `companies`.`id` IN (1, 3, 6, ...) AND `service_areas`.`state_name` = 'CA'

它的工作方式如下:第一个查询查找指定半径内的位置。第二个查询查找拥有第一个查询中找到的地点的所有公司。

第二个查询中的部分 - (1, 3, 6, ...) - 在ruby中,我从地点获取所有company_id并将它们放到第二个查询中(company_id是一个属性places表格。)

我试图将这两个查询合并为一个,因为我想按distance对公司进行排序(如果最接近给定点的位置属于&#34;公司A&#34; ,所以这家公司将在输出中排在第一位。作为查询的结果,我试图收到:

  • 在给定半径内具有位置的公司
  • 属于公司的地方,这些地方也在指定范围内。

这似乎有点超出了我的联盟,我试图将这两个查询合并为一个,因为有两个查询,我必须使用Ruby进行一些操作(过滤地点)并且这些操作最后来自60-90秒......

提前感谢你们的时间。

修改 我稍微修改了一下查询,这里是它的外观:

SELECT places.*, companies.*,
       69.0 * HAVERSINE(places.lat, places.lng, 27.950575,-82.45717) AS distance
FROM places 
JOIN companies ON companies.id = places.company_id
JOIN service_areas ON service_areas.company_id = companies.id  
WHERE places.lat BETWEEN 27.950575 - (200.0 / 69.0)
  AND 27.950575 + (200.0 / 69.0)    
  AND places.lng BETWEEN -82.45717 - (200.0 / (69.0 * COS(RADIANS(27.950575))))
  AND -82.45717 + (200.0 / (69.0 * COS(RADIANS(27.950575))))     
  AND companies.id = places.company_id 
AND service_areas.state_name = 'CA'   
ORDER BY distance

我还在places.latplaces.lng列添加了索引。当我在MySQL控制台中运行此查询时,我得到了586个结果;查询持续了1分22秒,当时我第二次跑30秒,第三次尝试18秒。

我只是在分析收到的结果,以验证我的需要。

EDIT2:

当我更深入地查看获取的结果时,我发现查询加载companies,但始终没有places。我认为特定搜索没有places,所以我更改了城市等等,但查询仍然没有返回places

所以我尝试单独运行查询,如下所示:

SELECT places.*,
  69.0 * HAVERSINE(places.lat,places.lng, 27.950575,-82.45717) AS distance                                          
FROM places 
WHERE places.lat 
  BETWEEN 27.950575 - (200 / 69.0)
    AND 27.950575 + (200 / 69.0)
    AND places.lng
  BETWEEN -82.45717 - (200 / (69.0 * COS(RADIANS(27.950575))))
    AND -82.45717 + (200 / (69.0 * COS(RADIANS(27.950575))))

此查询返回6,600个位置,查询持续30秒。我试图改变&#34; big&#34;中JOIN s的顺序。查询希望可能导致没有places被提取,但它没有帮助,仍然没有加载places。我想知道是什么导致了这个问题。

编辑3:

尝试这样做(省略WHERE service_areas表,目的是调试它,并找出原因永远不会被查询返回任何places

SELECT places.*,
  69.0 * HAVERSINE(places.lat,places.lng, 27.950575,-82.45717) AS distance                                          
FROM places 
JOIN companies ON places.company_id = companies.id
WHERE places.lat 
  BETWEEN 27.950575 - (200 / 69.0)
    AND 27.950575 + (200 / 69.0)
    AND places.lng
  BETWEEN -82.45717 - (200 / (69.0 * COS(RADIANS(27.950575))))
    AND -82.45717 + (200 / (69.0 * COS(RADIANS(27.950575))))

结果是超过5,000家未经过滤的公司,但仍然没有地方。

谢谢

1 个答案:

答案 0 :(得分:2)

看来你有两个问题。

  1. 提高此查询效率。
  2. 使用距离计算查找places表格中的内容,并将其与其他表格中的内容相关联。
  3. 看起来您正在使用(以度为单位)27.950575,-82.45717的特定位置的常量值。如果那是位于美国佛罗里达州坦帕市中心Zack St的一个地方,我猜对了你的常数的含义。我们将这些值称为latpointlonpoint

    另一个常数3963.19告诉我们你在英里工作。每度有69英里。

    要开始解决这个问题,不要淹没在大量的数学中,让我们假设存在一个名为

    的存储函数
     HAVERSINE(lat1,long1, lat2,long2)
    

    这样的功能可以在这里找到:http://www.plumislandmedia.net/mysql/stored-function-haversine-distance-computation/

    通过这种方式,我们可以自由地构建代码,以说服自己拥有正确的代码。

    您可以使用一些不错的WHERE子句优化您的第一个查询:

         places.lat BETWEEN latpoint - (200.0 / 69.0)
                        AND latpoint + (200.0 / 69.0)
     AND places.lon BETWEEN lonpoint - (200.0 / (69.0 * COS(RADIANS(latpoint))))
                        AND lonpoint + (200.0 / (69.0 * COS(RADIANS(latpoint)))) 
    

    这些条款在你的起点周围划出了一个200英里的边界框。他们可以非常有效地使用表格中(lat, lon)的索引。

    因此,这将是您修改的距离计算查询。

    SELECT places.*,
           69.0 * HAVERSINE(places.lat,places.lon, latpoint,lonpoint) AS distance
      FROM places
     WHERE places.lat BETWEEN latpoint - (200.0 / 69.0)
                          AND latpoint + (200.0 / 69.0)
       AND places.lon BETWEEN lonpoint - (200.0 / (69.0 * COS(RADIANS(latpoint))))
                          AND lonpoint + (200.0 / (69.0 * COS(RADIANS(latpoint)))) 
      ORDER BY distance
      LIMIT 50
    

    因为WHERE子句有望消除places表的许多行,这将节省大量时间。有关更完整的说明,请参阅此处:http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

    现在我们已经准备好了有效查询的框架,我需要做出一个假设。就是这样:你可以这样做。

     ... places
    JOIN companies ON companies.id = places.company_id
    

    因此,将这些内容添加到查询中变得非常容易。 已编辑,其中包含有关companiesplaces表格相关的信息。

    SELECT places.*, companies.*,
           69.0 * HAVERSINE(places.lat,places.lon, latpoint,lonpoint) AS distance
      FROM places
      JOIN companies ON companies.id = places.company_id
      JOIN service_areas ON companies.id = service_areas.company_id
     WHERE places.lat BETWEEN latpoint - (200.0 / 69.0)
                          AND latpoint + (200.0 / 69.0)
       AND places.lon BETWEEN lonpoint - (200.0 / (69.0 * COS(RADIANS(latpoint))))
                          AND lonpoint + (200.0 / (69.0 * COS(RADIANS(latpoint)))) 
       AND  companies.id IN (1, 3, 6, ...) AND service_areas.state_name = 'CA'
      ORDER BY distance
      LIMIT 50
    

    这将找到所有在加利福尼亚州的服务区的公司,距离您的latpoint,lonpoint位置(佛罗里达州)<200> 。

    places上的复合索引:(company_id, lat, lon)可能会提高此查询的效果。

    如果您使用距离标准以避免混淆,则可能需要省略state_name条件。