几何操作符与函数
上一节认识了七种几何类型的输入格式,本节讲怎样真正用起来:用操作符判断包含、重叠、相对位置与距离,用函数算面积、中心、长度、点数,以及把一种几何类型转换成另一种。学完本节,你将能用 box 做二维范围判断、用 point 做距离排序,而不必自己手写几何算法。
前置知识
阅读本节前,建议先了解:几何类型概述
基础概念
几何类型的能力由两部分组成:
| 能力载体 | 形式 | 示例 | 说明 |
|---|---|---|---|
| 操作符 | 中缀 / 前缀符号 | box && box、@@ circle | 表达"关系"与"简单运算",可被索引加速 |
| 函数 | 具名函数 | area(polygon)、npoints(path) | 表达"计算"与"转换",需要明确参数类型 |
| 构造函数 | 具名函数 | point(3, 4)、circle(p, r) | 用表达式动态拼装几何值,等价于字面量 |
| 类型转换 | 具名函数 | box(circle)、polygon(path) | 不同几何类型之间的相互转换 |
理解操作符前先要建立包围盒(Bounding Box) 的概念:
polygon '((0,0),(2,0),(2,1),(0,1))' 的包围盒
y
1 +--------+
| |
| 多边形 |
0 +--------+----- x
0 2
包围盒 = box '(2,1),(0,0)' ← 恰好等于该多边形包围盒是"能装下该图形的最小正矩形"。所有几何类型都有一个包围盒,&&、<<、>>、&<、&> 这一族操作符只比较包围盒,不做精确几何判定。这是几何操作符最容易被误解的地方:它们全部是"快筛"而非"精判"。
术语提示
操作符(Operator)、前缀操作符(Prefix Operator)、包围盒(Bounding Box)、几何函数(Geometric Function)、类型转换(Type Conversion)。@> / <@ 是本节唯一能做精确包含判定的操作符对。
语法与用法
操作符总表
PostgreSQL 18 官方文档中几何操作符的完整列表如下:
| 操作符 | 形式 | 含义 | 示例 |
|---|---|---|---|
+ | 中缀 | 平移(加法) | box '((0,0),(1,1))' + point '(2,0)' |
- | 中缀 | 平移(减法) | box '((0,0),(1,1))' - point '(2,0)' |
* | 中缀 | 缩放(乘法) | box '((0,0),(1,1))' * point '(2,2)' |
/ | 中缀 | 缩放(除法) | box '((0,0),(2,2))' / point '(2,2)' |
@-@ | 前缀 | 长度或周长 | @-@ path '[(0,0),(1,1),(2,0)]' |
@@ | 前缀 | 中心点 | @@ circle '<(1,2),3>' |
# | 前缀 | 点的个数 | # path '[(0,0),(1,1)]' |
@> | 中缀 | 左包含右 | box '((0,0),(2,2))' @> point '(1,1)' |
<@ | 中缀 | 左被右包含 | point '(1,1)' <@ circle '<(0,0),5>' |
&& | 中缀 | 包围盒重叠 | box '((0,0),(1,1))' && box '((1,1),(2,2))' |
<< | 中缀 | 严格在左侧 | box '((0,0),(1,1))' << box '((2,2),(3,3))' |
>> | 中缀 | 严格在右侧 | box '((2,2),(3,3))' >> box '((0,0),(1,1))' |
&< | 中缀 | 右边界不超出对方左边界 | box '((0,0),(1,1))' &< box '((0,0),(2,2))' |
&> | 中缀 | 左边界不超出对方右边界 | box '((0,0),(2,2))' &> box '((0,0),(1,1))' |
| `&< | ` | 中缀 | 不延伸到对方上方 |
|&> | 中缀 | 不延伸到对方下方 | box '((0,0),(2,2))' |&> box '((0,0),(1,1))' |
<<| | 中缀 | 严格在下方 | box '((0,0),(1,1))' <<| box '((0,2),(1,3))' |
|>> | 中缀 | 严格在上方 | box '((0,2),(1,3))' |>> box '((0,0),(1,1))' |
<-> | 中缀 | 两者间最短距离 | point '(0,0)' <-> point '(3,4)' |
<^> | 中缀 | 两包围盒中心间距离 | box '((0,0),(1,1))' <^> box '((4,4),(5,5))' |
| `? | ` | 中缀 | |
?# | 中缀 | 相交或重叠 | lseg '[(0,0),(1,1)]' ?# lseg '[(0,1),(1,0)]' |
?- | 中缀 | 水平 | lseg '[(0,0),(1,0)]' ?- lseg '[(2,0),(3,0)]' |
?| | 中缀 | 垂直 | lseg '[(0,0),(0,1)]' ?| lseg '[(2,0),(2,3)]' |
| `?- | ` | 中缀 | 两线段垂直 |
?|? | 中缀 | 两线垂直 | — |
~= | 中缀 | 完全相同 | polygon '((0,0),(1,1),(1,0))' ~= polygon '((0,0),(1,1),(1,0))' |
# | 中缀 | 两直线的交点 | line '{1,-1,0}' # line '{0,1,0}' |
# 有两个含义
# 作为前缀是"点的个数",作为中缀是"两直线交点"。同一个符号两种用法,靠操作数个数区分:
SELECT # path '[(0,0),(1,1),(2,0)]' AS n_points, -- 前缀:3
line '{1,-1,0}' # line '{0,1,0}' AS cross_pt; -- 中缀:(0,0)函数总表
几何函数按用途分为四组。
形状计算类:
| 函数 | 参数类型 | 返回 | 说明 |
|---|---|---|---|
area(object) | box / circle / path / polygon | double precision | 面积 |
center(object) | box / circle / lseg / path / polygon | point | 几何中心 |
diameter(circle) | circle | double precision | 直径 |
radius(circle) | circle | double precision | 半径 |
length(path) | path | double precision | 路径长度 |
height(box) | box | double precision | 竖直方向高度 |
width(box) | box | double precision | 水平方向宽度 |
diagonal(box) | box | lseg | 对角线线段 |
npoints(path) / npoints(polygon) | path / polygon | integer | 点的个数 |
形状变换类:
| 函数 | 参数类型 | 返回 | 说明 |
|---|---|---|---|
pclose(path) | path | path | 转为闭合路径 |
popen(path) | path | path | 转为开放路径 |
isclosed(path) | path | boolean | 是否闭合 |
isopen(path) | path | boolean | 是否开放 |
构造与转换类:
| 函数 | 返回 | 说明 |
|---|---|---|
point(double precision, double precision) | point | 由坐标构造点 |
point(box) / point(circle) / point(lseg) / point(polygon) | point | 取中心点 |
box(point, point) / box(point) | box | 由两点 / 单点构造矩形 |
box(circle) / box(polygon) | box | 紧凑包围盒 |
circle(point, double precision) | circle | 由圆心与半径构造圆 |
circle(box) | circle | 圆 = 内接/外切于矩形 |
circle(polygon) | circle | 多边形外接圆 |
lseg(point, point) | lseg | 由两点构造线段 |
lseg(box) | lseg | 矩形对角线 |
path(polygon) | path | 多边形转闭合路径 |
polygon(box) | polygon | 矩形转 4 边形 |
polygon(circle) | polygon | 圆转 12 边正多边形 |
polygon(integer, circle) | polygon | 圆转 n 边正多边形 |
polygon(path) | polygon | 闭合路径转多边形 |
关于缩放与取整,PostgreSQL 的几何类型没有 scale() / translate() / round() 函数:
- 平移用
+/-操作符(第二个操作数是point)。 - 缩放用
*//操作符(第二个操作数是point)。 - 取整需要先取出坐标再用
round():SELECT (round((p)[0]), round((p)[1])) FROM ...。
scale() 与 translate() 是 PostGIS 的函数名(ST_Scale / ST_Translate),别混淆。
详细说明
平移与缩放:+ - * /
四个算术操作符只对 box / circle / point / path 等类型定义,且第二个操作数必须是 point:这个 point 的 x、y 分别代表两个维度上的位移量或倍率。
-- 平移:矩形整体向右 2、向上 3
SELECT box '((0,0),(1,1))' + point '(2,3)' AS moved;
-- 结果:(3,4),(2,3)
-- 原点矩形平移:point 直接与 point 相加
SELECT point '(1,1)' + point '(2,3)' AS p2; -- (3,4)
-- 缩放:矩形两个维度都放大 2 倍
SELECT box '((0,0),(1,1))' * point '(2,2)' AS scaled;
-- 结果:(2,2),(0,0) moved | p2 | scaled
-------------+-----+--------
(3,4),(2,3) | (3,4) | (2,2),(0,0)注意 box 的输出顺序永远是 (右上角),(左下角),所以 ((0,0),(1,1)) + (2,3) 输出为 (3,4),(2,3) 而不是 (2,3),(3,4)。
包含与重叠:@> <@ &&
这三个是业务代码里出现频率最高的:
-- @> 左包含右:点是否落在矩形内
SELECT box '((0,0),(10,10))' @> point '(3,4)' AS in_box,
box '((0,0),(10,10))' @> point '(11,4)' AS out_box;
-- <@ 左被右包含:同一条判断反向写
SELECT point '(3,4)' <@ box '((0,0),(10,10))' AS inside;
-- && 包围盒重叠:两图形是否有交集的可能
SELECT box '((0,0),(2,2))' && box '((1,1),(3,3))' AS overlap,
box '((0,0),(1,1))' && box '((5,5),(6,6))' AS separate; in_box | out_box | inside | overlap | separate
--------+---------+--------+---------+----------
t | f | t | t | f@> 与 <@ 会做真实的几何判定(对 polygon、circle 等复杂图形是有代价的),而 && 只比包围盒:
-- && 的"假阳性":两个三角形的包围盒重叠,但图形本身不相交
SELECT polygon '((0,0),(10,0),(0,10))' && polygon '((10,10),(0,10),(10,0))' AS bbox_overlap;
-- 结果:t(包围盒都是 (10,10),(0,0))
-- @> 能给出正确答案
SELECT polygon '((0,0),(10,0),(0,10))' @> point '(8,8)' AS contains_pt;
-- 结果:f因此标准的空间查询是两段式:先用 && 走 GiST 索引快速筛掉绝大多数行,再用 @> / <@ 精判。
相对位置:<< >> &< &> <<| |>>
这六个操作符回答"两者在什么方位",全部基于包围盒:
| 操作符 | 读法 | 判定条件(box A 与 box B) |
|---|---|---|
<< | A 严格在 B 左侧 | A 的右边界 < B 的左边界 |
>> | A 严格在 B 右侧 | A 的左边界 > B 的右边界 |
&< | A 不延伸到 B 右侧 | A 的右边界 <= B 的右边界 |
&> | A 不延伸到 B 左侧 | A 的左边界 >= B 的左边界 |
<<| | A 严格在 B 下方 | A 的上边界 < B 的下边界 |
|>> | A 严格在 B 上方 | A 的下边界 > B 的上边界 |
&<| | A 不延伸到 B 上方 | A 的上边界 <= B 的上边界 |
|&> | A 不延伸到 B 下方 | A 的下边界 >= B 的下边界 |
-- 注意"严格"与"不延伸"的区别
SELECT box '((0,0),(1,1))' << box '((1,0),(2,1))' AS strictly_left, -- f!贴着不算严格在左
box '((0,0),(1,1))' &< box '((1,0),(2,1))' AS not_ext_right, -- t
box '((0,0),(1,1))' << box '((2,0),(3,1))' AS gap_left; -- t strictly_left | not_ext_right | gap_left
---------------+---------------+----------
f | t | t这两个操作符对"时间/区间排布类"的二维场景很有用,例如判断图块是否在左侧而不越界。
距离:<-> 与 <^>
<-> 返回两个图形之间的最短距离,是几何类型里唯一能做精确距离计算的操作符:
SELECT point '(0,0)' <-> point '(3,4)' AS pt_pt, -- 5
point '(0,0)' <-> '[(0,0),(0,10)]'::lseg AS pt_seg, -- 0(点在线上)
point '(5,0)' <-> '[(0,0),(0,10)]'::lseg AS pt_seg2, -- 5
box '((0,0),(1,1))' <-> box '((4,0),(5,1))' AS box_box, -- 3
'<(0,0),1>'::circle <-> '<(10,0),2>'::circle AS cir_cir; -- 7 pt_pt | pt_seg | pt_seg2 | box_box | cir_cir
-------+--------+---------+---------+---------
5 | 0 | 5 | 3 | 7<^> 则只算两个包围盒中心点之间的距离,代价低但语义粗糙:
SELECT box '((0,0),(1,1))' <^> box '((4,4),(5,5))' AS center_dist;
-- 中心 (0.5,0.5) 与 (4.5,4.5) 的距离 center_dist
------------------
5.65685424949238别混用 <-> 与 <^>
<-> 是真实最短距离,<^> 是中心点距离。对两个相距很近但很大的矩形,<-> 可能接近 0,而 <^> 会很大。排序、阈值判断必须用 <->。
前缀操作符:@@ @-@ #
前缀操作符只有一个操作数,写在操作数左侧:
-- @@ 取中心点
SELECT @@ circle '<(1,2),3>' AS c_center,
@@ box '((0,0),(4,6))' AS b_center,
@@ lseg '[(0,0),(4,6)]' AS s_center;
-- @-@ 取长度 / 周长
SELECT @-@ path '[(0,0),(3,4)]' AS path_len, -- 5
@-@ path '((0,0),(3,0),(3,4),(0,4))' AS poly_peri; -- 14
-- # 取点的个数
SELECT # path '[(0,0),(1,1),(2,0)]' AS open_n,
# polygon '((0,0),(1,0),(1,1))' AS poly_n,
# box '((0,0),(1,1))' AS box_n; c_center | b_center | s_center | path_len | poly_peri | open_n | poly_n | box_n
----------+----------+----------+----------+-----------+--------+--------+-------
(1,2) | (2,3) | (2,3) | 5 | 14 | 3 | 3 | 4# 前缀与 @@ 的结果与对应函数等价:
| 前缀写法 | 等价的函数写法 |
|---|---|
@@ box '(...)' | center(box '(...)') |
@-@ path '[...]' | length(path '[...]') |
@-@ path '((...))' | 无函数等价,需 length + 判断闭合 |
# polygon '(...)' | npoints(polygon '(...)') |
形状计算函数
-- 面积:只有 box / circle / path(闭合) / polygon 有意义
SELECT area(box '((0,0),(4,3))') AS box_area, -- 12
area(circle '<(0,0),3>') AS circle_area, -- 28.27...
area(polygon '((0,0),(4,0),(4,3),(0,3))') AS poly_area; -- 12
-- 圆相关
SELECT radius(circle '<(0,0),3>') AS r,
diameter(circle '<(0,0),3>') AS d;
-- 矩形尺寸
SELECT width(box '((0,0),(4,3))') AS w,
height(box '((0,0),(4,3))') AS h,
diagonal(box '((0,0),(4,3))') AS diag,
@-@ box '((0,0),(4,3))' AS peri;
-- 路径长度与点数
SELECT length(path '[(0,0),(3,4),(3,0)]') AS len,
npoints(path '[(0,0),(3,4),(3,0)]') AS n; box_area | circle_area | poly_area | r | d | w | h | diag | peri | len | n
----------+------------------+-----------+---+---+---+---+---------+------+-----+---
12 | 28.2743338823081 | 12 | 3 | 6 | 3 | 4 | [(0,0),(4,3)] | 14 | 8 | 3area() 需要参数类型明确,PostgreSQL 会用隐式类型转换匹配重载。当参数是表达式时,最好显式转换:
-- 参数类型不明确时报错
-- 错误!unknown 类型无法确定调用哪个重载
SELECT area('((0,0),(4,3))');
-- 正确:显式转换
SELECT area('((0,0),(4,3))'::box);路径的开闭:isclosed / pclose / popen
path 是唯一有"开闭"状态的几何类型,这三个函数用来查改状态:
-- 开放路径:方括号写、首尾点不同
SELECT isclosed(path '[(0,0),(3,0),(3,4),(0,4)]') AS closed_1, -- f
isopen(path '[(0,0),(3,0),(3,4),(0,4)]') AS open_1, -- t
isclosed(path '((0,0),(3,0),(3,4),(0,4))') AS closed_2, -- t
isopen(path '((0,0),(3,0),(3,4),(0,4))') AS open_2; -- f
-- 转换
SELECT pclose(path '[(0,0),(3,0),(3,0),(0,0)]') AS to_closed,
popen(path '((0,0),(3,0),(3,4),(0,4))') AS to_open,
length(path '((0,0),(3,0),(3,4),(0,4))') AS peri_of_closed; closed_1 | open_1 | closed_2 | open_2 | to_closed | to_open
----------+--------+----------+--------+-------------------------+-----------------------
f | t | t | f | ((0,0),(3,0),(3,0),(0,0)) | [(0,0),(3,0),(3,4),(0,4)]length() 对闭合路径会把首尾闭合边也算进去,所以上一个例子里正方的闭合路径长度是 3+4+3+4 = 14。
构造与转换函数
构造函数让几何值可以来自列、变量或计算结果,转换函数则在不同类型之间搭桥。
-- 构造:从数值坐标出发
SELECT point(3.5, 4.5) AS pt,
lseg(point(0,0), point(3,4)) AS seg,
box(point(0,0), point(4,3)) AS rect,
circle(point(2,2), 3) AS cir;
-- 由复杂图形抽取中心点
SELECT point(polygon '((0,0),(4,0),(4,3),(0,3))') AS poly_center, -- (2,1.5)
point(box '((0,0),(4,3))') AS box_center; -- (2,1.5)
-- 包围盒转换:任意图形 → 最小矩形
SELECT box(polygon '((0,0),(4,0),(4,3),(0,3))') AS b1,
box(circle '<(2,2),3>') AS b2,
box(path '[(0,0),(3,4),(5,1)]') AS b3; pt | seg | rect | cir
-----------+-------------+-------------+-------------------
(3.5,4.5) | [(0,0),(3,4)] | (4,3),(0,0) | <(2,2),3>
poly_center | box_center | b1 | b2 | b3
-------------+------------+-----------+-------------+-----------
(2,1.5) | (2,1.5) | (4,3),(0,0) | (5,5),(-1,-1) | (5,4),(0,0)box(circle) 返回的是外切矩形:圆心 ± 半径。所以 < (2,2),3 > 的包围盒是 (5,5),(-1,-1)。
圆与多边形的互转也值得记住:
-- 多边形 → 外接圆
SELECT circle(polygon '((0,0),(4,0),(4,3),(0,3))') AS cir_of_box;
-- 结果:<(2,1.5),2.5>
-- 圆 → 多边形(默认 12 边)
SELECT npoints(polygon(circle '<(0,0),1>')) AS default_sides, -- 12
npoints(polygon(4, circle '<(0,0),1>')) AS square_sides; -- 4polygon(circle) 是"用 n 个顶点近似圆",顶点数越多越接近圆。
比较操作符与去重
几何类型的 = 语义不是按坐标逐字比较:
| 类型 | = 的比较依据 | 有 < > 吗 |
|---|---|---|
point | 坐标对(PostgreSQL 9.4+) | 无 |
box | 面积 | 有(按面积) |
circle | 面积 | 有(按面积) |
polygon | 面积 | 有(按面积) |
path | 包围盒 + 几何 | 有 |
lseg | 端点坐标 | 有(按几何长度) |
line | 系数 A、B、C | 有(字典序) |
-- 面积相同的不同图形被判为"相等",这通常不是业务想要的结果
SELECT circle '<(0,0),2>'::circle = circle '<(5,5),2>'::circle AS same_by_area; -- t!
SELECT circle '<(0,0),2>'::circle ~= circle '<(5,5),2>'::circle AS really_same; -- f~= 才是"图形完全相同"。业务去重、唯一约束都应该用 ~= 思路(或存文本/坐标哈希),不要依赖 =。
-- PostgreSQL 11+:box 支持 GROUP BY / DISTINCT?不支持!
-- 错误!几何类型没有默认 B-tree 操作符类,不能直接 DISTINCT
SELECT DISTINCT circle FROM some_table;需要去重时,把几何值转成文本再比:
SELECT DISTINCT circle::text AS c FROM some_table;实战示例
案例一:用 box 做二维范围判断(库位管理)
DROP TABLE IF EXISTS warehouse_slots;
CREATE TABLE warehouse_slots (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
code TEXT NOT NULL UNIQUE,
area_box BOX NOT NULL, -- 库位所占的矩形区域
zone TEXT NOT NULL
);
INSERT INTO warehouse_slots (code, area_box, zone) VALUES
('A-01', box(point(0,0), point(2,3)), 'A 区'),
('A-02', box(point(2,0), point(4,3)), 'A 区'),
('B-01', box(point(0,5), point(6,8)), 'B 区');
CREATE INDEX idx_slots_box ON warehouse_slots USING GIST (area_box);
-- 查询 1:某个坐标落在哪些库位
SELECT code, zone
FROM warehouse_slots
WHERE area_box @> point(3, 1)
ORDER BY code; code | zone
------+------
A-02 | A 区
(1 row)-- 查询 2:与目标矩形有重叠的库位(先 && 快筛)
SELECT code, zone, area_box
FROM warehouse_slots
WHERE area_box && box(point(1,1), point(3,2))
ORDER BY code; code | zone | area_box
------+------+-------------
A-01 | A 区 | (2,3),(0,0)
A-02 | A 区 | (4,3),(2,0)
(2 rows)-- 查询 3:库位面积与尺寸一览
SELECT code,
area(area_box) AS area,
width(area_box) AS w,
height(area_box) AS h,
@@ area_box AS center
FROM warehouse_slots
ORDER BY code; code | area | w | h | center
------+------+---+---+--------
A-01 | 6 | 2 | 3 | (1,1.5)
A-02 | 6 | 2 | 3 | (3,1.5)
B-01 | 18 | 6 | 3 | (3,6.5)案例二:用 point 做距离排序(门店就近查询)
DROP TABLE IF EXISTS shops;
CREATE TABLE shops (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
location POINT NOT NULL -- 平面坐标,1 单位 = 1 米
);
INSERT INTO shops (name, location) VALUES
('中央店', point(100, 100)),
('东街店', point(130, 100)),
('西巷店', point(60, 80)),
('北苑店', point(100, 400)),
('南湖店', point(150, 20));
CREATE INDEX idx_shops_loc ON shops USING GIST (location);
-- 查询 1:距 (105,95) 最近的 3 家店
SELECT name,
round((location <-> point(105,95))::numeric, 2) AS dist_m
FROM shops
ORDER BY location <-> point(105,95)
LIMIT 3; name | dist_m
--------+--------
中央店 | 7.07
东街店 | 25.50
西巷店 | 47.17
(3 rows)-- 查询 2:3 公里(3000 单位)内的门店,按距离升序
SELECT name,
round((location <-> point(105,95))::numeric, 2) AS dist_m
FROM shops
WHERE location <-> point(105,95) < 3000
ORDER BY dist_m; name | dist_m
--------+---------
中央店 | 7.07
东街店 | 25.50
西巷店 | 47.17
南湖店 | 85.44
北苑店 | 305.16
(5 rows)-- 查询 3:用 EXPLAIN 确认走了 GiST 的 KNN 排序
EXPLAIN (ANALYZE, COSTS OFF)
SELECT name FROM shops ORDER BY location <-> point(105,95) LIMIT 3; QUERY PLAN
-------------------------------------------------------
Limit (actual time=0.032..0.041 rows=3 loops=1)
-> Index Scan using idx_shops_loc on shops
Order By: (location <-> '(105,95)'::point)
(actual time=0.028..0.036 rows=3 loops=1)
Planning Time: 0.121 ms
Execution Time: 0.062 ms<-> 走索引的前提
ORDER BY location <-> 常量 这种形式才能利用 GiST 的 KNN 能力。写成 ORDER BY (location <-> $1)(参数化查询)在部分场景下也能走,但如果把距离包进表达式(如 ORDER BY round((location <-> $1)::numeric, 2))就一定会退化为全表扫描 + 排序。
案例三:路径长度与闭合判断
-- 行车轨迹:把轨迹点拼成 path,算总里程
SELECT @-@ path '[(0,0),(3,4),(13,4)]' AS total_len,
isopen(path '[(0,0),(3,4),(13,4)]') AS is_open,
@-@ pclose(path '[(0,0),(3,4),(13,4)]') AS closed_len; total_len | is_open | closed_len
-----------+---------+------------
15 | t | 25.63注意事项
&&只比包围盒,不是精确相交。 返回t只代表"外框有重叠",必须配合@>/~=/ 几何函数精判。- 算术操作符的第二个操作数必须是
point。box + box不存在,box + point才是平移。 box输出顺序固定为(右上角),(左下角)。 不要按字面顺序读成"左下、右上"。<<是"严格在左",贴着不算。 两个矩形共享边界时<<返回f,而&<返回t。<^>是中心点距离,不是最短距离。 用错会得出完全错误的结果。=对box/circle/polygon是按面积比较。 面积相同即"相等",业务语义上几乎总是错的。
-- 错误!以为在做几何相同判断,实际是按面积
SELECT * FROM t WHERE circle_col = circle '<(0,0),1>';
-- 正确:按具体坐标判断(或存文本列做唯一约束)
SELECT * FROM t WHERE circle_col ~= circle '<(0,0),1>';- 几何类型没有默认 B-tree 操作符类。
DISTINCT、GROUP BY、ORDER BY、唯一索引都不能直接用,需要btree_gist扩展或转文本。 area()/length()对参数类型敏感。 传字符串字面量会因unknown类型而报"函数不存在",必须显式::box/::path。polygon(circle)默认 12 边。 用它做面积计算会低估圆面积,需要精度就用area(circle)。point不支持</>。 想按坐标排序必须先拆成两个标量。
-- 正确:拆坐标后排序
SELECT name FROM shops ORDER BY (location)[0], (location)[1];
-- 错误!point 没有排序操作符
SELECT name FROM shops ORDER BY location;
-- 报错:could not identify an ordering operator for type pointline与lseg的#用法不同。line # line返回交点point;lseg ?# lseg返回布尔"是否相交"。- 几何字面量中的负号要紧贴数字。
'(-1,-2)'合法,'(- 1,- 2)'会解析失败。
最佳实践
- 建立"
&&快筛 +@>精判"的两段式查询习惯。 这是唯一能同时保证正确性与性能的写法。 - 热查询列一律建 GiST 索引。
<->排序、&&重叠、@>包含都能受益;SP-GiST 在点数据量极大时可作备选。 ORDER BY geom <-> 常量里不要包表达式。 保持操作符表达式原样,才能让规划器用上 KNN。box单独存一列做包围盒缓存。 复杂polygon先按box列粗筛,再对polygon精判,性能提升明显。- 用
round((a <-> b)::numeric, 2)控制小数位数。double precision直接输出会出现7.0710678118654755这种长串,报表里应截断。 - 业务唯一性不要依赖几何
=。 用~=,或者把几何值::text存成额外列加唯一索引。 - 统一坐标单位并在列注释里写清楚。 比如
COMMENT ON COLUMN shops.location IS '平面坐标,1 单位 = 1 米'。 - 能用
box就别用polygon。 轴对齐的矩形区域用box判断更快、更省空间、语义更清晰。 - 注意前缀操作符的可读性。
@@ box '(...)'对不了解的人来说很费解,团队内应统一风格,必要时改用center()/length()函数名写法。 - 真要地理计算就换 PostGIS。 一旦需要用
<->得到"米"、需要坐标系转换,说明内置类型已经不够用了。
下一节
参考链接
- PostgreSQL 官方文档:Geometric Functions and Operators
- PostgreSQL 官方文档:Geometric Types
- PostgreSQL 官方文档:GiST Indexes
- PostgreSQL 官方文档:Index Types