渐进式放松
渐进式放松是 Oracle 10g 提供的一种文本搜索新技巧。
本文旨在使您熟悉这一技巧,并使您明了使用该技巧的情形及方法。本文假定您具备 Oracle Text 的基本工作知识,如在查询表达式中使用的操作符。
何时使用?
首先,让我们来看一个搜索情形。
假定有一个销售书籍的网站。用户在“search author”框中搜索“Michael Crichton”。OK,非常容易。执行搜索,返回与搜索准则匹配的前 10 个命中结果(或任何东西)。
但如果用户拼错了名字,搜索“Michel Crichton”,那该怎么办?在这种情况下,处理该情形的一种好策略是从以下这些搜索中分别找出前 10 个命中结果:
-
作者与“Michel Crichton”精确匹配的任何书籍
-
与任一单词模糊匹配(以正确的顺序)的任何书籍
-
包含任一单词的任何书籍
-
与任一单词模糊匹配的任何书籍
当然,我们可以在如下搜索中实现此目的
select book_id from books
where contains (author, '(michel crichton) OR (?michel ?crichton)
OR (michel OR crichton) OR (?michel OR ?crichton)
但这个搜索存在两个问题:
-
从用户的角度看,匹配程度低的命中结果将和匹配程度高的命中结果混在一起。用户希望将匹配程度高的结果显示在前面。
-
从系统的角度看,该搜索效率不高。即使有与“Michel Crichton”精确匹配的许多命中结果,仍然需要执行扩展的模糊查询,并获取满足查询的所有行的数据。
一种替代方法是运行四个单独的查询。利用这种方法,我们可以首先执行精确搜索,然后只需运行这些使用更松散准则的查询 — 如果需要这些查询来为结果页面获取足够多命中结果的话。
但且不谈潜在运行多条查询的低效性,我们还需要消除重复的结果。“松散”查询在很多情况下将命中精确查询返回的行(同时还包括其他许多更不精确的命中结果)。为了避免结果集中的重复,应用程序必须筛选这些命中结果,这就编程和维护而言,任务可能很艰巨(如果不只是想要获得原始性能的话)。
解决方案
为了解决这个问题,Oracle 数据库 10g 的 Oracle Text 引进了“渐进式放松”。它允许您指定要运行的不同搜索,Oracle 将依次运行每个搜索,返回已消除重复的结果,直到应用程序不再获取命中结果。
返回的匹配度经过处理,以便您按匹配度排序时可以确保按前面准则指定的所有行将在按后面准则指定的行之前返回。
好处
渐进式放松的好处是应用程序开发人员能够以声明的方式指定在用户查询上的应用操作。不需要分析查询并将操作符应用到每个搜索项上 — 开发人员只需指定应用到各个搜索项上的选项以及这些选项的组合方式(例如
AND
或
OR
)。
应用程序获得的好处还包括在查询的较早阶段(在 docid 到 rowid 的转换之前)就自动消除了重复的结果 — 这比在最后的阶段执行这一操作(如果您运行多个查询,您就必须这么做)要更有效。
查询模板
渐进式放松的实际实施是通过查询模板机制进行的。如果您以前没有接触过这一机制,不用担心 — 它非常简单,下面的例子将使您更加清楚明了。从根本上说,一个查询模板是一个在 CONTAINS 子句中代替简单查询字符串使用的 XML 段。
您还可以用查询模板做其他一些事情,如指定语言、查询语法和匹配度选项,但我们在此不作介绍。
好的,来看我们的第一个例子:
create table mybooks (title varchar2(20), author varchar2(20));
insert into mybooks values ('Consider the Lillies', 'Ian Crichton Smith');
insert into mybooks values ('Sphere', 'Michael Crichton');
insert into mybooks values ('Stupid White Men', 'Michael Moore');
insert into mybooks values ('Lonely Day', 'Michaela Criton');
insert into mybooks values ('How to Teach Poetry', 'Michaela Morgan');
create index auth_idx on mybooks (author) indextype is ctxsys.context;
SELECT score(1), title, author FROM mybooks WHERE CONTAINS (author, '
<query>
<textquery>
<progression>
<seq>michael crichton</seq>
<seq>?michael ?crichton</seq>
<seq>michael OR crichton</seq>
<seq>?michael OR ?crichton</seq>
</progression>
</textquery>
</query>', 1) > 0 ORDER BY score(1) DESC;
该查询的输出为:
SCORE(1) TITLE AUTHOR
---------- -------------------- --------------------
76 Sphere Michael Crichton
51 Lonely Day Michaela Criton
26 Stupid White Men Michael Moore
26 Consider the Lillies Ian Crichton Smith
1 How to Teach Poetry Michaela Morgan
根据我们的渐进序列中的第一个 <seq> 项,我们可以看出第一行是精确匹配。第二行按顺序对应每一项的模糊匹配 — 我们的第二个准则。第三行和第四行是“micheal OR chrichton”精确查询的结果,最后一行是模糊命中其中一个搜索项的单个匹配结果。
很显然,在这个例子中,我们将获取所有的行,因此使用渐进式放松没有很大的优势。但我们可以使用一个 PL/SQL 游标限制仅返回前两个命中结果:
set serveroutput on format wrapped
declare
max_rows integer := 2;
counter integer := 0;
begin
-- do the headings
dbms_output.put_line(rpad('Score', 8)||rpad('Title', 20)||rpad('Author', 20));
dbms_output.put_line(rpad('-----', 8)||rpad('-----', 20)||rpad('------', 20));
-- loop for the required number of rows
for c in (select score(1) scr, title, author from mybooks where contains (author, '
<query>
<textquery>
<progression>
<seq>michael crichton</seq>
<seq>?michael ?crichton</seq>
<seq>michael OR crichton</seq>
<seq>?michael OR ?crichton</seq>
</progression>
</textquery>
</query>
', 1) > 0) loop
dbms_output.put_line(rpad(c.scr, 8)||rpad(c.title, 20)||rpad(c.author, 20));
counter := counter + 1;
exit when counter >= max_rows;
end loop;
end;
/
该查询的输出为:
Score Title Author
----- ------ -------
76 Sphere Michael Crichton
51 Lonely Day Michaela Criton
作为一个应用程序设计人员,我们可能还需要另外一个特性。那就是在成功应用了第一个搜索准则后停止搜索。因此在我们的例子中,如果我们的“michael chrichton”搜索精确匹配到了一个或多个命中结果,我们就不想再尝试任何其他搜索。如果精确搜索失败,我们才希望尝试其他搜索,直到其中某个搜索返回一行或多行。
目前就查询模板语法而言,还不能做到这一点。然而,有可能通过着眼于返回的匹配度来在应用程序层实现这一点。让我们仔细观察一下上次测试的完整结果:
Score Title Author
----- ----- ------
76 Sphere Michael Crichton
51 Lonely Day Michaela Criton
26 Stupid White Men Michael Moore
26 Consider the Lillies Ian Crichton Smith
1 How to Teach Poetry Michaela Morgan
现在,假定文本查询的最高匹配度为 100,我们在搜索中有四个 <seq> 步骤,我们可能会在此发现什么。特别是,第一个步骤中的任何匹配的匹配度将始终在四分之三以上 — 76% 到 100%。下一步骤的匹配度将在 51-75% 的范围之内,再下一个步骤为 26-50%,最后一个步骤为 1-25%。如果我们的查询有 5 个步骤,那么匹配度将分别在 81-100%、61-80%、41-60%、21-40% 和 1-20% 的范围之内。
因此为了在第一次有效搜索后停止返回结果,我们需要检测跨越这些界限的某个的匹配度。为了实现这一目的,我们
必须
提前知道有多少个步骤。实现所有这些的 PL/SQL 比以前多了一些技巧:
declare
max_rows integer := 2;
counter integer := 0;
number_of_steps integer := 4;
score_range_size integer; -- 33 for 3 steps, 25 for 4, 20 for 5 etc
this_score_group integer; -- final step is 1, penultimate step is 2 ...
last_score_group integer := 0; -- to compare change
begin
-- do the headings
dbms_output.put_line(rpad('Score', 8)||rpad('Title', 20)||rpad('Author', 20));
dbms_output.put_line(rpad('-----', 8)||rpad('-----', 20)||rpad('------', 20));
for c in (select score(1) scr, title, author from mybooks where contains (author, '
<query>
<textquery>
<progression>
<seq>michael crichton</seq>
<seq>?michael ?crichton</seq>
<seq>michael OR crichton</seq>
<seq>?michael OR ?crichton</seq>
</progression>
</textquery>
</query>
', 1) > 0) loop
score_range_size := 100/number_of_steps;
this_score_group := c.scr/score_range_size;
exit when this_score_group < last_score_group;
last_score_group := this_score_group;
dbms_output.put_line(rpad(c.scr , 8)||rpad(c.title, 20)||rpad(c.author, 20));
counter := counter + 1;
exit when counter >= max_rows;
end loop;
end;
/
上述代码的输出为:
Score Title Author
----- ----- ------
76 Sphere Michael Crichton
我们可以增加一个新的、更严格的步骤(记住增加 number_of_steps 变量的值)。这实际上不会找到任何东西,但它将演示该过程将一直进行,直到确实找到至少一行:
number of steps number := 5;
...
<query>
<textquery>
<progression>
<seq>michael p crichton</seq>
<seq>michael crichton</seq>
<seq>?michael ?crichton</seq>
<seq>michael OR crichton</seq>
<seq>?michael OR ?crichton</seq>
</progression>
</textquery>
</query>
我们的输出将为:
Score Title Author
----- ----- ------
61 Sphere Michael Crichton
最后,有一个简化可以使应用程序开发人员的工作更加轻松。生成上述的全部语法可能使程序非常复杂。因此有一种“简短”的语法,称为“查询重写模板”:
<query>
<textquery> michael crichton
<progression>
<seq><rewrite>transform((TOKENS, "{", "}", " "))</rewrite></seq>
<seq><rewrite>transform((TOKENS, "?{", "}", " "))</rewrite>/seq>
<seq><rewrite>transform((TOKENS, "{", "}", "OR"))</rewrite></seq>
<seq><rewrite>transform((TOKENS, "?{", "}", "OR"))</rewrite></seq>
</progression>
</textquery>
</query>
这将创建与上面相同的包含四个步骤的语法。TOKENS 之后的参数如下:
-
前缀 — 在每个标记前放什么
-
后缀 — 在每个标记后放什么
-
连接器 — 连接每个标记的操作符。空格用于进行词组搜索。
将每个词语用括弧“{}”括住通常是个不错的主意,万一用户输入保留字(如 STEM 或 NT)呢。不过当心,在括弧后添加通配符会产生奇特的效果 — {dog}% 与 dog% 是不同的。
|