地理检索支持SQL合集
本文汇总地理检索相关的 DDL、数据导入方式、查询语法、空间索引行为和异常处理,供开发与测试人员查阅。
概览
Doris 是一款集地理检索、全文检索、向量检索和 OLAP 分析于一体的数据库。Doris 提供基于 SQL 的 GEO 地理数据写入、格式转换、空间计算、空间关系判断,以及 S2 和 RTREE 空间索引检索能力,可用于地理围栏、范围过滤、空间相交和距离判断等场景。
| 能力 | 当前支持 |
|---|---|
| 数据表示 | 使用 GEOMETRY 类型保存 Doris 编码后的 geometry。 |
| 几何构造 | 支持 Point、LineString、Polygon、WKT、WKB 相关构造和转换函数。 |
| 空间计算 | 支持坐标读取、球面距离、角度、方位角、面积、圆近似多边形。 |
| 空间关系 | 支持 contains、within、intersects、equals、touches、overlaps、crosses、dwithin。 |
| 空间索引 | 支持 USING SPATIAL + algorithm='s2' 和 algorithm='rtree'。S2 提供精确空间索引检索;RTREE 基于二维最小外接矩形(MBR)预过滤,并由原 GEO 谓词完成精确判断。 |
使用流程
- 创建
GEOMETRY列,并根据场景选择 S2 或 RTREE 空间索引。 - 使用 GEO 构造函数,将 WKT 或坐标转换为 Doris geometry 编码后写入目标列。
- 使用
ST_Contains、ST_Within、ST_Intersects或ST_DWithin等函数执行空间查询。 - 对比开启和关闭空间索引时的结果,确认结果一致,并通过扫描行数和执行时间评估索引效果。
- 查询异常时,参考“异常和边界行为”检查参数、数据类型和 geometry 编码。
选择空间索引
| 索引 | 工作方式 | 适用说明 |
|---|---|---|
| S2 | 使用 S2 空间单元执行精确空间索引检索 | 适用于需要使用空间关系谓词进行精确检索的场景 |
| RTREE | 先使用二维最小外接矩形(MBR)过滤候选行,再由原 ST_* 谓词完成精确判断 |
适用于通过 MBR 减少扫描行数的空间过滤场景 |
无论使用 S2 还是 RTREE,最终结果都由原空间谓词决定。空间索引用于减少扫描和计算开销,不应改变查询结果。
创建表和空间索引
DDL 能力速查
| 场景 | SQL 形态 | 当前状态 |
|---|---|---|
| 普通 geometry 列 | 建表时定义 geo GEOMETRY |
正常 |
| 建表时带 S2 空间索引 | INDEX ... USING SPATIAL PROPERTIES("algorithm"="s2") |
正常 |
| 建表时带 RTREE 空间索引 | INDEX ... USING SPATIAL PROPERTIES("algorithm"="rtree") |
正常 |
| 建表后创建空间索引 | CREATE INDEX ... USING SPATIAL |
正常 |
| ALTER 添加空间索引 | ALTER TABLE ... ADD INDEX ... USING SPATIAL |
正常 |
| 删除空间索引 | DROP INDEX ... ON ... 或 ALTER TABLE ... DROP INDEX ... |
正常 |
| 查看空间索引 | SHOW INDEX FROM ... |
正常 |
创建普通 GEOMETRY 列
1DROP TABLE IF EXISTS geo_places;
2
3CREATE TABLE geo_places (
4 id INT NOT NULL,
5 geo GEOMETRY NULL,
6 name STRING NULL
7)
8DUPLICATE KEY(id)
9DISTRIBUTED BY HASH(id) BUCKETS 1
10PROPERTIES("replication_num" = "1");
GEOMETRY 类型目前使用 4326 坐标参考语义,对应全球通用的地理坐标系统。
建表时创建 S2 空间索引
1DROP TABLE IF EXISTS geo_places_s2;
2
3CREATE TABLE geo_places_s2 (
4 id INT NOT NULL,
5 geo GEOMETRY NULL,
6 name STRING NULL,
7 INDEX idx_geo_s2 (`geo`) USING SPATIAL
8 PROPERTIES(
9 "algorithm" = "s2",
10 "s2_max_edges_per_cell" = "10"
11 )
12 COMMENT 's2 spatial index'
13)
14DUPLICATE KEY(id)
15DISTRIBUTED BY HASH(id) BUCKETS 1
16PROPERTIES("replication_num" = "1");
建表时创建 RTREE 空间索引
1DROP TABLE IF EXISTS geo_places_rtree;
2
3CREATE TABLE geo_places_rtree (
4 id INT NOT NULL,
5 geo GEOMETRY NULL,
6 name STRING NULL,
7 INDEX idx_geo_rtree (`geo`) USING SPATIAL
8 PROPERTIES(
9 "algorithm" = "rtree",
10 "max_mbr_in_rtree_node" = "50",
11 "min_mbr_in_rtree_node" = "2"
12 )
13 COMMENT 'rtree spatial index'
14)
15DUPLICATE KEY(id)
16DISTRIBUTED BY HASH(id) BUCKETS 1
17PROPERTIES("replication_num" = "1");
RTREE 参数
| 参数 | 默认值 | 校验 | 说明 |
|---|---|---|---|
max_mbr_in_rtree_node |
50 |
1 到 65535 |
RTREE 节点可包含的最大 MBR 数量。 |
min_mbr_in_rtree_node |
2 |
1 到 65535,且必须小于 max_mbr_in_rtree_node |
RTREE 节点包含的最小 MBR 数量。 |
使用 CREATE INDEX 创建和删除空间索引
1CREATE INDEX idx_geo_rtree ON geo_places (`geo`) USING SPATIAL
2PROPERTIES(
3 "algorithm" = "rtree",
4 "max_mbr_in_rtree_node" = "50",
5 "min_mbr_in_rtree_node" = "2"
6);
7
8SHOW INDEX FROM geo_places;
9
10DROP INDEX idx_geo_rtree ON geo_places;
S2 索引使用相同的 CREATE INDEX / DROP INDEX 语法:
1CREATE INDEX idx_geo_s2 ON geo_places (`geo`) USING SPATIAL
2PROPERTIES(
3 "algorithm" = "s2",
4 "s2_max_edges_per_cell" = "10"
5);
6
7SHOW INDEX FROM geo_places;
8
9DROP INDEX idx_geo_s2 ON geo_places;
使用 ALTER TABLE 添加和删除空间索引
1ALTER TABLE geo_places ADD INDEX idx_geo_rtree (`geo`) USING SPATIAL
2PROPERTIES(
3 "algorithm" = "rtree",
4 "max_mbr_in_rtree_node" = "50",
5 "min_mbr_in_rtree_node" = "2"
6);
7
8SHOW INDEX FROM geo_places;
9
10ALTER TABLE geo_places DROP INDEX idx_geo_rtree;
S2 索引使用相同的 ALTER TABLE ... ADD / DROP INDEX 语法:
1ALTER TABLE geo_places ADD INDEX idx_geo_s2 (`geo`) USING SPATIAL
2PROPERTIES(
3 "algorithm" = "s2",
4 "s2_max_edges_per_cell" = "10"
5);
6
7SHOW INDEX FROM geo_places;
8
9ALTER TABLE geo_places DROP INDEX idx_geo_s2;
CREATE INDEX、DROP INDEX 和 ALTER TABLE ... ADD / DROP INDEX 都属于 schema change 操作。需要确认完成时,可以查看最近的 schema change 状态:
1SHOW ALTER TABLE COLUMN WHERE TableName = 'geo_places';
DDL 限制
| 限制 | 示例 | 当前状态 |
|---|---|---|
必须指定 algorithm |
CREATE INDEX idx ON geo_places (geo) USING SPATIAL; |
DDL 拒绝 |
| 支持的算法 | PROPERTIES("algorithm"="s2") 或 PROPERTIES("algorithm"="rtree") |
正常 |
| 非支持算法 | PROPERTIES("algorithm"="quadtree") |
DDL 拒绝 |
| SPATIAL 只能建单列索引 | CREATE INDEX idx ON geo_places (geo, id) USING SPATIAL ... |
DDL 拒绝 |
| 索引列类型 | GEOMETRY 或可承载编码 geometry 的字符串类列 |
正常;其他不兼容类型拒绝 |
| AGG_KEYS 表 | 在 value 列上建 SPATIAL | DDL 拒绝 |
s2_max_edges_per_cell 范围 |
1 到 65535,默认 10 |
DDL 校验 |
| RTREE MBR 参数 | 1 到 65535,且 min_mbr_in_rtree_node < max_mbr_in_rtree_node |
DDL 校验 |
写入和导入地理数据
用于 GEO 函数和空间索引的底层列值必须是 Doris geometry 编码结果。目标列为 GEOMETRY 时,合法 WKT 字符串可在 INSERT、CSV Stream Load 或 JSON Stream Load 中自动转换;也可以显式使用 ST_GeometryFromText。目标列为 STRING / VARCHAR / CHAR 时,应显式调用 GEO 构造函数生成编码值,不要把 WKT 原文直接保存为普通字符串。
使用 INSERT 写入 geometry
1INSERT INTO geo_places_s2 VALUES
2 (1, ST_GeomFromText('POINT(1 1)'), 'p01'),
3 (2, ST_GeomFromText('POINT(2 2)'), 'p02'),
4 (3, ST_GeomFromText('POINT(3 3)'), 'p03'),
5 (4, ST_GeomFromText('POINT(4 4)'), 'p04'),
6 (5, ST_GeomFromText('POINT(5 5)'), 'p05'),
7 (6, ST_GeomFromText('POINT(6 6)'), 'p06'),
8 (7, ST_GeomFromText('POINT(7 7)'), 'p07'),
9 (8, ST_GeomFromText('POINT(8 8)'), 'p08'),
10 (9, ST_GeomFromText('POINT(5 2)'), 'p09'),
11 (10, ST_GeomFromText('POINT(2 5)'), 'p10'),
12 (11, ST_GeomFromText('POINT(-1 -1)'), 'p11'),
13 (12, ST_GeomFromText('POINT(11 11)'), 'p12'),
14 (21, NULL, 'null-01');
15
16SYNC;
GEOMETRY 列可以自动转换合法 WKT 字面量,也可以显式调用 ST_GeomFromText。
使用 INSERT SELECT 转换 WKT
1INSERT INTO geo_places_rtree
2SELECT id, ST_GeometryFromText(wkt), name
3FROM (
4 SELECT 101 AS id, 'POINT(1 1)' AS wkt, 'p101' AS name
5 UNION ALL
6 SELECT 102 AS id, 'POINT(20 20)' AS wkt, 'p102' AS name
7) AS raw_geo_wkt;
使用 Stream Load 导入 WKT
源文件列为 id|tmp_geo|name 时,tmp_geo 是临时导入列,目标表没有这个字段;通过 geo=ST_GeometryFromText(tmp_geo) 显式写入 geometry。示例使用 | 分隔,避免 WKT 中的逗号和普通 CSV 分隔冲突。
1curl --location-trusted -uroot: \
2 -H "Expect:100-continue" \
3 -H "column_separator:|" \
4 -H "columns:id,tmp_geo,name,geo=ST_GeometryFromText(tmp_geo)" \
5 -T geo_wkt.tsv \
6 "http://<fe_http>/api/<db>/geo_places_rtree/_stream_load"
当目标列是 GEOMETRY 且源字段名可以直接映射到 geo 时,也可以直接导入合法 WKT:
1curl --location-trusted -uroot: \
2 -H "Expect:100-continue" \
3 -H "column_separator:|" \
4 -H "columns:id,geo,name" \
5 -T geo_wkt.tsv \
6 "http://<fe_http>/api/<db>/geo_places_rtree/_stream_load"
使用 Stream Load 导入坐标列
源文件列为 id|x|y|name 时,x、y 是临时导入列;通过 geo=ST_Point(x,y) 写入点 geometry。
1curl --location-trusted -uroot: \
2 -H "Expect:100-continue" \
3 -H "column_separator:|" \
4 -H "columns:id,x,y,name,geo=ST_Point(x,y)" \
5 -T geo_point.tsv \
6 "http://<fe_http>/api/<db>/geo_places_rtree/_stream_load"
查询地理数据
构造、转换、坐标读取
| 分类 | SQL | 说明 | 当前状态 |
|---|---|---|---|
| 点构造 | SELECT ST_AsText(ST_Point(24.7, 56.7)); |
返回 WKT 文本。 | 正常 |
| WKT 构造 | SELECT ST_AsText(ST_GeomFromText('LINESTRING (1 1, 2 2)')); |
ST_GeometryFromText 同义可用。 |
正常 |
| LineString 构造 | SELECT ST_AsText(ST_LineStringFromText('LINESTRING (1 1, 2 2)')); |
ST_LineFromText 同义可用。 |
正常 |
| Polygon 构造 | SELECT ST_AsText(ST_Polygon('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))')); |
ST_PolyFromText、ST_PolygonFromText 同义可用。 |
正常 |
| WKB 转换 | SELECT ST_AsText(ST_GeomFromWKB(ST_AsBinary(ST_Point(24.7, 56.7)))); |
ST_GeometryFromWKB 同义可用。 |
正常 |
| 坐标读取 | SELECT ST_X(ST_Point(1, 2)), ST_Y(ST_Point(1, 2)); |
只适用于点。 | 正常 |
度量和几何计算
| 分类 | SQL | 说明 | 当前状态 |
|---|---|---|---|
| 球面距离 | SELECT ST_Distance_Sphere(116.35620117, 39.939093, 116.4274406433, 39.9020987219); |
经纬度球面距离。 | 正常 |
| 球面夹角 | SELECT ST_Angle_Sphere(116.35620117, 39.939093, 116.4274406433, 39.9020987219); |
经纬度球面角度。 | 正常 |
| 三点夹角 | SELECT ST_Angle(ST_Point(1, 0), ST_Point(0, 0), ST_Point(0, 1)); |
三个点输入。 | 正常 |
| 方位角 | SELECT ST_Azimuth(ST_Point(1, 0), ST_Point(0, 0)); |
两个点输入。 | 正常 |
| 面积平方米 | SELECT ST_Area_Square_Meters(ST_Polygon('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))')); |
Polygon / MultiPolygon。 | 正常 |
| 面积平方公里 | SELECT ST_Area_Square_Km(ST_Polygon('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))')); |
Polygon / MultiPolygon。 | 正常 |
| 圆近似多边形 | SELECT ST_PolyFromCircle(ST_Point(0, 0), 1.0, 36, 'wkt'); |
返回 WKT;mode='wkb' 返回 WKB 字符串。 |
正常 |
纯地理关系检索
以下写法同时适用于 S2 和 RTREE。若表上存在 RTREE,RTREE 先按 MBR 过滤候选行,再由原谓词完成精确判断。
使用空间关系函数时,请注意参数方向:
| 查询目标 | 推荐写法 |
|---|---|
| 判断索引列中的点是否位于指定多边形内 | ST_Contains(指定多边形, geo) |
| 判断索引列中的 geometry 是否位于指定多边形内 | ST_Within(geo, 指定多边形) |
| 判断索引列中的 geometry 是否与指定 geometry 相交 | ST_Intersects(geo, 指定 geometry) |
| 判断索引列中的 geometry 是否在指定距离内 | ST_DWithin(geo, 指定 geometry, 距离) |
ST_DWithin 的点距语义按球面距离处理,距离阈值单位为米。
1-- 点是否在多边形内
2SELECT id, name
3FROM geo_places_rtree
4WHERE ST_Contains(
5 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
6 geo
7)
8ORDER BY id;
9
10-- 是否相交
11SELECT id, name
12FROM geo_places_rtree
13WHERE ST_Intersects(
14 geo,
15 ST_GeomFromText('POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))')
16)
17ORDER BY id;
18
19-- 是否被包含
20SELECT id, name
21FROM geo_places_rtree
22WHERE ST_Within(
23 geo,
24 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))')
25)
26ORDER BY id;
27
28-- 是否在指定距离内。点距语义按球面距离处理,阈值单位为米。
29SELECT id, name
30FROM geo_places_rtree
31WHERE ST_DWithin(geo, ST_Point(5, 5), 200.0)
32ORDER BY id;
当前空间关系函数集合:
| 函数 | 示例 | 当前状态 |
|---|---|---|
ST_Contains(a, b) |
ST_Contains(ST_Polygon(...), geo) |
标量执行正常;S2 精确查询;RTREE 候选过滤后复核 |
ST_Within(a, b) |
ST_Within(geo, ST_Polygon(...)) |
标量执行正常;S2 精确查询;RTREE 候选过滤后复核 |
ST_Intersects(a, b) |
ST_Intersects(geo, ST_GeomFromText(...)) |
标量执行正常;S2 精确查询;RTREE 候选过滤后复核 |
ST_Equals(a, b) |
ST_Equals(ST_Point(1, 1), ST_Point(1, 1)) |
标量执行正常;S2 精确查询;RTREE 候选过滤后复核 |
ST_Touches(a, b) |
ST_Touches(ST_Point(0, 0), ST_Polygon(...)) |
标量执行正常;S2 精确查询;RTREE 候选过滤后复核 |
ST_Overlaps(a, b) |
ST_Overlaps(ST_Polygon(...), ST_Polygon(...)) |
标量执行正常;S2 精确查询;RTREE 候选过滤后复核 |
ST_Crosses(a, b) |
ST_Crosses(ST_GeomFromText('LINESTRING ...'), ST_Polygon(...)) |
标量执行正常;S2 精确查询;RTREE 候选过滤后复核 |
ST_DWithin(a, b, distance) |
ST_DWithin(ST_Point(0, 0), ST_Point(0.001, 0), 200.0) |
标量执行正常;S2 精确查询;RTREE 按距离扩展 MBR 后复核 |
RTREE 候选过滤语义
RTREE 为每一行 geometry 建立二维最小外接矩形(MBR),查询条件中的常量 geometry 也会转换为 MBR:
| 谓词 | RTREE 候选条件 |
|---|---|
INTERSECTS / TOUCHES / OVERLAPS / CROSSES |
两个 MBR 相交或接触。 |
CONTAINS |
查询 MBR 覆盖存储 MBR。 |
WITHIN |
存储 MBR 覆盖查询 MBR。 |
EQUALS |
两个 MBR 相等。 |
DWITHIN |
查询 MBR 按距离做保守膨胀后,与存储 MBR 相交;距离单位为米。 |
KNN |
当前不支持。 |
MBR 只用于“可能命中”的筛选。多边形洞、线段实际形状、球面距离、跨反子午线等语义仍由原 ST_* 函数复核。因此 RTREE 可以多返回候选行,但不能漏掉满足条件的行。
ST_DWithin 使用 RTREE 时,先按距离扩展查询 MBR 并筛选候选行,再按球面距离完成精确判断。距离参数建议使用常量,单位为米。
带标量过滤的地理检索
1SELECT id, name
2FROM geo_places_rtree
3WHERE ST_Contains(
4 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
5 geo
6)
7AND id IN (2, 4, 6, 8, 10, 12)
8ORDER BY id;
1SELECT id, name
2FROM geo_places_rtree
3WHERE ST_Contains(
4 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
5 geo
6)
7AND ST_X(geo) > 4
8ORDER BY id;
OR / NOT / NULL 检索
1-- 两个空间条件 OR
2SELECT id, name
3FROM geo_places_rtree
4WHERE ST_Contains(
5 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
6 geo
7 )
8 OR ST_Contains(
9 ST_GeomFromText('POLYGON((70 70, 90 70, 90 90, 70 90, 70 70))'),
10 geo
11 )
12ORDER BY id;
13
14-- 空间条件 OR 标量条件
15SELECT id, name
16FROM geo_places_rtree
17WHERE ST_Contains(
18 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
19 geo
20 )
21 OR name = 'p11'
22ORDER BY id;
23
24-- NOT
25SELECT id, name
26FROM geo_places_rtree
27WHERE NOT ST_Contains(
28 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
29 geo
30)
31AND id <= 12
32ORDER BY id;
33
34-- NULL 列参与查询
35SELECT id, name
36FROM geo_places_rtree
37WHERE ST_Contains(
38 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
39 geo
40 )
41 OR geo IS NULL
42ORDER BY id;
RTREE 当前只对正向 AND 条件做候选过滤。空间条件位于 OR、NOT 等组合表达式中时,保留普通表达式执行,以保证查询结果和 NULL 语义不变。
开启 / 关闭空间索引
1SET enable_spatial_index = false;
2
3SELECT id, name
4FROM geo_places_rtree
5WHERE ST_Contains(
6 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
7 geo
8)
9ORDER BY id;
1SET enable_spatial_index = true;
2
3SELECT id, name
4FROM geo_places_rtree
5WHERE ST_Contains(
6 ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
7 geo
8)
9ORDER BY id;
打开或关闭索引时,结果应一致;RTREE 的差异体现在扫描行数和执行时间,而不是最终结果。
索引友好的常见写法:
| 谓词 | 推荐写法 | 说明 |
|---|---|---|
ST_Contains |
ST_Contains(ST_GeomFromText('POLYGON(...)'), geo) |
常见点在多边形内检索;如果索引列保存的是面,也可以按语义写成 ST_Contains(geo, ST_Point(...))。 |
ST_Within |
ST_Within(geo, ST_GeomFromText('POLYGON(...)')) |
常见点在多边形内检索的反向谓词。 |
ST_Intersects |
ST_Intersects(geo, ST_GeomFromText('POLYGON(...)')) |
判断索引列 geometry 与常量 geometry 是否相交。 |
ST_DWithin |
ST_DWithin(geo, ST_Point(...), const_distance) |
距离参数使用常量,便于 RTREE 预过滤。 |
异常和边界行为
| 场景 | SQL | 当前状态 |
|---|---|---|
| NULL 参数 | SELECT ST_Within(NULL, ST_Point(1, 1)); |
返回 NULL |
| 非法 WKT | SELECT ST_DWithin(ST_GeomFromText('BAD'), ST_Point(1, 1), 10.0); |
返回 NULL |
| 参数类型不兼容 | SELECT ST_Within(array(1, 2), ST_Point(1, 1)); |
FE 拒绝 |
| 参数个数错误 | SELECT ST_DWithin(ST_Point(0, 0), ST_Point(1, 0)); |
FE 拒绝 |
| RTREE 常量 geometry 无法解析 | ST_Intersects(geo, 'not_encoded_geo') |
不使用 RTREE,保留普通 GEO 表达式执行。 |
| RTREE 查询无法完成预过滤 | 合法空间谓词 | 回退普通 GEO 表达式执行。 |
ST_DWithin 距离为负 |
ST_DWithin(ST_Point(0, 0), ST_Point(1, 0), -1.0) |
按普通 GEO 函数的非法参数语义处理。 |
| RTREE 与 NULL 行 | 空间谓词命中含 NULL 的表 | NULL 行不被当作空间命中;OR geo IS NULL 仍由普通谓词语义决定。 |
ST_PolyFromCircle 半径为负 |
SELECT ST_PolyFromCircle(ST_Point(0, 0), -1.0, 36, 'wkt'); |
返回 NULL |
ST_PolyFromCircle 边数过少 |
SELECT ST_PolyFromCircle(ST_Point(0, 0), 1.0, 2, 'wkt'); |
返回 NULL |
ST_PolyFromCircle mode 非法 |
SELECT ST_PolyFromCircle(ST_Point(0, 0), 1.0, 36, 'invalid'); |
返回 NULL |
ST_Circle |
SELECT ST_AsText(ST_Circle(111, 64, 10000)); |
当前不可用,不作为正常支持 SQL 使用 |
评价此篇文章
