0Pricing
SQL Academy · 课时

距离与最近邻

查找附近的对象。

距离与最近邻 是 CoddyKit 上的免费 SQL Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Academy 课程共包含 4 节课。

什么是空间距离

在地理空间数据库中,距离是两个地理位置之间测量得到的间隔。PostGIS 提供了强大的函数,用于计算点、线、多边形和其他几何类型之间的距离。

理解距离查询后,您就能回答这样的问题:离我最近的餐厅是哪一家? 或 哪些客户距离我们的仓库在 5 千米以内?

ST_Distance 函数

ST_Distance(geom_a, geom_b) 返回两个几何对象之间的最小距离。默认情况下,使用普通几何类型时,结果的单位与坐标参考系相同(对于 EPSG:4326,通常为度)。

若要获得以米为单位的有意义结果,您应使用地理类型或重新投影数据。

SELECT ST_Distance(
  ST_MakePoint(28.9784, 41.0082)::geography,
  ST_MakePoint(29.0100, 41.0200)::geography
) AS distance_meters;

设置示例表

现在创建一个包含地理坐标的简单地点表。我们将使用地理类型,这样所有距离计算都会自动返回米,无需手动进行投影。

此表将存储某个城市中的咖啡馆,每家咖啡馆都有名称和以点形式存储的位置。

CREATE TABLE cafes (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  location GEOGRAPHY(Point, 4326)
);

INSERT INTO cafes (name, location) VALUES
  ('Cafe Alpha',   ST_MakePoint(28.9784, 41.0082)::geography),
  ('Cafe Beta',    ST_MakePoint(28.9900, 41.0150)::geography),
  ('Cafe Gamma',   ST_MakePoint(29.0100, 41.0200)::geography),
  ('Cafe Delta',   ST_MakePoint(28.9650, 40.9980)::geography),
  ('Cafe Epsilon', ST_MakePoint(29.0250, 41.0350)::geography);

计算到固定点的距离

有了位置数据后,您可以计算每一行到参考点的距离。这里我们计算每家咖啡馆与坐标为 (28.9800, 41.0100) 的用户之间的距离。

由于使用了地理类型,结果的单位是米。

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
ORDER BY distance_m;

查找最近邻

要查找距离给定点最近的单个位置,请按距离排序,并使用 LIMIT 1 只取一行。这是经典的最近邻查询。

这种模式在位置感知应用中非常常见,例如查找最近的商店、医生或公共交通站点。

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography
LIMIT 1;

用于最近邻查询的 <-> 运算符

PostGIS 提供了 <-> 运算符(KNN——K 个最近邻),该运算符可以利用索引。与在 ORDER BY 子句中使用 ST_Distance 不同,<-> 可以使用 GiST 空间索引,避免扫描每一行。

需要高效获取最近邻时,请始终优先在 ORDER BY 中使用 <->。创建如下索引即可支持该操作:

CREATE INDEX ON cafes USING GIST (location);

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography
LIMIT 5;

按距离筛选(ST_DWithin)

当两个几何对象之间的距离在指定范围内时,ST_DWithin(geom_a, geom_b, distance) 返回 TRUE。对于地理类型,距离的单位是米。

与在 WHERE 子句中使用 ST_Distance(...) < radius 相比,这种方式效率更高,因为 ST_DWithin 可以利用索引,并会在满足条件后立即停止。

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
WHERE ST_DWithin(
  location,
  ST_MakePoint(28.9800, 41.0100)::geography,
  2000
)
ORDER BY distance_m;

带半径限制的前 N 个最近位置

一种常见的生产环境模式是将 ST_DWithin 作为预筛选条件,并结合 <-> 排序。ST_DWithin 检查会利用空间索引快速排除距离过远的行,然后按精确距离对剩余候选项进行排序。

这样就能非常高效地获取最大半径内距离最近的 N 个位置。

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
WHERE ST_DWithin(
  location,
  ST_MakePoint(28.9800, 41.0100)::geography,
  5000
)
ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography
LIMIT 3;

两个表之间的距离(跨表距离)

您可以将 JOIN 与 ST_Distance 结合起来,计算两个不同表中各行之间的距离。这对于匹配非常有用,例如将每位客户匹配到最近的仓库,或将每起事件匹配到最近的医院。

下面我们使用 LATERAL 连接,为 users 表中的每位用户查找最近的咖啡馆。这是一种强大的 PostgreSQL 模式,可用于执行逐行子查询。

SELECT
  u.username,
  c.name AS nearest_cafe,
  ROUND(ST_Distance(u.location, c.location)::NUMERIC) AS distance_m
FROM (
  VALUES
    ('alice', ST_MakePoint(28.9810, 41.0095)::geography),
    ('bob',   ST_MakePoint(29.0200, 41.0300)::geography)
) AS u(username, location)
CROSS JOIN LATERAL (
  SELECT name, location
  FROM cafes
  ORDER BY location <-> u.location
  LIMIT 1
) c;

格式化距离输出

以米为单位的原始距离可能不易阅读。您可以使用 CASE 表达式和字符串格式化,将距离格式化为千米或添加便于理解的标签。下面的查询会将较短距离以米显示,将较长距离以千米显示。

SELECT
  name,
  CASE
    WHEN ST_Distance(location, ST_MakePoint(28.9800, 41.0100)::geography) < 1000
    THEN ROUND(ST_Distance(location, ST_MakePoint(28.9800, 41.0100)::geography)::NUMERIC)
         || ' m'
    ELSE ROUND((ST_Distance(location, ST_MakePoint(28.9800, 41.0100)::geography) / 1000.0)::NUMERIC, 2)
         || ' km'
  END AS formatted_distance
FROM cafes
ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography;

按距离分配排名

RANK() 和 ROW_NUMBER() 等窗口函数与距离查询非常匹配。您可以为相对于用户的每个位置分配邻近排名,这对于构建按接近程度排序的排名推荐列表或搜索结果非常有用。

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m,
  ROW_NUMBER() OVER (
    ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography
  ) AS proximity_rank
FROM cafes;

知识测验

检验您对 PostGIS 距离查询和最近邻查找的理解。

课程回顾

在本课中,您学习了如何在 PostGIS 中处理空间距离和最近邻查询:

  • ST_Distance 计算两个几何对象之间的精确距离;使用地理类型即可自动获得米制结果。
  • ST_DWithin 利用空间索引高效筛选半径范围内的行——在 WHERE 子句中优先使用它,而不是 ST_Distance < radius。
  • ORDER BY 中的 <-> 运算符(KNN)可以利用索引,是获取距离最近的 N 个邻居的最快方式。
  • 将 ST_DWithin 作为预筛选条件,并结合 <-> 排序,可以最高效地执行半径内前 N 个查询。
  • CROSS JOIN LATERAL 可以让您按行查找另一个表中的最近匹配项。
  • 使用 ROW_NUMBER() 等窗口函数,可以为按距离排序的结果分配邻近排名。

这些模式构成了现实应用中位置感知功能的基础。

常见问题解答

「距离与最近邻」课时是免费的吗?

是的 — 「距离与最近邻」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Academy 课程的其余内容,请升级到 CoddyKit PRO。 SQL Academy 课程共包含 4 节课。

「距离与最近邻」这节课中我会学到什么?

查找附近的对象。 你通过在浏览器中直接运行的动手代码来练习 SQL Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 SQL Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 SQL Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「距离与最近邻」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 SQL Academy 课中编写并运行代码吗?

能。每节 SQL Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 空间数据类型
  2. 距离与最近邻
  3. 空间连接与包含关系
  4. 空间索引(GiST)
← 返回 SQL Academy