正如标题所说,我正在尝试计算许多经度和纬度点集之间的最短距离。我有一套房子和一套商店。对于每个家庭,我试图确定在 20 英里半径范围内哪个商店最近。
我编写的 SQL 可以正常工作,但是在为执行添加更多家时它不能很好地扩展。我正在尝试找到一种有效地进行此计算的方法。即使运行需要几个小时,我也会很高兴,因为我可以每月运行一次。但是,就目前而言,如果我针对数据库中的全部房屋进行尝试,此查询将运行数天。
到目前为止我尝试过的
- 使用这个问题的指导,我利用 Oracle 的 SDO_GEOM 包来执行距离计算。
- 在效率方面,我按照本指南中的建议在每个 long/lat 列上设置索引,并在 where 子句中设置代码以限制为 20 英里半径,以尝试立即过滤掉无效的 long/lat蝙蝠,从而减少了多余的计算。
- 我可以为查询添加并行性,但我觉得这是一种减少运行时间的蛮力方法。虽然我认为补充并行性是可行的,但我想找到一个解决方案,让查询在我投入处理器之前高效运行。
数据设置
我正在开发一个具有两个数据集的 Oracle 19c 数据库:
1. HOME_ID 列表及其关联的经度和纬度
create table tmp_homes (
home_id number not null,
home_long float not null,
home_lat float not null,
primary key(home_id)
) nologging compress pctfree 0
;
该列表的大小可能有数十万条记录。
为每个 long/lat 列设置一个索引。
2. STORE_ID 列表及其关联的经度和纬度
create table tmp_stores (
store_id number not null,
store_long float not null,
store_lat float not null,
primary key(store_id)
) nologging compress pctfree 0
;
这个列表的大小大约是一千条记录。
为每个 long/lat 列设置一个索引。
询问
create table tmp_homes_to_stores compress nologging pctfree 0 as
select *
from (
select
h.home_id,
s.store_id,
sdo_geom.sdo_distance(
sdo_geometry(2001, 4326, sdo_point_type(h.home_long, h.home_lat, null), null, null),
sdo_geometry(2001, 4326, sdo_point_type(s.store_long, s.store_lat, null), null, null),
0.01,
'unit=KM'
) as distance,
s.radius
from tmp_homes h
cross join (
select store_id, store_long, store_lat, 32.1869 as radius, 111.045 as distance_unit, 0.0174532925 as deg2rad--, 57.2957795 as rad2deg
from tmp_stores
) s
where h.home_lat between s.store_lat - (s.radius / s.distance_unit) and s.store_lat + (s.radius / s.distance_unit)
and h.home_long between s.store_long - (s.radius / (s.distance_unit * cos(s.deg2rad * (s.store_lat)))) and s.store_long + (s.radius / (s.distance_unit * cos(s.deg2rad * (s.store_lat))))
)
where distance <= radius -- 32.1869km = 20.00mi
;
如果我为少量记录运行此查询,它会很好地工作。不幸的是,当我对相当一部分工作数据进行测试时,它需要几个小时才能运行。我可以使用哪些修改或技巧来显着加快此查询的运行速度?
笔记
当前状态下的查询将返回与 20 英里半径内的 HOME_ID 关联的所有 STORE_ID。下一步是按每个 HOME_ID 的距离对输出进行排序,并选择到商店距离最短的记录。作为参考,该查询如下所示:
select home_id, store_id, distance
from (
select
hs.*,
row_number() over(partition by home_id order by distance asc) as distance_rank
from tmp_homes_to_stores hs
)
where distance_rank = 1
;