PostgreSQL tsearch2 FTSエンジンを使用したオートコンプリートの実装例

はじめに



むかしむかし、私はビジネスのために日記を書き、メモを取り、さまざまなタスクの動きを修正しました。 もともとは、PHP + Kohana 2 + PostgreSQLを使用して作成されました。 時間が経つにつれて、私はYiiですべてを書き直しました(最初のバージョン、そして唯一のバージョン)。 全文検索には、PostgreSQLに組み込まれたtsearch2エンジンが使用されました。 何年もの間、私はシステムを使用し、徐々に開発し、その中のテキストの量はまともであるという結論に達しました。 検索は非常に頻繁に使用する必要があり、その利便性を高めるために、JQuery UIパッケージからオートコンプリートをそれにねじ込むことにしました。



実装



すべてが正しいためには、ヒントの選択は検索自体と同じインデックスに基づいている必要があります。 私が持っているすべてのテキストは、「テキスト」と呼ばれる別のテーブルに保存されます。 その構造は次のとおりです。



Table "public.texts" Column | Type | Modifiers -------------+-----------------------------+---------------------------------------------------------- txt_id | bigint | not null default nextval(('gen_txt_id'::text)::regclass) user_id | integer | not null txt | text | not null fti_txt | tsvector | last_update | timestamp without time zone | default now() format | textformat | default 'wiki'::textformat Indexes: "texts_pkey" PRIMARY KEY, btree (txt_id) "texts_txt_id_key" UNIQUE CONSTRAINT, btree (txt_id) "fti_texts_idx" gist (fti_txt) "last_update_idx" btree (last_update) "texts_uid_idx" btree (user_id)
      
      





現在の検索行のプロンプトのリストを作成するタスクを実装するために、Actionは別個の接続されたアクションとして記述されました。 ソース保護/拡張機能/アクション/SearchAutocompleteAction.php



 <?php class SearchAutocompleteAction extends CAction { public $model; public $attribute; public $fts_field; public function run() { //   $_uid = Yii::app()->user->id; $_model = new $this->model; $_tableName = $_model->tableName(); //     ,      //         $_query_array = explode(' ', trim(Yii::app()->db->quoteValue($_GET['term']), " '\t\n\r\0\x0B")); $_word = array_pop($_query_array); $_preQuery = implode(' ', $_query_array); $_suggestions = array(); /* *   tsvector    .       *      ,     (  ). */ $_sub_sql = "SELECT $this->fts_field FROM $_tableName WHERE user_id=''$_uid''"; if (count($_query_array) > 0) $_sub_sql .= " AND $this->fts_field @@ to_tsquery(''russian'', ''$_preQuery'')"; /* *  ,   ,     . *   ts_stat  tsearch2.      ,   , *        .      ndoc,  *  ,   . */ $_sql = "SELECT word AS $this->attribute FROM ts_stat('$_sub_sql') WHERE word LIKE '$_word%' ORDER BY nentry DESC LIMIT 15;"; foreach(Yii::app()->db->createCommand($_sql)->query() as $_m) $_suggestions[] = count($_query_array) > 0 ? $_preQuery.' '.$_m[$this->attribute] : $_m[$this->attribute]; echo CJSON::encode($_suggestions); } }
      
      





アクションのアルゴリズムを解析するために、アクションによって形成された検索文字列「hi hub」に対するSQLクエリの例を示します。



 SELECT word AS txt FROM ts_stat('SELECT fti_txt FROM texts WHERE user_id=''1'' AND fti_txt @@ to_tsquery(''russian'', '''')') WHERE word LIKE '%' ORDER BY nentry DESC LIMIT 15;
      
      





一般にtsearch2の本質は、テキストに加えてtsvectorタイプのレコードを作成することです。この例では、これはフィールドfti_txtです。 テキストの単語がテキストに書き込まれ、テキスト内での位置と出現回数を示します。 このインデックスを使用してインデックス(ジンまたは要旨)を作成し、検索を実行します。 tsearch2のインデックスのステータスをデバッグおよび監視するために、関数ts_statがあります。 パラメーターとして、タイプtsvectorの一連のフィールドを返すSQLクエリのテキストを受け取ります。 このセットによれば、統計は、単語のリスト(フォームの出現数(nentry)および単語が出現するドキュメント(レコード)の数(ndoc))の形式で構築されます。



私の例では、検索クエリ内の単語が同じ場合、すべてのユーザーエントリでそれに類似した検索が実行されます。 クエリに複数の単語がある場合-最後の単語がクエリから削除されると、レコードのセットはクエリの最初の部分(最後の単語なし)の全文検索に制限されます。



プロジェクトへの接続



この部分はYii 1固有のもので、ここでは魔法ではありません。 メモの整合性のために用意されています。 合計で2つのステップがあります。 ステップ1は、アクションをコントローラー(私の場合はDiaryController)に接続することです。 これを行うには、彼のactions()メソッドに行を追加します:



  public function actions() { return array( ... 'acsearch' => array( 'class' => 'application.extensions.actions.SearchAutocompleteAction', 'model' => 'Texts', 'attribute' => 'txt', 'fts_field' => 'fti_txt', ), ... ); }
      
      





次に、対応するビューで、古い検索テキストボックスを置き換えます。



 <?php echo CHtml::textField('sh', $search->sh, array('size' => 60,'maxlength' => 255)); ?>
      
      





jQuery UIウィジェット:



  <?php $this->widget('zii.widgets.jui.CJuiAutoComplete', array( 'attribute'=> 'sh', 'sourceUrl' => array('acsearch'), 'name' => 'sh', 'value' => $search->sh, 'options' => array( 'minLength' => '2', ), 'htmlOptions' => array( 'size' => 60, 'maxlength' => 255, ), )); ?>
      
      





その結果、図のようなものが得られます。



画像



短所



システム全体に1つの大きな欠点があります-タイプtsvectorのフィールドの単語はステミング後に書き込まれます。 簡単に言えば、ほとんどの単語は、さまざまな形式の検索において、会計の終わりを「切り捨てる」。 上の写真を見て、「形成された」という言葉に注意を払ってください。 したがって、このソリューションは、個人用または社内用のプロジェクトに適用できます。 この問題を解決せずに、これを人々に示すことは不可能です。 おそらく誰かが価値のある解決策、または少なくとも考えを見つけるでしょう。 コメントへようこそ。



All Articles