ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

DBならOracleでしょ♪コミュのOracleText

  • mixiチェック
  • このエントリーをはてなブックマークに追加
OracleTextで困っています。

検索によってレコードが検索できるのですが、
検索する文字列に制限があるものは、OracleTextの予約語だけでいいのでしょうか?
ちなみに検索する文字はXMLの内容です。

Javaのプログラム上によってXMLを検索するのですが、検索する文字列に自動的にエスケープすればいいとおもったのですが、しかしOracleTextの予約語自体を検索する場合に困ってしまうと思います。

コメント(16)

検索文字列に制限なんてありましたっけ?
それに、Oracle Textに予約語もあったかなぁ…

テストケースを載せてもらえれば、何か分かるかもしれませんが。
Oracle Textの予約語であっても、中括弧 { } でエスケープすれば検索できますよ。

こんな感じ・・・
SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, '{AND}') > 0;

Javaプログラム側で一律に中括弧を付けてしまえばいいのでは?

なお、「AND」「OR」「NOT」などは、デフォルトではストップワードなので、
ストップリストを CTXSYS.EMPTY_STOPLIST に設定することも忘れずに・・・。
OracleTextはマイナーなものかと思ってて返信はないと思っていましたが、返信いただきありがとうございます。

>それに、Oracle Textに予約語もあったかなぁ…

はい、それが予約語がたくさんあるのですよね・・。

下記のとおりです。
URL:予約語
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/text.102/B19214-01/cqspcl.htm#CBHDJBGB

>Javaプログラム側で一律に中括弧を付けてしまえばいいのでは?

AND検索やOR検索を可能にしたいときは、
たとえばANDの文字があるときは }AND{ へ置き換えてやれば大丈夫なのかが心配です。
{}AND{}

>なお、「AND」「OR」「NOT」などは、デフォルトではストップワードなので、ストップリストを CTXSYS.EMPTY_STOPLIST に設定することも忘れずに・・・。

すみませんが上記のことがわからなかったですが、
検索に引っかからないワード(ワードストップワード)になっていることを解除するには、CTXSYS.EMPTY_STOPLISTへ登録すれば検索されるようになるという認識で正しいでしょうか?


具体的な文字の検索についてですけど、改行文字の検索がうまくいきません。

SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, chr(13)) > 0;

と行うとエラーが出てしまいます。
だからといって''をつけると、「chr(13)」自体の文字を探す感じになってしまいますし・・。

また、タブ文字の検索についてですが、
SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, chr(9)) > 0;

SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, '{ }') > 0;({}の中にタブ文字が入ってます。)

と検索しても1行もヒットしないです。
きちんとタブ文字をINSERT INTOで挿入してあります。

あと、OracleTextは文字の一部分で検索できないのでしょうか?
また、たとえば「PANDA」と言う文字を検索するときに「AND」で検索したいときに、

SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, '{AND}') > 0;

とやってもヒットせず
だからといって、ワイルドカードの%を前後に入れて

SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, '%{AND}%') > 0;

とやってみても{}が空白とみなされて「%」「AND」「%」と3つに分割されてしまうせいかヒットしません。

あと、深刻な問題は、すべてを{}で囲むようにしても
SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, '{}}}}}{{{}}}') > 0;
のようにデタラメに複数{}が入れられるとエラーが発生してしまうようになります。
Oracle Textの予約語によるAND検索やOR検索を実行したい場合には、

SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, '{AND} AND {OR}') > 0;
SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, '{AND} OR {OR}') > 0;

などのようにすればOKです。

それと、ストップワードに関しては、CREATE INDEX 文で次のように記述するだけです。

CREATE INDEX 索引名 ON テーブル名 (列名)
INDEXTYPE IS CTXSYS.CONTEXT
PARAMETERS ('
stoplist CTXSYS.EMPTY_STOPLIST
');

CREATE INDEX 文で CTXSYS.EMPTY_STOPLIST を指定することで、ストップワードの機能自体がOFFになりますので、「CTXSYS.EMPTY_STOPLISTへ登録」などの操作は必要ありません。

また、改行文字やタブ文字は、Oracle Textの検索対象から除外されています。詳しくは、
http://otndnld.oracle.co.jp/products/oracle8i/intermedia/htdocs/imt.htm
をご覧になってください。この中で「空白文字」となっているものが、検索対象から除外されます。

空白文字をどうしても検索したければ、空白文字を別の (Oracle Textで検索対象となっている) 文字に置き換えるか、Oracle Textを使うのをやめるしかないですね・・・。

Oracle Text はもともと全文検索のための仕組みなので、改行文字やタブ文字などは、単語の区切りとして認識されるだけで、検索の対象からは除外されるのです。言語処理を意識しない、単純な部分文字列検索を実行したいのであれば、Oracle Textではなく、別の仕組みを検討する方がいいかもしれません。

また、「PANDA」を「AND」で検索したい場合には、
SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, '%AND%') > 0;
でOKです。

それと、エスケープ文字がめちゃくちゃに入力されることが心配であれば、Javaプログラム側でOracle Textのエスケープ文字 (「{」「}」「\」の3つ) を一律に排除してから CONTAINS 関数に渡せばいいと思います。
ご返事本当にどうもありがとうございます。

ストップワードの追加のためにCREATE INDEX 文で CTXSYS.EMPTY_STOPLIST を指定してみましたけど、変化がありませんでした。しない状態でも{AND}を検索することはできるのですが、違いがわからないです。

Javaプログラム側で一律に中括弧をつけた場合、ANDとORやワイルドカードのみをエスケープさせずにする方法はJavaで文字列を検索しつつ括弧をはずすしかないですよね?

単純な部分文字列というのはOraclTextの予約語の1文字の記号だけでなく、.や/といったものも単純な部分文字列なのですよね?

ABCDEを検索するためには、Aでは検索してくれないのでJavaで%A%に変換することをやらないといけませんね。Oracle側で設定があればいいのですが・・・。
> しない状態でも{AND}を検索することはできるのですが、違いがわからないです。

CTXSYS.EMPTY_STOPLIST を指定しないと、「AND」は任意のストップワードと一致します。

例えば、「This is a pen.」というドキュメントが格納されていた場合・・・

CREATE INDEX 文で CTXSYS.EMPTY_STOPLIST を指定しなければ・・・
このドキュメントは、「a pen」で検索しても「the pen」で検索しても、ヒットします。(「a」「the」は共にストップワードです。)
つまり、Oracle Textの索引情報としては、「そこにストップワードがあった」という情報のみが保持され、そのストップワードが具体的に何であるかという情報は保持されない、ということです。

CREATE INDEX 文で CTXSYS.EMPTY_STOPLIST を指定すれば・・・
「This is a pen.」は、「the pen」で検索してもヒットしません。「a pen」で検索した場合のみヒットします。

> Javaプログラム側で一律に中括弧をつけた場合、ANDとORやワイルドカードのみをエスケープさせずにする方法はJavaで文字列を検索しつつ括弧をはずすしかないですよね?

「AND」や「OR」を必ずOracle Textの演算子として認識させたいのであれば、それでいいと思います。

ちょっと、何をやりたいのかよく分からないのですが、当初は「AND」や「OR」などのOracle Textの予約語も含めて、全てキーワードとして検索をしたいということではなかったでしょうか。

いずれにしても、Javaプログラム内で必要な処理を行うようにしてください。

> 単純な部分文字列というのはOraclTextの予約語の1文字の記号だけでなく、.や/といったものも単純な部分文字列なのですよね?

Oracle Textで空白文字として扱われるのは、Oracle Textで予約語となっている記号文字だけではありません。これは、前回示したURLにあるとおりです。

> ABCDEを検索するためには、Aでは検索してくれないのでJavaで%A%に変換することをやらないといけませんね。Oracle側で設定があればいいのですが・・・。

英数字でsubstring検索を実行する場合には、ワイルドカード文字を付けた上で CONTAINS 関数に渡す必要があります。
begin
ctx_ddl.drop_preference('索引名');
end;
/

drop index 索引名;

で一度索引を消した後で、CTXSYS.EMPTY_STOPLISTを試してみましたが、どちらの場合でも'the pen'はヒットしませんでした。

>Oracle Textで空白文字として扱われるのは、Oracle Textで予約語となっている記号文字だけではありません。これは、前回示したURLにあるとおりです。

空白文字だけでなく、整数区切り文字の.(ドット)や⇒などの記号も検索対象になってないのですね。
さらに'は{}ではなく、''でエスケープなのですね。

ctx_ddl.set_attribute('索引名', 'printjoins', '* & = ? { } \ ( ) . ^ [ ] - ; ~ | @ $ ! > * % _ '' < " / , ');

というようにprintjoinsで追加すれば空白文字も検索されたのですが、ここに全て追加するしか方法がなさそうですね。
全ての文字を全て洗い出すのは難しそうですし・・。

ドットや!の場合は下記も追加しなくてはなりませんでした。
ctx_ddl.set_attribute('索引名', 'punctuations', 'YES');
確かに、printjoin や punctuation を使えば、空白文字や整数区切り文字をカスタマイズできますね。

# ただし、これらは basic_lexer の属性です。basic_lexer は日本語を検索できませんので注意してください。
# 検索対象に日本語が含まれる場合、basic_lexerは日本語のコードポイントを無視して索引を作成します。
# (CREATE INDEXコマンド自体は正常に終了します。)

加えて、部分文字列検索を高速にする手段として、basic_wordlist 型の substring_index 属性があります。ご参考まで。

なお、テキスト・リテラル内で ' のエスケープ方法が '' なのは、Oracle Textに限らず、SQL一般の話です。

あと、

> 一度索引を消した後で、CTXSYS.EMPTY_STOPLISTを試してみましたが、どちらの場合でも'the pen'はヒットしませんでした。

に関してですが、次のようにすれば動作を確認できます。
※ デフォルトのストップリストが上書きされていないことが前提。

/*------------------------------------------------
* CTXSYS.EMPTY_STOPLIST を指定しない場合
*----------------------------------------------*/

/* ユーザ作成 */
connect / as sysdba
create user ctxtest identified by ctxtest;
grant connect, resource, ctxapp to ctxtest;

/* テスト用テーブル作成 */
connect ctxtest/ctxtest
create table testtab (id number primary key, text varchar2(4000));
insert into testtab values (1, 'This is a pen.');
commit;

/* テキスト索引作成 */
create index testtabidx on testtab (text)
indextype is ctxsys.context
parameters ('
lexer ctxsys.basic_lexer
');

/* 検索 */
select id, text from testtab where contains (text, 'a pen') > 0;
--> ヒット
select id, text from testtab where contains (text, 'the pen') > 0;
--> ヒット

/*------------------------------------------------
* CTXSYS.EMPTY_STOPLIST を指定する場合
*----------------------------------------------*/

/* テキスト索引削除 */
drop index testtabidx;

/* テキスト索引作成 */
create index testtabidx on testtab (text)
indextype is ctxsys.context
parameters ('
lexer ctxsys.basic_lexer
stoplist ctxsys.empty_stoplist
');

/* 検索 */
select id, text from testtab where contains (text, 'a pen') > 0;
--> ヒット
select id, text from testtab where contains (text, 'the pen') > 0;
--> ヒットせず
御忙しい中長文でのご返事どうもありがとう御座います。

デフォルトのストップリストの上書きをなくす手順ですね?
大変参考になりました。

あと、仕様をまとめますと。
XMLファイルのXMLの要素、属性、値を検索対象とする。
(例)<codeId>AAAA</codeId>といったXMLファイル で、「<codeId>AAAA</codeId>」での検索が引っかかる。

OR検索はしないが、スペースではAND検索を行うようにしたい。
ワイルドカードを使用しない。

方向で進めたいと思います。

空白文字はかなりあり、全て把握できるものでしょうか?
URLサイトに載ってないのもあるような気がします。
空白文字は、上記 6: のURLにあるもので全てです。

> (例)<codeId>AAAA</codeId>といったXMLファイル で、「<codeId>AAAA</codeId>」での検索が引っかかる。

XML文書を、タグの構造に基づいて検索するのであれば、PATH_SECTION_GROUP を使った方がいいのではないでしょうか。
PATH_SECTION_GROUP を使うことで、全文検索とXPathを組み合わせることができます。

たとえば、「任意の階層にある codeId タグの中身が AAAA であるもの」という条件は、

SELECT 列名リスト FROM テーブル名 WHERE CONTAINS (列名, 'AAAA INPATH(//codeId)') > 0;

のように記述できます。

XML文書に対して「%<codeId>AAAA</codeId>%」のようなsubstring検索を実行するのは、アプリケーションの実装としてあまり好ましくないと思います。(流儀うんぬんだけではなく、索引サイズや検索速度の観点から。)

PATH_SECTION_GROUP や INPATH 演算子の詳細は、Oracle Text のマニュアルに載っています。(Oracle Database リリース 9.0.1 以降で使用可能。)
XPath の使い方に関しては、Oracle Text のマニュアルにも載っていますが、XPath のより詳細な仕様は W3C のサイトに載っています。
http://www.w3.org/TR/xpath

それと、他の人が見ているかもしれないので念のため・・・

> デフォルトのストップリストの上書きをなくす手順ですね?

上記 11: の末尾にあるサンプルは、「デフォルトのストップリストの上書きをなくす手順」ではなく、「CTXSYS.EMPTY_STOPLIST の動作確認のためのスクリプト」です。このスクリプトが期待通りに動作する前提として、「デフォルトのストップリストが上書きされていないこと」が必要です。
ご返事有難う御座います。
現在このような形で索引をつけました。

CREATE INDEX 索引名 ON テーブル名 (列名)
INDEXTYPE IS CTXSYS.CONTEXT
LEXER レクサー名
transactional
section group CTXSYS.PATH_SECTION_GROUP
sync (every "FREQ=DAILY; BYHOUR=00; BYMINUTE=0; ")
');


初歩的な質問ですが、下記の^^^の位置へsection groupやstoplistとするのはどの一覧にあるのでしょうか?
私は検索してそのまま載せましたが・・。

section group CTXSYS.PATH_SECTION_GROUP
^^^^^^^^^^^^^

しかしながら、
AAAA INPATH(//codeId)を一般ユーザに入力させるのは難しそうですね。
> 初歩的な質問ですが、下記の^^^の位置へsection groupやstoplistとするのはどの一覧にあるのでしょうか?
> 私は検索してそのまま載せましたが・・。
>
> section group CTXSYS.PATH_SECTION_GROUP
> ^^^^^^^^^^^^^

「CTXSYS.PATH_SECTION_GROUP」は、システム定義プリファレンスの1つです。
DB 10.2 のマニュアルでは、以下の箇所に載っています。
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/text.102/B19214-01/cdatadic.htm#i1009772

もともと、プリファレンスというのは、各ユーザで作成して使用できるものですので、必ずしもシステム定義プリファレンスを利用する必要はありません。

以下のようにすれば、各ユーザが PATH_SECTION_GROUP 型のセクション・グループを作成できます。

BEGIN
CTX_DDL.CREATE_SECTION_GROUP('my_section_group', 'PATH_SECTION_GROUP');
END;
/

ここ(↑)で作成した my_section_group を CREATE INDEX 文で使うには、

CREATE INDEX 索引名 ON テーブル名 (列名)
INDEXTYPE IS CTXSYS.CONTEXT
PARAMETERS ('
transactional
section group my_section_group
sync (every "FREQ=DAILY; BYHOUR=00; BYMINUTE=0; ")
');

のようにします。このCREATE INDEX文で作成されたテキスト索引の動作は、「section group CTXSYS.PATH_SECTION_GROUP」と記述した場合と全く同じです。

※ CTXSYS.PATH_SECTION_GROUP というのは、Oracle Textの管理ユーザである CTXSYS が所有する PATH_SECTION_GROUP 型のセクション・グループ (その名称も再び PATH_SECTION_GROUP) を、各ユーザが使いまわしているのです。

> しかしながら、
> AAAA INPATH(//codeId)を一般ユーザに入力させるのは難しそうですね。

そうですね。

どのようなエンドユーザが想定されているのか分かりませんが、そもそもアプリの作りとして、XML構造をエンドユーザに意識させるのはどうなのだろう、、、と思ってしまいます。(すみません。。。)

前提として考えづらいですが、もし「エンドユーザが直接XML検索をすること」に特化したアプリケーションを作成しているということであれば、入力フォームとして、キーワードとXPathをセットで受け付けるようにすればいいのではないでしょうか。XPath は、XMLを直接使う人にとっては一般的なものですので。

ログインすると、残り4件のコメントが見れるよ

mixiユーザー
ログインしてコメントしよう!

DBならOracleでしょ♪ 更新情報

DBならOracleでしょ♪のメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング