Skip to content

几何操作符与函数 ​

上一节认识了七种几何类型的输入格式,本节讲怎样真正用起来:用操作符判断包含、重叠、相对位置与距离,用函数算面积、中心、长度、点数,以及把一种几何类型转换成另一种。学完本节,你将能用 box 做二维范围判断、用 point 做距离排序,而不必自己手写几何算法。

前置知识

阅读本节前,建议先了解:几何类型概述

基础概念 ​

几何类型的能力由两部分组成:

能力载体形式示例说明
操作符中缀 / 前缀符号box && box、@@ circle表达"关系"与"简单运算",可被索引加速
函数具名函数area(polygon)、npoints(path)表达"计算"与"转换",需要明确参数类型
构造函数具名函数point(3, 4)、circle(p, r)用表达式动态拼装几何值,等价于字面量
类型转换具名函数box(circle)、polygon(path)不同几何类型之间的相互转换

理解操作符前先要建立包围盒(Bounding Box) 的概念:

text
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}'

# 有两个含义

# 作为前缀是"点的个数",作为中缀是"两直线交点"。同一个符号两种用法,靠操作数个数区分:

sql
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 / polygondouble precision面积
center(object)box / circle / lseg / path / polygonpoint几何中心
diameter(circle)circledouble precision直径
radius(circle)circledouble precision半径
length(path)pathdouble precision路径长度
height(box)boxdouble precision竖直方向高度
width(box)boxdouble precision水平方向宽度
diagonal(box)boxlseg对角线线段
npoints(path) / npoints(polygon)path / polygoninteger点的个数

形状变换类:

函数参数类型返回说明
pclose(path)pathpath转为闭合路径
popen(path)pathpath转为开放路径
isclosed(path)pathboolean是否闭合
isopen(path)pathboolean是否开放

构造与转换类:

函数返回说明
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 分别代表两个维度上的位移量或倍率。

sql
-- 平移:矩形整体向右 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)
text
   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)。

包含与重叠:@> <@ && ​

这三个是业务代码里出现频率最高的:

sql
-- @> 左包含右:点是否落在矩形内
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;
text
 in_box | out_box | inside | overlap | separate
--------+---------+--------+---------+----------
 t      | f       | t      | t       | f

@> 与 <@ 会做真实的几何判定(对 polygon、circle 等复杂图形是有代价的),而 && 只比包围盒:

sql
-- && 的"假阳性":两个三角形的包围盒重叠,但图形本身不相交
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 的下边界
sql
-- 注意"严格"与"不延伸"的区别
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
text
 strictly_left | not_ext_right | gap_left
---------------+---------------+----------
 f             | t             | t

这两个操作符对"时间/区间排布类"的二维场景很有用,例如判断图块是否在左侧而不越界。

距离:<-> 与 <^> ​

<-> 返回两个图形之间的最短距离,是几何类型里唯一能做精确距离计算的操作符:

sql
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
text
 pt_pt | pt_seg | pt_seg2 | box_box | cir_cir
-------+--------+---------+---------+---------
     5 |      0 |       5 |       3 |       7

<^> 则只算两个包围盒中心点之间的距离,代价低但语义粗糙:

sql
SELECT box '((0,0),(1,1))' <^> box '((4,4),(5,5))' AS center_dist;
-- 中心 (0.5,0.5) 与 (4.5,4.5) 的距离
text
   center_dist
------------------
 5.65685424949238

别混用 <-> 与 <^>

<-> 是真实最短距离,<^> 是中心点距离。对两个相距很近但很大的矩形,<-> 可能接近 0,而 <^> 会很大。排序、阈值判断必须用 <->。

前缀操作符:@@ @-@ # ​

前缀操作符只有一个操作数,写在操作数左侧:

sql
-- @@ 取中心点
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;
text
 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 '(...)')

形状计算函数 ​

sql
-- 面积:只有 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;
text
 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 | 3

area() 需要参数类型明确,PostgreSQL 会用隐式类型转换匹配重载。当参数是表达式时,最好显式转换:

sql
-- 参数类型不明确时报错
-- 错误!unknown 类型无法确定调用哪个重载
SELECT area('((0,0),(4,3))');

-- 正确:显式转换
SELECT area('((0,0),(4,3))'::box);

路径的开闭:isclosed / pclose / popen ​

path 是唯一有"开闭"状态的几何类型,这三个函数用来查改状态:

sql
-- 开放路径:方括号写、首尾点不同
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;
text
 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。

构造与转换函数 ​

构造函数让几何值可以来自列、变量或计算结果,转换函数则在不同类型之间搭桥。

sql
-- 构造:从数值坐标出发
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;
text
   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)。

圆与多边形的互转也值得记住:

sql
-- 多边形 → 外接圆
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;    -- 4

polygon(circle) 是"用 n 个顶点近似圆",顶点数越多越接近圆。

比较操作符与去重 ​

几何类型的 = 语义不是按坐标逐字比较:

类型= 的比较依据有 < > 吗
point坐标对(PostgreSQL 9.4+)无
box面积有(按面积)
circle面积有(按面积)
polygon面积有(按面积)
path包围盒 + 几何有
lseg端点坐标有(按几何长度)
line系数 A、B、C有(字典序)
sql
-- 面积相同的不同图形被判为"相等",这通常不是业务想要的结果
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

~= 才是"图形完全相同"。业务去重、唯一约束都应该用 ~= 思路(或存文本/坐标哈希),不要依赖 =。

sql
-- PostgreSQL 11+:box 支持 GROUP BY / DISTINCT?不支持!
-- 错误!几何类型没有默认 B-tree 操作符类,不能直接 DISTINCT
SELECT DISTINCT circle FROM some_table;

需要去重时,把几何值转成文本再比:

sql
SELECT DISTINCT circle::text AS c FROM some_table;

实战示例 ​

案例一:用 box 做二维范围判断(库位管理) ​

sql
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;
text
 code | zone
------+------
 A-02 | A 区
(1 row)
sql
-- 查询 2:与目标矩形有重叠的库位(先 && 快筛)
SELECT code, zone, area_box
FROM warehouse_slots
WHERE area_box && box(point(1,1), point(3,2))
ORDER BY code;
text
 code | zone |  area_box
------+------+-------------
 A-01 | A 区 | (2,3),(0,0)
 A-02 | A 区 | (4,3),(2,0)
(2 rows)
sql
-- 查询 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;
text
 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 做距离排序(门店就近查询) ​

sql
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;
text
  name  | dist_m
--------+--------
 中央店 |   7.07
 东街店 |  25.50
 西巷店 |  47.17
(3 rows)
sql
-- 查询 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;
text
  name  | dist_m
--------+---------
 中央店 |    7.07
 东街店 |   25.50
 西巷店 |   47.17
 南湖店 |   85.44
 北苑店 |  305.16
(5 rows)
sql
-- 查询 3:用 EXPLAIN 确认走了 GiST 的 KNN 排序
EXPLAIN (ANALYZE, COSTS OFF)
SELECT name FROM shops ORDER BY location <-> point(105,95) LIMIT 3;
text
                      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))就一定会退化为全表扫描 + 排序。

案例三:路径长度与闭合判断 ​

sql
-- 行车轨迹:把轨迹点拼成 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;
text
 total_len | is_open | closed_len
-----------+---------+------------
        15 | t       |      25.63

注意事项 ​

  1. && 只比包围盒,不是精确相交。 返回 t 只代表"外框有重叠",必须配合 @> / ~= / 几何函数精判。
  2. 算术操作符的第二个操作数必须是 point。 box + box 不存在,box + point 才是平移。
  3. box 输出顺序固定为 (右上角),(左下角)。 不要按字面顺序读成"左下、右上"。
  4. << 是"严格在左",贴着不算。 两个矩形共享边界时 << 返回 f,而 &< 返回 t。
  5. <^> 是中心点距离,不是最短距离。 用错会得出完全错误的结果。
  6. = 对 box / circle / polygon 是按面积比较。 面积相同即"相等",业务语义上几乎总是错的。
sql
-- 错误!以为在做几何相同判断,实际是按面积
SELECT * FROM t WHERE circle_col = circle '<(0,0),1>';

-- 正确:按具体坐标判断(或存文本列做唯一约束)
SELECT * FROM t WHERE circle_col ~= circle '<(0,0),1>';
  1. 几何类型没有默认 B-tree 操作符类。 DISTINCT、GROUP BY、ORDER BY、唯一索引都不能直接用,需要 btree_gist 扩展或转文本。
  2. area() / length() 对参数类型敏感。 传字符串字面量会因 unknown 类型而报"函数不存在",必须显式 ::box / ::path。
  3. polygon(circle) 默认 12 边。 用它做面积计算会低估圆面积,需要精度就用 area(circle)。
  4. point 不支持 < / >。 想按坐标排序必须先拆成两个标量。
sql
-- 正确:拆坐标后排序
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 point
  1. line 与 lseg 的 # 用法不同。 line # line 返回交点 point;lseg ?# lseg 返回布尔"是否相交"。
  2. 几何字面量中的负号要紧贴数字。 '(-1,-2)' 合法,'(- 1,- 2)' 会解析失败。

最佳实践 ​

  1. 建立"&& 快筛 + @> 精判"的两段式查询习惯。 这是唯一能同时保证正确性与性能的写法。
  2. 热查询列一律建 GiST 索引。 <-> 排序、&& 重叠、@> 包含都能受益;SP-GiST 在点数据量极大时可作备选。
  3. ORDER BY geom <-> 常量 里不要包表达式。 保持操作符表达式原样,才能让规划器用上 KNN。
  4. box 单独存一列做包围盒缓存。 复杂 polygon 先按 box 列粗筛,再对 polygon 精判,性能提升明显。
  5. 用 round((a <-> b)::numeric, 2) 控制小数位数。 double precision 直接输出会出现 7.0710678118654755 这种长串,报表里应截断。
  6. 业务唯一性不要依赖几何 =。 用 ~=,或者把几何值 ::text 存成额外列加唯一索引。
  7. 统一坐标单位并在列注释里写清楚。 比如 COMMENT ON COLUMN shops.location IS '平面坐标,1 单位 = 1 米'。
  8. 能用 box 就别用 polygon。 轴对齐的矩形区域用 box 判断更快、更省空间、语义更清晰。
  9. 注意前缀操作符的可读性。 @@ box '(...)' 对不了解的人来说很费解,团队内应统一风格,必要时改用 center() / length() 函数名写法。
  10. 真要地理计算就换 PostGIS。 一旦需要用 <-> 得到"米"、需要坐标系转换,说明内置类型已经不够用了。

参考链接 ​