最可靠方式是用STContains判断点是否在多边形内,但需确保边界为GEOGRAPHY类型、SRID=4326,且经纬度顺序为(longitude, latitude),否则结果错误却不报错。

SQL Server 中用 STContains 判断点是否在多边形内
直接用 STContains 是最可靠的方式,但前提是你的省份/城市边界数据必须是 GEOGRAPHY 类型的多边形(POLYGON),且 SRID 为 4326。常见错误是把经纬度顺序写反——geography::Point(longitude, latitude, 4326),不是 (lat, lng);一旦颠倒,结果全错,但 SQL 不报错,只会返回空。
实操建议:
- 建表时,
Boundary字段必须声明为GEOGRAPHY,不能用GEOMETRY(后者是平面坐标,不适用于地球曲面) - 插入边界数据前,先用
geography::STPolyFromText('POLYGON((...))', 4326)验证字符串格式是否合法,非法多边形会导致INSERT失败 - 查询时务必把用户坐标转成
GEOGRAPHY点对象,再调用.STContains(),不要试图用字符串拼接或 CAST 强转
MySQL 中用 ST_Contains 实现相同逻辑但要注意单位与索引
MySQL 的 ST_Contains 行为类似,但底层依赖空间索引(R-tree)才能高效运行。没建空间索引时,哪怕只有几千条区域数据,每次查询也会全表扫描,延迟从毫秒级升到秒级。
实操建议:
-
bounds字段类型必须是POLYGON,且定义时指定SRID 4326(如POLYGON((...)) SRID 4326),否则ST_Contains可能静默失效 - 必须对
bounds字段建立空间索引:CREATE SPATIAL INDEX idx_bounds ON region(bounds);普通 B-tree 索引无效 - 传入的点要用
POINT(longitude, latitude),注意 MySQL 的POINT是(x,y) = (lng,lat),和 SQL Server 一致 - 若查询返回空,先检查
SELECT ST_IsValid(bounds) FROM region,无效多边形会导致ST_Contains永远返回 false
触发器自动填充归属字段的隐患与替代方案
想在插入坐标时自动填上所属省/市,很多人第一时间想到用 INSTEAD OF INSERT 或 AFTER INSERT 触发器调用空间查询。这看似方便,实际埋了三个雷:一是触发器内空间函数执行慢,批量导入会卡死;二是事务中嵌套空间查询容易锁表;三是无法利用外部缓存或异步机制降压。
更稳妥的做法是:
- 把归属计算下沉到应用层或中间服务,比如用 Python 调用 PostGIS 或预加载 GeoJSON 边界做内存查找
- 如果坚持用数据库层,改用带重试的存储过程 + 延迟更新(如
UPDATE ... WHERE id IN (SELECT TOP 100 id FROM coords WHERE province IS NULL)分批处理) - 绝对不要在触发器里写
SELECT ... FROM provinces WHERE bounds.STContains(@point) = 1这类语句——它没有索引提示,优化器常选错执行计划
边界数据质量比函数选择更重要
所有空间函数都依赖输入数据的准确性。你拿到的“某省边界”CSV 或 GeoJSON,如果顶点顺序错乱、存在自相交、或缺失闭合首尾点,STPolyFromText 或 POLYGON 构造就会失败或产生不可靠结果。全国行政区划数据推荐用自然资源部标准矢量(非百度/高德脱敏版),且导入后必须跑校验:
- SQL Server:
SELECT Name, Boundary.IsValidDetailed() FROM Provinces - MySQL:
SELECT name, ST_IsValid(bounds), ST_IsClosed(bounds) FROM region - 任何数据库都应检查多边形面积是否合理(如“北京市”面积小于 100 km² 就明显异常)
真正卡住项目的,往往不是不会写 STContains,而是边界数据本身就有 20% 的 polygon 无效,却花三天调触发器逻辑。

















