一、Oracle几何空间数据对象和其他数据库的差异

和MySQL、PostgreSQL等数据库相比,Oracle数据库的地理几何空间更难学习和上手使用,我总结的一点原因是Oracle Spatial文档中阐述了太多的和其他数据库不通用的几何对象构建、查询方法,需要单独深入学习研究Oracle Spatial文档,知识碎片化严重,实现一个相同功能可能有N个功能相近的函数可以给你调用(茴香豆的茴字的N种写法),学习成本太高,还稍不注意就容易踩坑。

WKT(Well Known Text)是通用的几何空间对象的文本表示方法,相比于同样通用的GeoJSON文本,WKT更简单,易于理解也容易生成,重要的是几乎所有支持地理几何空间的数据库都支持WKT文本。

  • MySQLPostgreSQL中可用ST_GeomFromText('wkt',SRID)来构造几何对象
  • SQLServer中可用geometry::STGeomFromText('wkt',SRID)来构造几何对象
  • Oracle中可用SDO_GEOMETRY('wkt',SRID)来构造几何对象,但此处有坑

SQL语句中手写的单个文本字符串长度,在Oracle中也有限制,测试发现SQLPlus中最长的手写单个文本长度3000左右,SQL Developer中32767左右,超过了长度SQL就没法执行,直接报语法解析错误,这在其他数据库中是闻所未闻的。复杂的一个地理坐标边界图形的WKT可能有上百KB甚至上MB的超长文本,这在Oracle中单纯的使用SQL语句将会是一个很大的挑战。

本地测试所使用的数据库版本:Oracle Database 21c Express Edition Release 21.0.0.0.0

在线测试所使用的Live SQL版本:Live SQL 23.3.1, running Oracle Database 19c EE Extreme Perf - 19.17.0.0.0 (也有坑)

Oracle Spatial参考文档地址:https://docs.oracle.com/en/database/oracle/oracle-database/21/spatl/index.html

开源省市区坐标边界数据(可导入Oracle):https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov github可换成gitee

二、Oracle查询一个经纬度坐标是否在边界内部

2.1 查询条件

  • 边界几何图形:POLYGON ((53 20, 52 20, 52 23, 57 23, 57 20, 56 20, 56 22, 53 22, 53 20)),类似一个倒过来的凹字
  • 坐标点:POINT (55 21),这个点位于凹进去的里面,但不在凹字内部

查询这个坐标点是否在图形内部,结果应当是不在内部。

2.2 查询结果错误,似乎是仅做了MBR匹配

编写查询语句,这里直接给出相同一个图形的顺时针和逆时针两种写法:

declare
-- 定义坐标点
p SDO_GEOMETRY:=SDO_GEOMETRY('POINT (55 21)',4326);
-- 定义边界几何图形(顺时针),如果你的WKT是这种,那放到Oracle里面就惨了
g1 SDO_GEOMETRY:=SDO_GEOMETRY('POLYGON ((53 20, 52 20, 52 23, 57 23, 57 20, 56 20, 56 22, 53 22, 53 20))',4326);
-- 定义边界几何图形(逆时针),符合Oracle坐标顺序要求
g2 SDO_GEOMETRY:=SDO_GEOMETRY('POLYGON ((53 20, 53 22, 56 22, 56 20, 57 20, 57 23, 52 23, 52 20, 53 20))',4326);
v1 clob; v2 clob;
begin
-- 计算坐标点和2个边界的位置关系
select SDO_ANYINTERACT(g1,p),SDO_ANYINTERACT(g2,p) into v1,v2 from dual;
dbms_output.put_line('g1:'||v1 ||' '|| 'g2:'||v2); -- 检查2个边界是否有效(ST_IsValid)
dbms_output.put_line('g1 IsValid:'||SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(g1,0.0000001));
dbms_output.put_line('g2 IsValid:'||SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(g2,0.0000001));
end;

SQL Developer中执行后输出结果(和Live SQL中测试结果相同):

g1:TRUE  g2:FALSE
g1 IsValid:13367 [Element <1>] [Ring <1>]
g2 IsValid:TRUE

可以看到顺时针表示的边界WKT g1 居然包含了这个坐标点:g1:TRUE ;并且检查g1的有效性,显示图形是无效的。

尝试将坐标点改成POINT (55 10),此坐标完全在凹字外面,查询结果就是正确的,因此可以得出:边界g1和坐标p的位置计算只进行了外接矩形(MBR)的匹配,没有进行精确的计算,p坐标点在g1的MBR内部就直接返回了匹配,导致结果错误。

或者去掉SRID(删掉,4326,即SRID=NULL),查询结果也是正确的,这个就很奇葩了,代码正在以奇怪的方式正确运行。。。很有迷惑性。

相同的边界图形WKT 和 坐标点,在MySQL、PostgreSQL、SQLServer中测试均结果正确,也不存在图形无效的问题,仅仅是Oracle上有问题。

2.3 错误原因

这个错误结果困扰了一个多礼拜,SDO_ANYINTERACTSDO_RELATE的文档中没有找到关于图形坐标方向的说明,一度以为是提供了SRID的问题(只因SRID=NULL时查询结果又是正确的),还好后面在 SDO_ELEM_INFORecommendations for Loading and Validating Spatial Data文档中得到了确切的答案:

You should specify 3 only if you do not know if the simple polygon is exterior or interior, and you should then upgrade the table or layer to the current format using the SDO_MIGRATE.TO_CURRENT procedure, described in SDO_MIGRATE Package (Upgrading) .

.......

1005: exterior polygon ring (must be specified in counterclockwise order)

2005: interior polygon ring (must be specified in clockwise order)

For any geometries with the wrong orientation or an invalid ETYPE or GTYPE value, use SDO_MIGRATE.TO_CURRENT on these invalid geometries to fix them.

意思就是Oracle里面边界图形的外环必须逆时针的。但SDO_ANYINTERACT查询的时候,对于顺时针的错误图形,Oracle既不报错还返回错误结果,这就很离谱,这点的原因还没翻到文档说明。注:其他几个数据库并不要求环的方向顺序。

2.4 解决办法

根据文档提示,使用 SDO_MIGRATE.TO_CURRENT 方法对SDO_GEOMETRY构造出来的边界几何图形坐标的顺序进行处理,达到方向顺序要求

	-- 在原来的基础上,套一层TO_CURRENT()调用,提供一个dim数组即可
g1 SDO_GEOMETRY:=SDO_MIGRATE.TO_CURRENT(SDO_GEOMETRY('POLYGON ((53 20, 52 20, 52 23, 57 23, 57 20, 56 20, 56 22, 53 22, 53 20))',4326),SDO_DIM_ARRAY(SDO_DIM_ELEMENT('X', -180, 180, 0.0000001),SDO_DIM_ELEMENT('Y', -90, 90, 0.0000001)));

套了一层TO_CURRENT()后,对g1的查询就对了,图形的有效性也是正确的。

三、SQL中WKT超长文本在Oracle中如何编写

3.1 Oracle中执行含超长文本的SQL报错

修改并运行以下代码:

-- 字符串里面放50k的字符串,可浏览器控制台里面执行js得到长文本: new Array(50001).join("a")
select '这里放50000个字符......' from dual;

SQL Developer 中直接报错(Live SQL支持更差):

ORA-01704: 字符串文字太长
01704. 00000 - "string literal too long"
*Cause: The string literal is longer than 4000 characters.
*Action: Use a string literal of at most 4000 characters.
Longer values may only be entered using bind variables.

好家伙,第一次见限制SQL语句中字符串长度的,翻阅文档找到了 PL/SQL Program Limitssize of a string literal (bytes): 32767,其他数据库从来没有这种问题。Longer values may only be entered using bind variables.提示的这个解决办法目测是针对编程环境下的PreparedStatement,我都写SQL了,SQL文件里面绑个锤子的变量。

开头也说过了复杂地理坐标边界图形的WKT可能有上百KB甚至几MB的超长文本,其他数据库直接简单的通过类似insert tab values('100kb wkt')即可完成插入,现在Oracle由于这个书写字符串长度的限制,势必要额外编写非常规的代码。

3.2 使用CLOB无限拼接得到超长文本

Oracle的CLOB类型能放下4GB的文本,因此可以将超长的WKT文本切分成一小段一小段的文本,拼接进clob变量里面,避免单个字符串过长,即可解决这个问题。

declare txt clob:='';begin
txt:=txt||'POLYGON(( .... WKT切分的2KB字符串';
txt:=txt||'.... WKT切分的2KB字符串';
txt:=txt||'.... WKT切分的2KB字符串';
-- ...... 剩余的2KB字符串 dbms_output.put_line('txt: '||dbms_lob.getlength(txt)); -- 把超长WKT文本转成几何空间对象,修正坐标方向,插入数据库
-- INSERT INTO tableName(polygon) VALUES( SDO_MIGRATE.TO_CURRENT(SDO_GEOMETRY(txt,4326),SDO_DIM_ARRAY(SDO_DIM_ELEMENT('X', -180, 180, 0.0000001),SDO_DIM_ELEMENT('Y', -90, 90, 0.0000001))) );
end;
/

Oracle不同终端(SQLPlus、SQL Developer)里面SQL能书写的单个字符串长度混乱,3k-32k不等,因此长字符串采取2KB长度分段拼接进行支持,获得最大兼容;CLOB直接用||拼接即可,无需使用to_clobdbms_lob.append

注意:Live SQL中测试时,如果SQL语句的总长度超过32KB(非单个字符串),查询将会报错,或不压根返回结果,和SQLPlus、SQL Developer表现的完全不一样;可以理解为目前Live SQL中无法进行携带任何超长文本的测试。

3.3 导入开源的省市区坐标边界WKT数据

从开源库:AreaCity-JsSpider-StatsGov 里面的文档中获取到最新的省市区三级或乡镇级数据,有shp、geojson、sql格式支持,只需选择导出Oracle格式的SQL文件即可,在SQLPlus、或SQL Developer里面执行这个SQL文件即可完成导入:

@"D:/xxx/xxx.sql"; --改成实际的文件路径

SQL文件中的超长WKT文本按照上面2KB一段的长度进行了切分,使用CLOB拼接。此SQL文件结尾会自动创建索引,或者根据下文手动创建索引,建了索引后 SDO_ANYINTERACT 查询速度快100倍。

四、Oracle数据库的空间索引和查询返回WKT超长文本

4.1 给查询加速:Oracle数据库的空间索引

空间索引文档:Indexing and Querying Spatial Data,涉及user_sdo_geom_metadata这个表,这个表很重要,虽然里面只存了经纬度范围和SRID!

如果user_sdo_geom_metadata里面配置的SRID和导入到表里的数据SRID不一致,将会导致空间查询时直接报错。数据的SRID=NULL时,metadata里面的SRID也必须是NULL,其他SRID必须是一个存在的SRID值(通过 select * from MDSYS.CS_SRS order by srid 查询所有SRID)。

然后创建正常创建索引即可:

-- 先往user_sdo_geom_metadata里面插入配置数据
insert into user_sdo_geom_metadata(TABLE_NAME,COLUMN_NAME,DIMINFO,SRID)VALUES (upper('tableName'),upper('polygon'),SDO_DIM_ARRAY(SDO_DIM_ELEMENT('X', -180, 180, 0.0000001),SDO_DIM_ELEMENT('Y', -90, 90, 0.0000001)),NULL); -- SRID=NULL 或 一个具体值 -- 创建空间索引
create index indexName ON tableName(polygon) INDEXTYPE IS MDSYS.SPATIAL_INDEX_V2;

测试发现:修改表名会自动修改metadata里面的表名,删除表不会删除metadata里面的此表的相关配置。

表中空间数据量比较多的时候,加空间索引对查询速度的优化效果非常明细,上面导入的省市区三级的边界数据3600多条,不加索引时要7秒查询一个坐标,加了索引后只需0.06秒查询一次,查询性能提高了100倍。

4.2 如何查询返回SDO_GEOMETRY的WKT超长文本?

超长的WKT文本通过CLOB拼接的SQL语句插入数据库后,仅通过SQL查询语句从边界几何对象中读取出WKT文本似乎又是一个难题。

select SDO_GEOMETRY.GET_WKT(polygon) from tableName;

GET_WKT方法经常动不动就报错(偶尔又能正常查出来):

ORA-13199: wk buffer merge failure
ORA-06512: 在 "MDSYS.SDO_UTIL", line 857
ORA-06512: 在 "MDSYS.SDO_UTIL", line 896
ORA-06512: 在 "MDSYS.SDO_GEOMETRY", line 36

目前暂无办法稳定的查询出SDO_GEOMETRY的WKT超长文本,Oracle难搞哦。

注:以上问题(疑难杂症)MySQL、PostgreSQL、SQLServer 均无,这样说,你应该知道怎么选了吧=。=

【完】

Oracle数据库经纬度坐标查询优化与结果错误原因分析、SQL中WKT超长文本字符串处理的更多相关文章

  1. Oracle数据库排序后分页查询数据错误问题解决

    一.问题描述:根据更新时间倒序排序然后分页查询数据,但是点击分页操作的时候,会出现数据重复看似没有操作的情况 二.问题错误原因分析 分页查询的SQL语句: select * FROM (select ...

  2. undo丢失恢复异常恢复,运维DBA反映Oracle数据库无法启动报错ORA-01157 ORA-01110,分析原因为Oracle数据库坏块导致

    本文转自 惜纷飞 大师. 模拟基表事务未提交数据库crash,undo丢失恢复异常恢复,运维DBA反映Oracle数据库无法启动报错ORA-01157 ORA-01110,分析原因为Oracle数据库 ...

  3. 查询oracle数据库,返回的数据是乱码。 PL/SQL正常。

    查询oracle数据库,返回的数据是乱码. PL/SQL正常. 解决方案如下:

  4. loadFileSystems error & ExceptionUtils错误原因分析

    loadFileSystems error & ExceptionUtils错误原因分析 一见 2014/5/7 C/C++程序通过hdfs.h访问HDFS,运行时遇到如下错误,会是什么原因了 ...

  5. “undefined reference to JNI_GetCreatedJavaVM”和“File format not recognized”错误原因分析

    "undefined reference to JNI_GetCreatedJavaVM"和"File format not recognized"错误原因分析 ...

  6. 本机未装Oracle数据库时Navicat for Oracle 报错:Cannot create oci environment 原因分析及解决方案

    因为要更新数据库加个表,远程桌面又无法连接...所以就远程到另外一台电脑,然后用navicat通过内网修改目标数据库. 一直用着navicat操作数据库,所以很速度的弄好然后新建连接进入数据库. 然而 ...

  7. Oracle数据库版本号定期检视与升级的必要性分析

    目 录 ▇1.ORACLE数据库版本号知识 ▇2.看看自己的数据库还有没有支持服务 ▇3.看11.2.0.3版本号各PSU的公布时间与解决BUG数量列表 ▇4.看11.2.0.4版本号各PSU的公布时 ...

  8. SQL Server 磁盘请求超时的833错误原因分析以及解决

    本文出处:http://www.cnblogs.com/wy123/p/6984885.html 最近遇到一个SQL Server服务器响应极度缓慢,并且出现客户端请求报错的情况,在数据库中的erro ...

  9. 微信的redirect_uri参数错误原因分析

    我们可以根据微信的开发者文档  http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html 网页授权获取用户验证这一章来 ...

  10. Entity Framework 数据并发访问错误原因分析与系统架构优化

    博客地址 http://blog.csdn.net/foxdave 本文主要记录近两天针对项目发生的数据访问问题的分析研究过程与系统架构优化,我喜欢说通俗的白话,高手轻拍 1. 发现问题 系统新模块上 ...

随机推荐

  1. django model字段类型

    1.models.AutoField 自增列=int(11) 如果没有的话,默认会生成一个名称为id的列,如果要显示的定义一个自增列,必须把该列设置为主键(primary_key=True)2.mod ...

  2. MultiBoot SPI

    对于7系列FPGA来说,计算器件启动时间按照以下公式: Config time = Bitstream size / (Config clk freq * Config interface width ...

  3. 【Python笔记】第一章Python基本语法

    嗨你好,我是AllenMi, 这是我学习北京理工大学的<Python语言程序设计>第一张笔记. 写笔记的目的一方面在于记录自己一步一步学习Python的内容, 另一方面也希望能够帮助到他人 ...

  4. 程序员的 Windows 工具箱「GitHub 热点速览」

    如何精简 Windows 并快速配置开发环境呢?本周特推的 winutil 是一个程序员的 Windows 工具箱,它提供了开发工具的一键安装以及减少系统垃圾的功能,一切为了简洁.高效.同样高效的还有 ...

  5. Linux 文件系统inode号和备份恢复

    目录 一.inode原理 二.时间类型 三.inode号管理 四.inode实验 五.备份恢复 七.备份实验 一.inode原理 inode只有一个,唯一的,一个文件必须占用一个inode号,但是至少 ...

  6. bootstrap treeview基本运用

    虽然现在有了很多新的前端框架,但是有的时候我们做一个不需要任何其他js编译环境就可以运行的项目,那还是的使用一些老式技术,接下来就来回顾一些bootstrap treeview + jquery的使用 ...

  7. Simple Factory Pattern 简单工厂模式简介与 C# 示例【创建型】【设计模式来了】

    〇.简介 1.什么是简单工厂模式? 一句话解释:   客户类和工厂类严格分工,客户类只需知道怎么用,处理逻辑交给工厂类. 简单工厂模式(Simple Factory Pattern)是日常开发中常用的 ...

  8. django的部署在centos

    虚拟环境 #virtualenv是一个创建独立python环境的工具 sudo pip install virtualenv #virtualenvwrapper将所有的虚拟环境统一管理,留意安装路径 ...

  9. Java流程控制和循环(基础语法学习)

    一.流程控制 1.定义 ​ 在一个Java程序中,各条语句的执行对程序的结果有直接影响,也就是说 各个语句的执行顺序对程序的结果有直接影响. ​ 在程序中 ,可能出现不同的执行顺序,必须 自上而下顺序 ...

  10. Mybatis的parameterType造成线程阻塞问题分析

    一.前言 最近在新发布某个项目上线时,每次重启都会收到机器的 CPU 使用率告警,查看对应监控,持续时长达 5 分钟,对于服务重启有很大风险.而该项目有非常多 Consumer 消费,服务启动后会有大 ...