ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja de ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja en ja en ja ja ja ja ja ja ja ja en ja en ja ja ja ja ja sv en en en ja ja ja ja ja ja ja ja ja ja ja ja en ja ja en ja ja en ja ja en ja ja ja ja en ja ja en ja ja ja en ja zh ja zh ja ja ja ja ja ja ja ja en ja en ja en ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja en ja en ja ja en ja ja ja ja ja ja ja en zh ja zh ja ja zh ja ja ja ja en ja en ja ja en ja ja ja ja ja ja ja ja ja ja en ja en ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja eu ja ja ja ja ja ja ja ja ja en ja ja en ja ja vi ja sv eu ja en en hu ja ja en en pl ja ja jbo hbs_lat en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja
column_create Literature type COLUMN_SCALAR Types
# "paper"または"book" table_create Types TABLE_HASH_KEY ShortText
Literatureに「論文」と「書籍」の情報をすべて格納します。中には「論文」にしかない情報あるいは「書籍」にしかない情報も存在します。存在しない情報は該当カラムに値を設定しません。
たとえば、「子カテゴリー」情報は「書籍」にしか存在しないので「論文」用のレコードを格納するときは「子カテゴリー」情報のカラムに値を設定しません。
GroongaにはNULLはないので、値を設定しなかったカラムの値はその型の初期値になっています。たとえば、ShortTextなら空文字列ですし、Int32なら0です。
今回の設計ではLiteratureテーブルには次のカラムを用意します。
type (Types): 種類(「論文」("paper")か「書籍」("book"))
title (ShortText): タイトル
authors (Authors): 著者(複数)
volume (Volumes): 号(「論文」のみ)
book_publisher (BookPublishers): 出版社(「書籍」のみ)
child_category (ChildCategories): 子カテゴリー(「書籍」のみ)
series (Series): シリーズ(「書籍」のみ)
titleとauthorsは全文検索のためのカラムです。
検索項目を増やす場合は単にカラムを増やしてインデックスを追加するだけです。追加方法はauthorsを例にして後述します。全文検索用のスキーマ設計の方法もあわせて説明します。
以下のカラムはドリルダウンのためのカラムです。
book_publisher
child_category
これらの情報で親子関係を表現します。親の親がある場合でもGroongaでは各レコードは直接の親だけを格納していれば十分です。各レコードに親の情報だけでなく、親の親の情報も格納する必要はありません。これは正規化した状態のままでよいということです。正規化した状態のままで扱えるため情報の管理が楽です。たとえば、「雑誌」の名前を変更する時は雑誌テーブルの該当レコードを変更するだけでよく、「雑誌」情報を持っているすべてのレコードを変更する必要はないということです。
ドリルダウン用のスキーマ設計は後述します。
以上が設計の概要です。ポイントは次の通りです。
横断検索対象の情報はすべて1つのテーブルにまとめる
対象の種類を区別する必要がある場合はカラムにその情報を入れて区別する
検索条件に使いたい情報を増やす場合はテーブルにカラムを追加する
特定のレコードにしかない情報(「論文」にしかない情報や「書籍」にしかない情報)でもカラムを追加してよい
情報が存在しないレコードでは単にカラムに値を設定しない
ドリルダウン用の情報は正規化したままでよい
著者情報を例に検索項目を追加する方法を示します。
著者は複数存在するので次のようにCOLUMN_VECTORで定義します。
column_create Literature authors COLUMN_VECTOR Authors
型はAuthorsテーブルにしていますがShortTextにしてもよいです。テーブルを使っている理由はtypeカラムのときと同じで検索効率および空間効率がよいからです。著者でドリルダウンするなら(今回は説明しません)テーブルにするべきです。計算効率が全然違います。
今回の設計では著者名を主キーにします。
table_create Authors TABLE_HASH_KEY ShortText
同姓同名の著者を別人として扱いたい場合は著者IDを振ってnameカラムを追加します。今回の説明ではそこは本質ではないので単に著者名を主キーにしています。
著者名で完全一致検索する場合は次のようにすれば効率よく検索できます。
select \ --table Literature \ --query 'authors:@山田太郎'
著者名で全文検索する場合は追加のインデックスが必要です。
まず、Authors._keyで全文検索するためのインデックスが必要です。
table_create Terms TABLE_PAT_KEY ShortText \ --default_tokenizer TokenNgram \ --normalizer NormalizerNFKC100 column_create Terms authors_key \ COLUMN_INDEX|WITH_POSITION Authors _key
Termsテーブルは他の全文検索用インデックスでも共有可能です。共有するとトークン(全文検索用にテキストを分割したもの)の情報を共有でき、DB全体の空間効率がよくなります。
TermsテーブルではTokenNgramとNormalizerNFKC100を使っています。他にも指定できるものはありますが、これらがバランスがよいので、まずはこれから始めるのがよいです。必要なら後で調整するとよいです。
Terms.authors_keyは全文検索用のインデックスなのでWITH_POSITIONを指定しています。
これで、著者名で全文検索して該当著者を検索できるようになります。しかし、その著者から該当「論文」を見つけることはまだできません。追加で次のインデックスが必要です。
column_create Authors literature_authors \ COLUMN_INDEX Literature authors
このインデックスはどの著者がどの「論文」の著者かを高速に検索するためのインデックスです。このインデックスも作ることで「著者名で全文検索して著者を見つけ、さらに、その著者がどの論文の著者かを検索する」を実現できます。
検索クエリーは次のようになります。完全一致検索のときとの違いはauthors:@に._keyが加わってauthors._key:@となっているところです。
select \ --table Literature \ --query 'authors._key:@山田'
各インデックスカラムの役割を図示すると次の通りです。
authorsは複数の著者が存在するためCOLUMN_VECTORを使っています。また、重複した情報が多くなるため型にテーブルを利用しました。そのため、少し複雑になっています。
titleのように単純な情報の場合は次のようにするだけで十分です。
column_create Literature title COLUMN_SCALAR ShortText column_create Terms literature_title \ COLUMN_INDEX|WITH_POSITION Literature title
titleとauthorsを両方検索対象にするには次のようにします。
select \ --table Literature \ --match_columns 'title || authors._key' \ --query 'キーワード'
「論文」(typeが"paper")だけを検索する場合は次のように--filterで条件を追加します。selectでは--queryと--filterで条件を指定できますが、--queryはユーザーからの入力をそのまま入れる用のオプションで--filterはシステムでより詳細な条件を指定する用のオプションです。
select \ --table Literature \ --match_columns 'title || authors._key' \ --query 'キーワード' \ --filter 'type == "paper"'
1段のドリルダウンの実現
検索対象のデータには2段以上の親子関係のドリルダウンがありますが、まずは1段の親子関係のドリルダウンの実現方法について説明します。
例として次の親子関係のドリルダウンの実現方法について説明します。
効率的なドリルダウンを実現するためにテーブルを型にしたカラムを作成します。(enum型っぽい使い方とは別の型にテーブルを使う使い方。)
今回の設計では「号」用にVolumesテーブルを作成します。
table_create Volumes TABLE_HASH_KEY ShortText
「論文」はLiteratureテーブルなので、Literatureテーブルにvolumeカラムを作成します。型はVolumesテーブルです。
column_create Literature volume COLUMN_SCALAR Volumes
これでvolumeカラムで効率的にドリルダウンできます。次のようにすれば、「号」でドリルダウンし、その「号」には何件の「論文」があるかを検索できます。
select \ --table Literature \ --drilldowns[volumes].keys 'volume' \ --drilldowns[volumes].output_columns '_key,_nsubrecs'
図示すると次の通りです。
次の親子関係も同様に実現できます。
書籍(シリーズに属さない書籍もある)
2段以上のドリルダウンの実現
続いて2段以上の親子関係のドリルダウンの実現方法について説明します。
まずは、次の2段のケースについて説明します。
その後、次の3段のケースについて説明します。
2段の場合もテーブルを型にしたカラムを作成するのは同じです。
今回の設計では「雑誌」用にMagazinesテーブルを作成します。
table_create Magazines TABLE_HASH_KEY ShortText
「号」が所属する「雑誌」を格納するカラムをVolumesテーブルに追加します。
column_create Volumes magazine COLUMN_SCALAR Magazines
これで「号」から「雑誌」をたどることができます。
「号」と「雑誌」でドリルダウンするには次のようにします。ポイントは、.tableでvolumesを指定しているところと、calc_target・calc_typesです。
select \ --table Literature \ --drilldowns[volumes].keys 'volume' \ --drilldowns[volumes].output_columns '_key,_nsubrecs' \ --drilldowns[magazines].table 'volumes' \ --drilldowns[magazines].keys 'magazine' \ --drilldowns[magazines].calc_target '_nsubrecs' \ --drilldowns[magazines].calc_types 'SUM' \ --drilldowns[magazines].output_columns '_key,_sum'
--drilldowns[${LABEL}]は高度なドリルダウンのためのパラメーターです。
参考:高度なドリルダウン関連のパラメーター
このselectでは以下の2つのドリルダウンを実行します。
--drilldowns[volumes]: 「号」でドリルダウン
--drilldowns[magazines]: 「雑誌」でドリルダウン
--drilldowns[magazines].tableで他のドリルダウンの結果を指定できます。指定するとドリルダウン結果をさらにドリルダウンできます。今回のように親子関係がある場合は子のドリルダウン結果から親のドリルダウン結果を計算します。
ただ、普通にドリルダウンすると、カウントした件数は「論文」の件数ではなく、「号」の件数になります。孫(「論文」)でドリルダウンしているのではなく、子(「号」)でドリルダウンしているからです。孫(「論文」)の件数をカウントするには子(「号」)でカウントした件数をさらにカウントする。その設定が次のパラメーターです。
--drilldowns[magazines].calc_target '_nsubrecs'
--drilldowns[magazines].calc_types 'SUM'
_nsubrecsには子(「号」)でカウントした孫(「論文」)の件数が入っています。それのSUM(総計)を計算するので孫の件数になります。出力する時は_nsubrecsではなく_sumで参照します。
--drilldowns[magazines].output_columns '_key,_sum'
図示すると次の通りです。
3段になった次のケースも同様です。
まず、出版元を効率よくドリルダウンするためにPaperPublishersテーブルを作ります。
table_create PaperPublishers TABLE_HASH_KEY ShortText
Magazinesテーブル(「雑誌」)に出版元を格納するカラムを追加します。
column_create Magazines publisher COLUMN_SCALAR PaperPublishers
これで「雑誌」から「出版元」をたどることができます。
「号」と「雑誌」と「出版元」でドリルダウンするには次のようにします。ポイントは、「出版元」のドリルダウンのcalc_targetで_nsubrecsではなく_sumを使っているところです。「出版元」のドリルダウンで「論文」の件数をカウントするには「雑誌」のドリルダウンでカウント済みの「論文」の件数の総計を計算します。そのカウント済みの「論文」の件数が_nsubrecsではなく_sumにあるので_sumを使います。
select \ --table Literature \ --drilldowns[volumes].keys 'volume' \ --drilldowns[volumes].output_columns '_key,_nsubrecs' \ --drilldowns[magazines].table 'volumes' \ --drilldowns[magazines].keys 'magazine' \ --drilldowns[magazines].calc_target '_nsubrecs' \ --drilldowns[magazines].calc_types 'SUM' \ --drilldowns[magazines].output_columns '_key,_sum' \ --drilldowns[paper_publishers].table 'magazines' \ --drilldowns[paper_publishers].keys 'publisher' \ --drilldowns[paper_publishers].calc_target '_sum' \ --drilldowns[paper_publishers].calc_types 'SUM' \ --drilldowns[paper_publishers].output_columns '_key,_sum'
図示すると次の通りです。
次のケースも同様に実現できる。
親子階層の情報を使って子のレコードを検索する方法を説明します。
ここでは、対象の「出版元」内の「雑誌」の一覧を返すケースを例にして説明します。
まず、対象の「出版元」を絞り込む必要があります。ここでは「出版元」の名前(主キーに入っています)を全文検索して絞り込むとします。
全文検索用のインデックスのテーブルはAuthors._key用に作ったTermsテーブルを流用します。
column_create Terms paper_publishers_key \ COLUMN_INDEX|WITH_POSITION PaperPublishers _key
これで「出版元」の名前で全文検索できます。しかし、authorsのときと同じで、「出版元」は絞り込めますが、絞り込んだ「出版元」を元に「雑誌」を絞り込むことはできません。「雑誌」も絞り込めるようにするには追加で次のインデックスが必要です。
column_create PaperPublishers magazines_publisher \ COLUMN_INDEX Magazines publisher
このインデックスは「出版元」をキーにどの「雑誌」がその「出版元」を参照しているかを高速に検索するためのインデックスです。このインデックスがあることで、絞り込んだ「出版元」を元に「雑誌」を絞り込めます。
次のようなクエリーで「出版元」の名前で全文検索し、絞り込んだ「出版元」が発行している「雑誌」を出力できます。
select \ --table Magazines \ --match_columns 'publisher._key' \ --query 'おもしろ雑誌' \ --output_columns '_key, publisher._key'
他の親子関係のケースも同様に実現できます。
Groongaで以下の機能を効率的に実現するためのスキーマ設計方法について説明しました。
「論文」と「書籍」を横断全文検索
複数カラムで横断全文検索
たとえば「タイトル」と「著者」で横断全文検索
「論文」だけを全文検索
「書籍」だけを全文検索
検索結果を任意の親でドリルダウン
たとえば「出版元」でドリルダウン
指定した親に属する子レコードを検索
たとえば「出版元」が発行している「雑誌」を検索
今回の設計を実装したGroongaコマンドはgroonga/groonga-schema-design-exampleにあります。実際に試してみると理解が深まるはずなのでぜひ試してみてください。
クリアコードではGroongaのスキーマ設計もサポートしています。Groongaをもっと活用したい方はお問い合わせください。
現在クリアコードではソフトウェア開発者を募集しています! フリーソフトウェア開発での開発の仕方をベースに、成果や知識を公開し共有しながら業務に携われます。
Groongaについて開発者に質問できる「グルカイ!」ほぼ毎週火曜日12:15-12:45 Groongaチャンネルで開催中です。 Groongaについて知りたい人、ぜひご参加ください。
Linuxでキーボード入力の補助手段としてフットスイッチを活用するには
Gecko Embedded: 60ESR対応のフィードバック
Apache Arrow
milter manager
ククログの記事はCC BY-SA 4.0とGFDLのデュアルライセンスで自由に利用できます。
クリアコードはプログラミングが楽しいソフトウェア開発者を1名募集しています。
クリアコードは「クリアコードをいい感じにする人」を1名募集しています。
クリアコードはフリーソフトウェア開発で培った技術力を提供しています。特にMozilla製品(Mozilla FirefoxとMozilla Thunderbird)とRubyとGroonga(全文検索)に関連した開発を得意としています。