【问题标题】:How to combine geolocation query with other criteria如何将地理位置查询与其他条件相结合
【发布时间】:2014-11-21 16:56:16
【问题描述】:

我有这两个问题:

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_idplaces 表的属性)。

我试图将这两个查询合并为一个,因为我想按distance 对公司进行排序(如果离给定点最近的地方属于“公司 A”,那么这家公司将是第一个在输出中)并且作为查询的结果,我正在尝试接收:

  • 在给定半径范围内拥有位置的公司
  • 属于公司的地点以及这些地点都在指定的半径范围内。

这似乎有点不合我意,我正在尝试将这两个查询合并为一个,因为对于两个查询,我必须使用 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 秒。我试图在“大”查询中更改JOINs 的顺序,希望这可能会导致没有获取places,但它没有帮助,仍然没有加载places。我想知道是什么导致了这个问题。

编辑 3:

甚至尝试这样做(省略service_areas 表上的WHERE,目标是对其进行调试并找出查询从未返回任何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 家未经过滤的公司,但仍然没有位置。

谢谢

【问题讨论】:

  • 该代码需要稍微清理一下。我不知道有什么东西在哪里。
  • @padagome 我同意第一个查询很难阅读,但这是因为有一些使用 RADIUS 和测角函数的计算(不知道如何提高可读性)。第二个是带有 JOIN 的“简单”查询。
  • @Padagomez - 我尽力了...
  • 您的第一个查询是球余弦公式(通常称为Haversine 公式)的一个版本吗?该公式中的常数(例如0.4878295615756141)是什么意思? ypur places 表和第二个查询中提到的表如何相互关联?
  • 谢谢@PM77-1 我会看看我能从那里做些什么。

标签: mysql sql join geolocation inner-join


【解决方案1】:

看来你有两个问题。

  1. 提高查询效率。
  2. 使用距离计算在 places 表中查找内容并将其与其他表中的内容相关联。

您似乎正在对 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/

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

 ... 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 标准。

【讨论】:

  • Killer Answer @Ollie Jones 在一个差得多的解决方案上放弃。 对查询和公式优化的上述所有详细说明以及超越解释的额外赞誉。我只是要根据 ID 建议在 COMPANY 和 PLACES 表上进行第二次 JOIN ......并称之为好:-P
  • 您好@OllieJones,非常感谢您提供非常详细的回答,我很感激。当我研究解决方案时,问题是placesservice_areas 之间没有关系。每个公司可以有多个service_areas,每个service_area 都属于一个company。由于我目前正在使用脚本在查询中填充数组(1, 3, 6, ...)(它非常慢),我想知道我是否可以为此目的使用 MySQL。正在处理查询...再次感谢您。
  • service_areasplaces 中有哪些列?也许他们都有邮政编码/邮政编码?也许他们有地方名称?可能有办法建立联系。
  • service_areas: company_id, state_nameplacescompany_idziplatpng。我正在尝试运行查询,稍后将使用其结果更新 OP。
  • 我有点困惑..你说places和service_areas之间没有关系......但都有一个company_id。这些company_id在某些方面的含义是否不同?无论如何......如果它有帮助......我修改了我原来的 SQLFiddle 演示以适应 @Ollie Jones 解决方案。调试和/或进一步实验可能很有用。我相信这些表格的结构与您当前定义的一样。 sqlfiddle.com/#!2/860410/1
猜你喜欢
  • 1970-01-01
  • 2014-07-02
  • 1970-01-01
  • 2013-12-21
  • 1970-01-01
  • 1970-01-01
  • 2023-02-10
  • 1970-01-01
  • 2017-03-27
相关资源
最近更新 更多