在存储过程执行动态SQL一般有两种方法:
1、EXECUTE IMMEDIATE sql语句.
11g支持 EXECUTE IMMEDIATE CLOB变量.
2、使用DBMS_SQL包
11g的DBMS_SQL.PARSE也已经支持CLOB变量
由于存储过程的参数VARCHAR2只能支持4000字符长度传输,在传入动态SQL时候,要么使用LONG、要么使用LOB、要么使用多个VARCHAR2参数(需要在存储过程里拼接后再执行).
1、使用LONG类型传递
由于oracle已经明文建议不要再使用LONG,所以建议不要使用此类型做存储过程参数,实际上oracle很多函数也不支持LONG。
2、使用LOB对象类型
oracle对LOB类型大力推荐,也推出了DBMS_LOB包来辅助LOB对象的各种处理,所以我采用了CLOB类型做测试,结果证明CLOB完全可以处理超级大的SQL.
3、使用多个VARCHAR2参数
目前很多是采用这种方式,在存储里面进行拼接,不过虽然PL/SQL的varchar2变量可以到32762的长度,不过还是没有CLOB长.
下面开始准备使用CLOB变量做存储过程参数进行测试:
在这之前介绍一下SQL_TRACE,11g中sql跟踪的默认目录可以通过命令show parameter USER_DUMP_DEST查看
12:05:38 SYS@orcl> show parameter USER_DUMP_DEST NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ user_dump_dest string f:\app\administrator\diag\rdbm s\orcl\orcl\trace
使用命令 alter session set tracefile_identifier=’测试跟踪存储过程' 更改跟踪文件的名称,方便识别
使用sys.dbms_system.set_sql_trace_in_session(...)来进行会话中的SQL跟踪,它有3个参数(SID,SERIAL#,SQL_TRACE),所以需要查询到当前会话的SID及SERIAL#值.我这里使用下面的SQL查询到这2个值
select distinct b.sid, b.SERIAL# from v$mystat a, v$session b where a.sid = b.sid;
至此我将在存储过程中进行SQL的跟踪.
存储过程如下:
create or replace procedure p_TestClob ( parray in CLOB ,POUT out SYS_REFCURSOR ) as -- 测试存储过程参数为CLOB的情况 v_sql CLOB; v_sid number; v_SERIAL number; TYPE F IS TABLE OF clob INDEX BY BINARY_INTEGER;--定义CLOB对象数组 V_P F; V_SEP VARCHAR2(2) := '^'; rf sys_refcursor; V_CURSOR NUMBER DEFAULT DBMS_SQL.OPEN_CURSOR; V_RES NUMBER; begin dbms_lob.createtemporary(v_sql, true); --初始化CLOB --sql跟踪 execute immediate 'alter session set tracefile_identifier=''测试跟踪存储过程' || fn_getname || ''' '; select distinct b.sid, b.SERIAL# into v_sid, v_SERIAL from v$mystat a, v$session b where a.sid = b.sid; sys.dbms_system.set_sql_trace_in_session(v_sid, v_SERIAL, true); --分离字段 SELECT * BULK COLLECT INTO V_P FROM TABLE(SPLITCLOB(PARRAY, V_SEP)); v_sql := 'SELECT ''' || v_p(1); dbms_output.put_line('字段1长度:' || dbms_lob.getlength(v_p(1))); if v_p.count > 1 then for x in 2 .. v_p.count - 1 loop DBMS_LOB.append(V_SQL, ''' as t' || to_char(x - 1) || ', '''); DBMS_LOB.append(V_SQL, V_P(x)); end loop; end if; dbms_output.put_line('总长度为:' || dbms_lob.getlength(v_sql)); DBMS_LOB.append(V_SQL, ''' AS TT FROM DUAL '); DBMS_SQL.PARSE(V_CURSOR, V_SQL, DBMS_SQL.NATIVE);--解析SQL V_RES := DBMS_SQL.EXECUTE(V_CURSOR);--执行SQL POUT := DBMS_SQL.TO_REFCURSOR(V_CURSOR);--转换为REF游标 dbms_output.put_line('成功了!!!'); sys.dbms_system.set_SQL_TRACE_in_session(v_sid, v_SERIAL, false);--关闭SQL跟踪 exception when others then dbms_output.put_line('失败了!!!'); sys.dbms_system.set_SQL_TRACE_in_session(v_sid, v_SERIAL, false);--关闭SQL跟踪 end p_TestClob;
其中SPLITCLOB函数和fn_getname函数见本博客
http://blog.csdn.net/edcvf3/article/details/8050978一文
测试存储过程为:
declare v_r sys_refcursor; v_i integer; v_cb clob := empty_clob(); begin dbms_lob.createtemporary(v_cb, true);--初始化V_CB for j in 1 .. 10000 loop for i in 1 .. 100 loop dbms_lob.append(v_cb, '999985' || i); --v_cb := v_cb || 'TEST_CLOB' || i; end loop; v_cb := v_cb || '^';--^为字段分隔符 for i in 1 .. 100 loop dbms_lob.append(v_cb, '00234567' || i); end loop; end loop; v_i := dbms_lob.getlength(v_cb); --debug dbms_output.put_line('长度:' || v_i); p_TestClob(v_cb, v_r); end;
只要改变for后面的循环次数即可生成超级大的SQL语句.
我不断增大循环次数,当增加到如上10000*200次循环的时候,
执行结果为:
长度:17850000
字段1长度:792
总长度为:17987894
成功了!!!
花费时间:928.422 seconds
继续增大,仍然可以,只是时间需要的更长了.
呵呵,VARCHAR2变量没这么强大吧
可以发现测试的sql是这样的:SELECT ‘字段1’ as t1,‘字段2’ as t2,‘字段3’ as t3...‘最后字段' as tt from dual
当单个字段(如字段1)的长度超过4000的时候,会出现错误,猜想是因为在SQL里VARCHAR2只能支持4000字符长度,测试的结果如下:
长度:7568
字段1长度:4003
总长度为:4011
失败了!!!
查看SQL_TRACE文件发现如下错误:
PARSE ERROR #4:len=4011 dep=1 uid=84 oct=3 lid=84 tim=32007611307 err=1704
可以确定是在DBMS_SQL.PARSE解析SQL语句的时候出错的.
让我们调小一点,再测试发现
长度:7565
字段1长度:4000
总长度为:4008
成功了!!!
可见单个字段只能到4000的长度.
--------------------------------------------
至此可以体现CLOB变量的强大.加上ORACLE提供的DBMS_LOB包,我们就可以在PL/SQL编程中很方便的处理更长更大的变量了.#include "common.h"
int main (int argc, char *argv[])
{
int sock_fd,conn_fd;
struct sockaddr_in server_addr,client_addr;
socklen_t addrlen = ADDR_SIZE;
int wc = -1,rc = -1;
char buffer_r[BUFFER_SIZE],buffer_w[BUFFER_SIZE];
int i = 1;
sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd == -1)
Err_sys("Server socket:")
bzero(&server_addr,ADDR_SIZE);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(Server_port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,(void *)&i,sizeof(i));
if(bind(sock_fd,(struct sockaddr *)&server_addr,addrlen) == -1)
Err_sys("Server bind:")
if(listen(sock_fd,2) == -1)
Err_sys("Server listen:")
conn_fd = accept(sock_fd,(struct sockaddr *)&client_addr,&addrlen);
if(conn_fd == -1)
Err_sys("Server accept:")
else
printf("++++++++++++Accept Success++++++++++++++\n");
printf("Connect client [ip]:%s [port]:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
while(RUNNING)
{
memset(buffer_r,0,BUFFER_SIZE);
rc = recv(conn_fd,buffer_r,BUFFER_SIZE,0);
if(rc <= 0)
Err_sys("Server recv:")
printf("[Server recv]:%s\n",buffer_r);
memset(buffer_w,0,BUFFER_SIZE);
printf("[Server send]:");
fflush(stdout);
fgets(buffer_w,BUFFER_SIZE,stdin);
wc = send(conn_fd,buffer_w,BUFFER_SIZE,0);
if(wc <= 0)
Err_sys("Server send:")
} //while
shutdown(conn_fd,SHUT_RDWR);
close(sock_fd);
return 0;
}
接上文,本文将继续介绍基于Solr的地理位置搜索的第二种实现方案Cartesian Tiers+GeoHash
从基于Solr的地理位置搜索(2)中可以看到完全基于GeoHash的查询过滤,将完全遍历整个docment文档,从效率上来看并不太合适,所以结合笛卡尔层后,能有效缩减少过滤范围,从性能上能很大程度的提高。
构建索引阶段:
String geoHash = GeoHashUtils.encode(latitude, longitude); docment.addField("geohash", geoHash); //Cartesian Tiers int tier = START_TIER;//开始构建索引的层数 //Create a bunch of tiers, each deeper level has more precision //将一条记录的经纬度对应全部笛卡尔层的tierBoxId作为域值构建索引 for (CartesianTierPlotter plotter : plotters) { docment.addField("tier_" + tier , plotter.getTierBoxId(latitude, longitude)); tier++; }
看到这里大家肯定明白了。越相近的经纬度在同层肯定会在同一个网格中,所以他们存储的tierBoxId就会是一样。那么查询的时候通过经纬度对应层的tierBoxId,也就能找到相同层域的docId,但是如果给定的的查询范围大,可能需要将若干层的所属网格的docId都查到。
整个查询过程是先通过笛卡尔层将若干个网格涉及的DocList存入bitSet,如下代码所示:
public DocIdSet getDocIdSet(final IndexReader reader) throws IOException { final FixedBitSet bits = new FixedBitSet(reader.maxDoc()); final TermDocs termDocs = reader.termDocs(); //需要查询的若干层网格的boxIdList,当然至此已经过滤掉不需要查询层的boxIdList final List<Double> area = shape.getArea(); int sz = area.size(); final Term term = new Term(fieldName);// // iterate through each boxid for (int i =0; i< sz; i++) { double boxId = area.get(i).doubleValue(); termDocs.seek(term.createTerm(NumericUtils.doubleToPrefixCoded(boxId))); // iterate through all documents // which have this boxId //遍历所有包含给定boxId的docList,并将其放入bitset while (termDocs.next()) { bits.set(termDocs.doc()); } } return bits; }
介绍完笛卡尔层的计算后,接下来介绍笛卡尔层过滤后返还的bitset如何和geoHash结合,从实现上讲其实很简单,就是将通过笛卡尔层过滤的数据结果集合 依次遍历计算其与查询给定的经纬度坐标的球面距离,同时将该计算距离和查询指定范围距离进行比较,如果大于给定距离,则将当前记录继续过滤掉,那么最终剩下的数据结果集合,将是满足查询条件的地理位置结果集合。具体实现流程见如下代码:
//将笛卡尔层的Filter作为Geohash的Filter参数传递进去,形成一个过滤链 filter = distanceFilter = new GeoHashDistanceFilter(cartesianFilter, lat, lng, miles, geoHashFieldPrefix);
再看GeoHashDistanceFilter中最核心的方法getDocIdSet():
public DocIdSet getDocIdSet(IndexReader reader) throws IOException { //在这里使用到了Lucene的FieldCache来作为缓存,实际上缓存了一个以docId为下标,base32编码为值的数组 final String[] geoHashValues = FieldCache.DEFAULT.getStrings(reader, geoHashField); final int docBase = nextDocBase; nextDocBase += reader.maxDoc(); return new FilteredDocIdSet(startingFilter.getDocIdSet(reader)) { @Override public boolean match(int doc) { //通过笛卡尔层的过滤后的doc直接找到对应的base32编码 String geoHash = geoHashValues[doc]; //通过解码将base32还原成经纬度坐标 double[] coords = GeoHashUtils.decode(geoHash); double x = coords[0]; double y = coords[1]; Double cachedDistance = distanceLookupCache.get(geoHash); double d; if (cachedDistance != null) { d = cachedDistance.doubleValue(); } else { //计算2个经纬度坐标的距离 d = DistanceUtils.getDistanceMi(lat, lng, x, y); distanceLookupCache.put(geoHash, d); } //小于给定查询距离的的docid放入缓存,以供下次使用,同时返回True代表当前docId是满足条件的记录 if (d < distance){ distances.put(doc+docBase, d); return true; } else { return false; } } };
从上述分析中大家应该可以想到 采用笛卡尔层 Filter结合GoHash Filter的实现方案,在计算规模上会比单独使用GeoHash少了很多,而在查询性能也会有更优异的表现。
最后附上一个本地Demo的查询实例:
用geofilter查找给定经纬度500km内的的数据
http://localhost:8983/solr/select/?q=*:*&fq={!geofilt pt=30.15,-79.85 sfield=tier d=500}
已有 0 人发表留言,猛击->>这里<<-参与讨论
ITeye推荐
- —软件人才免语言低担保 赴美带薪读研!—