Článek přečtěte do 6 min.

Oracle Text poskytuje snadné fulltextové vyhledávání v textu v databázi Oracle. Ve většině případů můžete vytvořit jednoduchý výchozí textový index, který bude fungovat hned po vybalení.

Někdy je však potřeba přizpůsobit index tak, aby se vypořádal s neobvyklým textem nebo s ohledem na konkrétní požadavky na vyhledávání. Jedním z běžných příkladů je, jak by vyhledávání mělo pracovat se znaky s diakritikou. Mělo by hledání ‚kavárna‘ odpovídat také ‚kavárna‘? Toto je složitější téma, než se může na první pohled zdát, takže čtěte dál, kde najdete všechny krvavé detaily.

Lexer provádí počáteční část zpracování textu do indexovatelných jednotek. V aplikaci Oracle Text existuje řada atributů lexer, které ovlivňují způsob zacházení se znaky s diakritikou. Podíváme se na každý z nich a zvážíme účinky, které mají, samostatně a společně.

Než se na ně podíváme, rychlá odbočka k psaní znaků s diakritikou, pokud je nemáte nativně na klávesnici.

Zdá se, že většina světa se do značné míry usadila na UTF-8 jako na způsob reprezentace textu. UTF-8 je kompatibilní se 7bitovým ASCII pro běžná římská písmena AZ, čísla a běžná interpunkční znaménka. Pro ostatní znaky, jako jsou znaky s diakritikou a jiné než římské abecedy (např. řečtina, azbuka, arabština, čínština), používá více bajtů – až čtyři bajty na znak. Microsoft Windows (alespoň v západním světě) však používá jako vstupní znakovou sadu znakovou sadu WIN1252.

Pokud chci napsat jeden ze znaků s diakritikou v této sadě, mohu ve většině aplikací Windows podržet klávesu ALT a poté zadat čtyřmístný desetinný kód pro tento znak na numerické klávesnici. Chcete-li tedy zadat malý znak přehlásky o („o“ se dvěma tečkami nad ním: „ö“), podržte ALT a na numerické klávesnici napíšu 0246.

Pokud používáte Emacs, můžete použít CTRL+x 8 následované interpunkčním znaménkem a písmenem, takže o-umlaut bude CTRL+x 8 " o.

V případě, že je vaším cílem vložit znaky do databáze Oracle, existuje několik úskalí. SQL*Net se pokouší převést znaky ze znakové sady klienta do znakové sady databáze a mnoho klientských programů předpokládá výchozí znakovou sadu US7ASCII. Konverzi se můžete vyhnout nastavením proměnné prostředí, například pokud je vaše vstupní znaková sada a znaková sada databáze UTF8, můžete na svém klientovi nastavit následující proměnnou:

export NLS_LANG=american_america.al32utf8

Případně se můžete vyhnout jakémukoli riziku nejednoznačnosti pomocí funkce SQL UNISTR(). To přijímá hodnoty hexadecimálních znaků Unicode (USS-2, nikoli UTF-8, omlouváme se!), před kterými je zpětné lomítko, a vypíše správný znak. Takže „kavárna“ by mohla být vložena buď pomocí:

INSERT INTO tab VALUES ('caf'||UNISTR('\\00E9'));
INSERT INTO tab VALUES (UNISTR('caf\00E9'));

UNISTR() se důrazně doporučuje při psaní skriptů kvůli zamezení problémů se znakovou sadou na straně klienta. Čtení je samozřejmě o něco těžší.

Nyní se podívejme na různé atributy lexeru používané pro přizpůsobení indexu a na účinky, které mají.

BASE_LETTER

Začněme možná tím nejjednodušším – atributem BASE_LETTER. Pokud toto nastavím na „true“, znaky s diakritikou v textu budou efektivně indexovány jako jejich základní tvar.

Pokud tedy například zaindexuji německé slovo „schön“ (což v angličtině znamená krásný), pak skutečný indexovaný token bude „schon“. To znamená, že pokud uživatel hledá „schön“ nebo „schon“, slovo bude nalezeno ( protože dotazovaný výraz je zpracován stejným způsobem).

To výrazně usnadňuje život lidem, kteří nemají na klávesnici znak oh-umlaut „ö“ a neznají výše zmíněný trik ALT (nebo nechtějí hledat potřebný kód znaku).

Takže to je jednoduché – můžete tuto možnost nastavit vždy, ne? No, ne nutně. Protože v němčině má slovo „schon“ ve skutečnosti docela jiný význam než „schön“ a němečtí uživatelé by nechtěli najít slovo bez přízvuku, pokud by konkrétně chtěli hledat slovo s diakritikou.
Takže jednoduché pravidlo je toto: Pokud si myslíte, že většina vašich uživatelů bude hledat slova, pro která mají správnou klávesnici (například němečtí uživatelé hledající německý text), pak je nejlepší nastavit BASE_LETTER na false. Pokud si ale myslíte, že uživatelé mohou chtít hledat cizí slova, pro která nemají správnou klávesnici (například angličtina nebo nadnárodní uživatelé hledající německý nebo smíšený text), pak je nejlepší nastavit BASE_LETTER na TRUE.

ALTERNATE_SPELLING

Nyní to začíná být trochu složitější. Některé jazyky – zejména němčina – vám umožňují vyhnout se používání znaků s diakritikou tím, že slova hláskujete jinak. V němčině je znak s diakritikou nahrazen neakcentovanou formou stejného písmene, po níž následuje „e“. Takže „schön“ by mohlo být stejně platně napsáno „schoen“ a každý německý uživatel by to poznal jako stejné slovo. Stejně tak „Muenchen“ (město, kde se mluví anglicky mluvící obyvatelé města Mnichov), by bylo rozpoznáno jako stejné jako „München“.

Abychom mohli tyto alternativní formy pravopisu považovat za ekvivalentní, má Oracle Text atribut ALTERNATE_SPELLING. Při nastavení na „němčinu“ bude Oracle Text hledat „alternativní formu“ slova „samohláska + e“ a indexovat jak tento, tak i formu slova s ​​diakritikou. Při zpracování dotazu stejně přeloží „oe“ na „ö“ atd., čímž zajistí, že slovo lze vždy najít, bez ohledu na to, která z alternativních forem se hledá a která je v indexovaném textu.

Každý, kdo sleduje pozorně, by se mohl zeptat: „Proč indexuje jak alternativní tvar „schoen“ tak i tvar s diakritikou „schön“? Jistě by stačilo indexovat pouze tvar „shcön“? No, většinou by to bylo. Ale co se stane pokud by dotyčné slovo bylo ve skutečnosti anglické slovo uprostřed německého textu, například „básník“? Dobře, bylo by indexováno jako „pöt“ a každý, kdo by hledal „básník“, by ho stále našel (protože transformace je použito i na hledaný výraz). Ale co kdyby použili zástupný znak a hledali „po%“? Stále by očekávali, že výraz najdou, ale pokud by byl indexován pouze tvar s diakritikou, nenašli by to. indexovat oba formuláře, pro každý případ.

Kombinace ALTERNATE_SPELLING a BASE_LETTER

Dobře, takže chceme, aby byl ALTERNATE_SPELLING pro náš německý text nastaven na „německý“. Ale také víme, že lidé s neněmeckou klávesnicí ji často hledají. Nastavili jsme tedy také BASE_LETTER na true. Co se stane teď?

Pokud indexátor narazí na „schön“, ALTERNATE_SPELLING to normálně indexuje bez jakékoli změny. Ale BASE_LETTER jej vynutí bez přízvuku a „schon“ je ve skutečnosti indexován. Pokud indexátor narazí na „schoen“, pak ALTERNATE_SPELLING rozhodne, že by měl být indexován jako „schoen“ i „schön“. Ale před zápisem tokenů do indexu se použije BASE_LETTER, takže se indexují tokeny „schoen“ a „schon“.

To vše funguje dobře a oba výrazy můžeme najít hledáním „schon“, „schön“ nebo „schoen“. Skvělý!

Ale (vždy existuje nějaké ale), co se stane, když indexujeme francouzské slovo „Rouède“ (název města poblíž španělských hranic)?“ „uè“ není kandidátem na ALTERNATE_SPELLING, takže zůstane samo. Potom se použije BASE_LETTER a do indexu se zapíše slovo „Rouede“. Pokud uživatel hledá „Rouède“, pak zpracování dotazu funguje stejně, vyhledávání se převede na „Rouede“ a dotaz funguje dobře. Pokud však uživatel hledá formu základního písmene „Rouede“, věci nejdou tak dobře. Tentokrát se na dotazovaný výraz použije ALTERNATE_SPELLING ( protože procesor dotazu nemá jak vědět, že znak „e“ by měl být s diakritikou) a hledaný výraz se převede na „Roüde“. Poté se použije BASE_LETTER, a v indexu hledá „Roude“. Ale indexovaný výraz je „Rouede“, takže nebylo nic nalezeno.

OVERRIDE_BASE_LETTER

K vyřešení tohoto problému byl zaveden atribut OVERRIDE_BASE_LETTER.

Pokud nastavíte OVERRIDE_BASE_LETTER na „true“, pak ALTERNATE_SPELLING „maskuje“ BASE_LETTER. To znamená, že pokud se v textu setkáme se znaky s diakritikou, které mají alternativní tvar (např. „ö“), zaindexujeme je v jejich původní podobě s diakritikou a také v jejich alternativní podobě. Pokud se s nimi setkáme v jejich alternativní formě (např. Muenchen), budeme indexovat POUZE alternativní formu a nebudeme je transformovat. Znaky s diakritikou, které nemají alternativní tvar (např. „è“), je na ně použito zpracování BASE_LETTER, které je transformuje na ekvivalentní znak bez diakritiky. Poté v době dotazu použijeme pouze ALTERNATE_SPELLING na všechny vhodné hledané výrazy s diakritikou a BASE_LETTER na všechny ostatní. To má pozitivní efekt, že lze nalézt náš předchozí příklad „rouède“,

Má to negativní efekt, že vyhledávání podle základních písmen již nefunguje na německých slovech – už nemůžeme hledat „schon“, bude fungovat pouze „schön“ nebo „schoen“. Takže OVERRIDE_BASE_LETTER dává smysl, pokud chceme provádět ALTERNATE_SPELLING na německých (nebo jiném specifikovaném jazyce) slovech a BASE_LETTER na všech ostatních jazycích.

Více informací

Úplné specifikace těchto možností si můžete přečíst v příručce Oracle Text Developers Guide v následujících tématech: Základní nastavení Lexer Alternativní pravopis

Příloha: Testovací skript

Toto je skript, který jsem použil k testování účinků různých možností. Abych se vyhnul jakýmkoli problémům s překladem znakové sady, použil jsem funkci UNISTR() k vytvoření znaků Unicode pro mé znaky s diakritikou. Všimněte si, že před německými slovy jsou předpony dvoupísmenné kódy „wa“ – ccentem , „na“ – n o a ccent a „af“ alternativní forma. To mi umožnilo rozlišovat v indexu mezi indexovými termíny odvozenými od „schön“ a těmi, které jsou odvozeny od „schon“.

begin <font></font>
ctx_ddl.drop_preference ( 'my_lexer'); <font></font>
end; <font></font>
/<font></font>
<font></font>
begin <font></font>
ctx_ddl.create_preference ( 'my_lexer', 'BASIC_LEXER' ); <font></font>
ctx_ddl.set_attribute ( 'my_lexer', 'BASE_LETTER', 'true' ); <font></font>
ctx_ddl.set_attribute ( 'my_lexer', 'OVERRIDE_BASE_LETTER', 'true'); <font></font>
ctx_ddl.set_attribute ( 'my_lexer', 'ALTERNATE_SPELLING','german' ); <font></font>
end; <font></font>
/<font></font>
<font></font>
drop table tt; <font></font>
create table tt(a1 number primary key,text varchar2(45));<font></font>
<font></font>
-- town name "Rouède", accent on the e <font></font>
insert into tt values (1,'rou'||unistr('\00E8')||'de');<font></font>
-- schön with accent (wa) <font></font>
insert into tt values (2,'wasch'||unistr('\00F6')||'n');<font></font>
-- schon no accent (na) <font></font>
insert into tt values (3,'naschon');<font></font>
-- muenchen alternate form (af) <font></font>
insert into tt values (4,'afmuenchen');<font></font>
commit;<font></font>
<font></font>
select * from tt;<font></font>
<font></font>
create index tta on tt(text) indextype is ctxsys.context parameters ( 'lexermy_lexer' );<font></font>
<font></font>
set feedback 2<font></font>
select token_text, token_type from dr$tta$i;<font></font>
<font></font>
PROMPT searching for the base letter form, without accent on the first e select * from tt where contains(text,'Rouede')>0;<font></font>
<font></font>
PROMPT and with the accent select * from tt where contains(text,'Rou'||unistr('\00E8')||'de') > 0;<font></font>
--select * from tt where contains(text,'m'||unistr('\00FC')||'nchen')>0; --select * from tt where contains(text,'muenchen') > 0;<font></font>
<font></font>
set echo on<font></font>
--select * from tt where contains(text,'naschoen') > 0; --select * from tt where contains(text,'naschon') > 0; --select * from tt where contains(text,'na'||unistr('\00F6')||'n') > 0;<font></font>
select * from tt where contains(text,'waschon') > 0; select * from tt where contains(text,'waschoen') > 0; select * from tt where contains(text,'wa'||unistr('\00F6')||'n') > 0;<font></font>
set echo off<font></font>
<font></font>
-- The following section shows how to see how the query has been transformed - it shows -- the actual words looked for in the index.<font></font>
<font></font>
drop table test_explain; create table test_explain( explain_id varchar2(30), id number, parent_id number, operation varchar2(30), options varchar2(30), object_name varchar2(64), position number, cardinality number);<font></font>
<font></font>
begin <font></font>
ctx_query.explain( <font></font>
index_name => 'tta', <font></font>
text_query => 'wasch'||unistr('\00F6')||'n', <font></font>
explain_table => 'test_explain', <font></font>
sharelevel => 0, <font></font>
explain_id => 'Test'); <font></font>
end; <font></font>
/<font></font>
<font></font>
col explain_id for a10 col id for 99 col parent_id for 99 col operation for a10 col options for a10 col object_name for a20 col position for 99<font></font>
<font></font>
select explain_id, id, parent_id, operation, options, object_name, position from test_explain order by id;<font></font>

Zdroj: Oracle