4、操作实例
4.1 单列与多列支持中文检索
Create table mytable1(id number primary key, doc1 varchar2(400),doc2 clob,doc3 clob);
Insert into mytable1 values(1,'今天的天气很不错,我想去逛街','今天是星期天,不用上班。天天好心情','明天是星期一,要上班。心情不好');
Insert into mytable1 values(2,'天是蓝色的,万里无云。天气非常好。','天是多云的,天气看起来要下雨了。不适宜出门','天正在下雨,大雨倾盆。不能出门。');
Insert into mytable1 values(3,'this is a text','this is a word','this is a pdf');
Commit;
--先删除引用
begin
ctx_ddl.drop_preference('my_chinese_vgram_lexer');
ctx_ddl.drop_preference('my_chinese_lexer');
end;
--支持中文分词
Begin
ctx_ddl.create_preference('my_chinese_vgram_lexer', 'chinese_vgram_lexer');
ctx_ddl.create_preference('my_chinese_lexer', 'chinese_lexer');
End;
--先删除引用
begin
ctx_ddl.drop_preference('my_multi');
end;
--多列查询,如果仅仅是单列,则不用设置这个类型
Begin
Ctx_ddl.create_preference('my_multi', 'multi_column_datastore');
Ctx_ddl.set_attribute('my_multi', 'columns', 'doc1, doc2, doc3');
End;
drop index myindex;
--单列查询,支持中文的索引建立
Create index myindex on mytable(docs)
indextype is ctxsys.context
parameters ('datastore ctxsys.default_datastore lexer foo.my_chinese_lexer')
drop index idx_mytable;
--多列查询,支持中文的索引的建立
Create index idx_mytable on mytable1(doc1)indextype is ctxsys.context
parameters('datastore my_multi lexer foo.my_chinese_lexer');
--chinese_lexer词法分析器下的结果,三列都可以查询
Select * from mytable1 where contains(doc1, '今天')>0; --检索到第一条数据
Select * from mytable1 where contains(doc1, '不适宜')>0; --检索到第二条数据
Select * from mytable1 where contains(doc1, '适宜')>0; --检索不到数据,他的分词技术太简单,将‘不适宜’作为一个词了
Select * from mytable1 where contains(doc1, '出门')>0; --检索到第二条数据
Select * from mytable1 where contains(doc1, 'this is a word')>0; --检索到第三条数据,中英文适用
--chinese_vgram_lexer词法分析器下的结果,
--chinese_vgram_lexer词法分析器虽然没那么智能,但检索结果往往比较符合我们的要求,
--如:“不适宜”这个词语应该拆分为“不适宜”和“适宜”两个词语,而不是单独的作为一个词语,
--chinese_vgram_lexer可以查询的到,而chinese_lexer不可以。
drop index idx_mytable;
--多列查询,支持中文的索引的建立
Create index idx_mytable on mytable1(doc1)indextype is ctxsys.context
parameters('datastore my_multi lexer foo.my_chinese_vgram_lexer');
--chinese_vgram_lexer词法分析器下的结果,三列都可以查询
Select * from mytable1 where contains(doc1, '今天')>0; --检索到第一条数据
Select * from mytable1 where contains(doc1, '不适宜')>0; --检索到第二条数据
Select * from mytable1 where contains(doc1, '适宜')>0; --检索到第二条数据,这个分词虽然效率低点,但检索结果还可以
Select * from mytable1 where contains(doc1, '出门')>0; --检索到第二条数据
Select * from mytable1 where contains(doc1, 'this is a word')>0; --检索到第三条数据,中英文适用
--对于多列查询,更新列操作
--只更新从表,看是否能查到更新的信息
Update mytable1 set doc2='adladlhadad this datastore when your text is stored test' where id=2;
--同步更新索引
Begin
Ctx_ddl.sync_index('idx_mytable');
End;
--可见,虽然你检索是三个列,但是你更新的不是索引对应的那列(doc1),同步了索引也是不起作用的
Select * from mytable1 where contains(doc1,'adladlhadad')>0; --没有记录
--更新与doc1列原来相同内容(实际内容不变,只有操作而已)
Update mytable1 set doc1='天是蓝色的,万里无云。天气非常好。' where id=2;
--再同步更新索引
Begin
Ctx_ddl.sync_index('idx_mytable');
End;
--再查询一次
Select * from mytable1 where contains(doc1,'adladlhadad')>0; --有结果,可见,对于其他查询的列(非索引对应的列)的更新操作,可以连同索引对应的列一起更新,只是不改变索引的内容即可做到同步索引就可以出现效果了。
4.2 本地磁盘检索
create table mytable3(id number primary key, docs varchar2(2000));
insert into mytable3 values(111555,'1.txt');
insert into mytable3 values(111556,'1.doc');
insert into mytable3 values(111557,'1.xls');
insert into mytable3 values(111558,'1.pdf');
insert into mytable3 values(111559,'2.txt');
insert into mytable3 values(111560,'2.doc');
insert into mytable3 values(111561,'2.xls');
insert into mytable3 values(111562,'2.pdf');
commit;
--先删除引用
begin
ctx_ddl.drop_preference('COMMON_DIR');
end;
--建立 file datastore
begin
ctx_ddl.create_preference('COMMON_DIR','FILE_DATASTORE');
ctx_ddl.set_attribute('COMMON_DIR','PATH','D:\search');
end;
--先删除索引
drop index myindex3;
--建立索引,8个文件,内容简单,耗时1.5s
create index myindex3 on mytable3(docs) indextype is ctxsys.context parameters ('datastore COMMON_DIR lexer foo.my_chinese_lexer');
select * from mytable3 where contains(docs,'text')>0; --查询,支持txt
select * from mytable3 where contains(docs,'pdf')>0; --查询,支持pdf
select * from mytable3 where contains(docs,'excel')>0; --查询,支持excel
select * from mytable3 where contains(docs,'word')>0; --查询,支持doc
select * from mytable3 where contains(docs,'文本')>0; --查询,支持中文
select * from mytable3 where contains(docs,'文档')>0; --查询,支持中文
select * from mytable3 where contains(docs,'阅读')>0; --查询,支持中文pdf
select * from mytable3 where contains(docs,'这是Excel')>0; --查询,支持中文
--暂时测试支持doc,txt,xls,pdf
--更新了文件内容2.txt
select * from mytable3 where contains(docs,'这个测试用的文本')>0; --查询无更新好数据
--不同步索引,无效
--同步更新索引
Begin
Ctx_ddl.sync_index('myindex3');
End;
--再次查询
select * from mytable3 where contains(docs,'测试')>0; --还是无效
--用相同的值取代2.txt然后再同步索引
Update mytable3 set docs='2.txt' where id=111559;
--再同步索引
--同步更新索引
Begin
Ctx_ddl.sync_index('myindex3');
End;
--再次查询
select * from mytable3 where contains(docs,'测试')>0; --结果出现,可见,单更新文件内容,同步索引是无效的,索引认的是数据库纪录,数据库纪录改变,索引才会更新
--新增加文件,结果雷同。关键是要更新数据库纪录,即使改了文件内容,也要用相同的值update数据库纪录一次。
4.3 检索结果高亮显示
Create table my_high (id number primary key, docs varchar2(1000));
insert into my_high values (1, 'this is a oracle text example. And oracle is the key word.');
insert into my_high values (2, '<title>oracle text</title><body>this is a oracle ctx_doc hightlight example.</body>');
commit;
/
--建立索引
create index ind_m_high on my_high(docs) indextype is ctxsys.context;
--返回结果的偏移量
set serverout on
declare
v_restab ctx_doc.highlight_tab;
begin
ctx_doc.highlight('ind_m_high', 1, 'oracle', v_restab, true);
for i in 1..v_restab.count loop
dbms_output.put_line('begin with: ' || v_restab(i).offset || ' length: ' || v_restab(i).length);
end loop;
end;
/
begin with: 11 length: 6
begin with: 36 length: 6
ctx_doc.highlight参数说明:
ctx_doc.highlight(索引,数据库中的ID, 搜索关键字, 指明返回的偏移量是针对纯文本格式还是针对HTML格式, true);
true or false: 对比PLAINTEXT设置为FALSE和TRUE的区别可以发现,对于HTML所有的标识部分,Oracle统一认为长度等于2。
对于True: <title>oracle text</title><body>this is a oracle ctx_doc hightlight example.</body>,<title>认为是2个长度,false的话就全部纪录,认为总共有7个字符长度。
要在sqlplus执行
4.4 具体测试
4.4.1 基本的全文检索
--先删除引用
begin
ctx_ddl.drop_preference('my_chinese_vgram_lexer');
ctx_ddl.drop_preference('my_chinese_lexer');
end;
--支持中文分词
Begin
ctx_ddl.create_preference('my_chinese_vgram_lexer', 'chinese_vgram_lexer');
ctx_ddl.create_preference('my_chinese_lexer', 'chinese_lexer');
End;
Begin
Ctx_ddl.create_preference('F_DOCNEWS_Preference', 'multi_column_datastore');
Ctx_ddl.set_attribute('F_DOCNEWS_Preference', 'columns', 'F_CONTENT,F_DESCRIPTION,F_TITLE');
End;
drop index f_content_index;
Create index f_content_index on T_DOCNEWS(F_CONTENT)
indextype is ctxsys.context
parameters('datastore F_DOCNEWS_Preference lexer foo.my_chinese_lexer');
Select * from T_DOCNEWS where contains(F_CONTENT,'菲律宾')>0; --有结果,
4.4.2 带动态摘要的高亮全文检索
--先删除引用
begin
ctx_ddl.drop_preference('my_chinese_vgram_lexer');
ctx_ddl.drop_preference('my_chinese_lexer');
end;
--支持中文分词
Begin
ctx_ddl.create_preference('my_chinese_vgram_lexer', 'chinese_vgram_lexer');
ctx_ddl.create_preference('my_chinese_lexer', 'chinese_lexer');
End;
--先删除索引
drop index f_content_index;
--新建索引,默认属性,无过滤器,支持中文高级分词
Create index f_content_index on T_DOCNEWS(F_CONTENT)
indextype is ctxsys.context
parameters('datastore ctxsys.default_datastore filter ctxsys.null_filter section group
ctxsys.html_section_group lexer foo.my_chinese_lexer');
?--以下开始准备建立存储过程,先定义数组类型
CREATE or replace TYPE f_content_arr AS OBJECT(
id NUMBER ,
url varchar2(255),
title varchar2(255),
abstractcontent varchar2(255)
);
--定义数组变量
CREATE or replace type f_content_arr_re as table of f_content_arr;
--定义存储过程
create or replace procedure f_content_pro (keyword in varchar,v_cfjg out f_content_arr_re) is
v_restab ctx_doc.highlight_tab;
begin
DECLARE
i number;
s clob;
startnum number;
endnum number;
v_res_fun T_DOCNEWS%rowTYPE;
cursor c_fun is
select * from T_DOCNEWS where contains(F_CONTENT,keyword)>0;
BEGIN
i := 0;
v_cfjg := f_content_arr_re();
open c_fun;
LOOP
fetch c_fun
into v_res_fun;
EXIT WHEN c_fun%NOTFOUND;
i := i + 1;
s := v_res_fun.F_CONTENT;
v_cfjg.EXTEND;
ctx_doc.highlight('f_content_index', v_res_fun.F_ID, keyword, v_restab, false);
--只取第一个,没有loop循环
startnum:=v_restab(1).offset;
if v_restab(1).offset > 30 then
begin
startnum := v_restab(1).offset-30 ;
end;
end if;
if v_restab(1).offset <= 30 then
begin
startnum := 1 ;
end;
end if;
if length(s)-v_restab(1).offset > 30 then
begin
endnum := v_restab(1).offset+30 ;
end;
end if;
if length(s)-v_restab(1).offset <= 30 then
begin
endnum := length(s) ;
end;
end if;
v_cfjg(v_cfjg.count) := f_content_arr(v_res_fun.F_ID,v_res_fun.F_URL,v_res_fun.F_TITLE,substr(s,startnum,endnum-startnum));
dbms_output.new_line();
END LOOP;
end;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('TOO_MANY_ROWS');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(sqlerrm);
end f_content_pro;
--在此,全文检索存储过程定义完毕
--以下是sqlplus调用
declare
s f_content_arr_re;
begin
f_content_pro('菲律宾',s);
END;
Java后台调用存储过程并返回参数代码:
public ArrayList<DocNews> search(String keyword) {
ArrayList<DocNews> list = new ArrayList<DocNews>();
Connection conn = null;
ResultSet rs = null;
CallableStatement stmt = null;
DocNews docnews;
try {
conn = DBPool.getConnection();
stmt = null;
String procName = new StringBuffer().append("{ call f_content_pro(?,?) } ").toString();
stmt = conn.prepareCall(procName);
stmt.setString(1, keyword);
stmt.registerOutParameter(2, Types.ARRAY, "F_CONTENT_ARR_RE");
stmt.execute();
ARRAY arr = (ARRAY) stmt.getArray(2);
rs = arr.getResultSet();
while (rs.next()) {
STRUCT struct = (STRUCT) rs.getObject(2);
Object[] obs = struct.getAttributes();
docnews = new DocNews();
docnews.setId(((BigDecimal)obs[0]).longValue());
docnews.setUrl((String)obs[1]);
docnews.setTitle((String)obs[2]);
docnews.setAbstractcontent((String)obs[3]);
list.add(docnews);
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
注:在java中调用方法,除了在项目里加入class12.jar包以外,还需要加入Oracle自带的orai18n.jar包,如果仅仅是执行main方面,则可以,但如果是web项目,则要将orai18n.jar包加入到jdk的%jdk%\jre\lib\ext目录中才行。如果没有orai18n.jar这个包会造成检索调用存储过程返回结果是乱码(???三个问号)。
4.4.3 检索简单界面图
5.检索性能
执行以下索引
Create index f_content_index on T_DOCNEWS(F_CONTENT)
indextype is ctxsys.context
parameters('datastore F_DOCNEWS_Preference lexer foo.my_chinese_lexer');
总共5272条新闻,总耗时61s
合计约一分钟5000条
查询仅需200多毫秒