使用交叉连接和 Hversine 公式在 MySQL 地理搜索中重复记录
Posted
技术标签:
【中文标题】使用交叉连接和 Hversine 公式在 MySQL 地理搜索中重复记录【英文标题】:Duplicate Records in MySQL Geo Search Using Cross Join and Hversine Formula 【发布时间】:2014-02-22 19:50:29 【问题描述】:我正在尝试完成对这个Google tutorial的修改
我编写了这个 SQL 来使用位置“名称”查询位置表。给定位置的名称,查询返回附近的比萨餐厅。为了做到这一点,我交叉加入了我的餐厅位置表,标题为“标记”,并使用 Haversine 公式计算距离。
SELECT m.address,
m.name,
m.lat,
m.lng,
(3959 * ACOS(COS(RADIANS(poi.lat)) *
COS(RADIANS(m.lat)) *
COS(RADIANS(m.lng) - RADIANS(poi.lng)) + SIN(RADIANS(poi.lat))*
SIN(RADIANS(m.lat)))) AS distance
FROM markers poi
CROSS JOIN markers m
WHERE poi.address LIKE "%myrtle beach%"
AND poi.id <> m.id HAVING distance < 200
ORDER BY distance LIMIT 0,20
查询返回预期结果,但如果兴趣点在指定区域之外,在本例中为“默特尔海滩”,我每次匹配都会得到重复记录。这是因为 CROSS JOIN 很容易通过 DISTINCT 选择来修复。但是“lng”和“lat”字段是 FLOAT 类型,因此距离计算永远不会相同,即使对于重复记录也是如此。
这是回报的一个子集:
3901 North Kings Highway Suite 1, 默特尔比奇, SC |芝加哥比萨公司以东| 33.716099 -78.855583 | 4.0285562196955125
1706 S Kings Hwy # A, 默特尔比奇, SC |达美乐披萨:默特尔比奇 | 33.674881 | -78.905144 | 4.0285562196955125
82 Wentworth St, 查尔斯顿, SC |安多利尼斯披萨 | 2.782330 | -79.934235 | 85.68177495224947
82 Wentworth St, 查尔斯顿, SC |安多利尼斯披萨 | 32.782330 | -79.934235 | 89.71000040441085
114 Jungle Rd, 埃迪斯托岛, SC | Edisto Beach Inc 的雄鹿披萨 | 32.503971 -80.297951 | 114.22243529200529
114 Jungle Rd, 埃迪斯托岛, SC | Edisto Beach Inc 的雄鹿披萨 | 32.503971 -80.297951 | 118.2509427998286"
关于从这里去哪里有什么建议吗?
【问题讨论】:
您可以发布示例输入和输出以显示重复数据吗?为了便于阅读,我建议进行编辑,假定existence of aHaversine
function。
@Brandon Buster 您能否提供数据样本。另外,兴趣点是什么意思?我看到的唯一手动输入的数据是 %myrtle beach% 。你想做什么?因为这个问题有点难以理解,需要澄清和更多细节。
抱歉,我在几个不同的上下文中使用了“兴趣点”一词。此示例中的兴趣点仅是默特尔比奇。我想要返回的数据是该点半径 200 英里内的披萨餐厅。我将使用一些示例数据和更多信息来编辑我的帖子
【参考方案1】:
试试:
select distinct x.address, x.name, y.lat, y.lng, x.distance
from (SELECT m.address,
m.name,
m.lat,
m.lng,
(3959 *
ACOS(COS(RADIANS(poi.lat)) * COS(RADIANS(m.lat)) *
COS(RADIANS(m.lng) - RADIANS(poi.lng)) +
SIN(RADIANS(poi.lat)) * SIN(RADIANS(m.lat)))) AS distance
FROM markers poi
cross JOIN markers m
WHERE poi.address LIKE "%myrtle beach%"
and poi.id <> m.id HAVING distance < 200) x
join markers y
on x.address = y.address
and x.name = y.name
and x.lat = y.lat
and x.lng = y.lng
order by x.distance limit 0, 20
【讨论】:
谢谢。指出看起来可行的解决方案。不过,我宁愿不执行第三个表连接。如果这是唯一的答案(很可能),我会将其标记为答案。【参考方案2】:您会得到重复的结果,因为这两个点都匹配“默特尔海滩”。使用poi.id < m.id
之类的条件来确保您只获得一个匹配项。
例子:
poi id m id distance
1 2 100
2 1 100
查询:
SELECT
m.address,
m.name,
m.lat,
m.lng,
(3959 * ACOS(COS(RADIANS(poi.lat)) *
COS(RADIANS(m.lat)) *
COS(RADIANS(m.lng) - RADIANS(poi.lng)) + SIN(RADIANS(poi.lat))*
SIN(RADIANS(m.lat)))) AS distance
FROM markers poi
CROSS JOIN markers m
WHERE
(poi.address LIKE "%myrtle beach%" OR m.address LIKE "%myrtle beach%")
AND poi.id < m.id
HAVING distance < 200
ORDER BY distance LIMIT 0,20
或者,如果您确实在标记中有一个单行作为兴趣点,请指定该行而不是地址上的任何匹配项。那么poi.id <> m.id
的条件将确保没有重复。
SELECT
m.address,
m.name,
m.lat,
m.lng,
(3959 * ACOS(COS(RADIANS(poi.lat)) *
COS(RADIANS(m.lat)) *
COS(RADIANS(m.lng) - RADIANS(poi.lng)) + SIN(RADIANS(poi.lat))*
SIN(RADIANS(m.lat)))) AS distance
FROM markers poi
CROSS JOIN markers m
WHERE
poi.id = (SELECT TOP(1) id FROM markers WHERE address LIKE "%myrtle beach%")
AND poi.id <> m.id
HAVING distance < 200
ORDER BY distance LIMIT 0,20
【讨论】:
感谢您的回复。它确实消除了重复项,尽管它还假设所有匹配记录的 ID 都比在我的目标区域中找到的任何记录都更高,但情况并非总是如此。不过,这让我对我是如何编写 SQL 有了不同的看法。 @BrandonBuster,它不假设是这样,因为在第一个查询中,编号较高或较低的行可以匹配。在第二个查询中,我们不使用>
比较。【参考方案3】:
查看每个人的回复让我开始思考。我没有问为什么我得到重复的结果,而是开始想知道查询计算距离的两个默特尔比奇位置中的哪一个?答案是两者。这也解释了为什么我一开始每场比赛都能获得两条记录。
这是我的解决方案:
SELECT m.address, m.name, m.lat, m.lng, (3959
* ACOS(COS(RADIANS(poi.lat)) * COS(RADIANS(m.lat))
* COS(RADIANS(m.lng) - RADIANS(poi.lng)) + SIN(RADIANS(poi.lat))
* SIN(RADIANS(m.lat)))) AS distance
FROM markers m
cross JOIN (
select name, lat, lng from markers
where address like '%myrtle beach %'
limit 1
) poi
HAVING distance < 200
ORDER BY name
LIMIT 0, 20
这并没有给我最准确的距离计算,因为它任意使用它找到的第一家餐厅作为震中。但就我的直接目的而言,这已经足够了。我认为要让这个应用程序准备好生产,我需要第二个包含城市中心坐标的城市表。
【讨论】:
以上是关于使用交叉连接和 Hversine 公式在 MySQL 地理搜索中重复记录的主要内容,如果未能解决你的问题,请参考以下文章