PGroonga で検索の幅を広げる！複数のカラムを合わせた全文検索のテクニック

## 状況
例えば articles という記事を表すテーブルがあるとします。
そこには記事名を表す `title` と、記事本文である `content` があります。

![articles テーブル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2038389/8071d9c7-0761-7648-8207-1c8a25bb078a.png)


「やりたいこと」は以下の通りです。
* 全文検索を行うとき `title` と `content` の両方を合わせて検索したい
* また `title` と `content` の重み付けをした検索を行いたい

なおこの記事では PGroonga の導入自体は済んでいるものとします。

## 結論
### テーブル作成
ここは特に変わりありません。
サンプルとしては以下のようなテーブルを作成して進めます。

```sql
CREATE TABLE articles(
  id integer,
  title text,
  content text
);
```

### INDEX 作成
<font color="Red"><b>ここがポイントです！</b></font>
`(ARRAY[title, content])` のように合わせて検索を行いたいものをセットで INDEX 作成をします。
```sql
-- 拡張機能の有効化
CREATE EXTENSION IF NOT EXISTS pgroonga;
-- pgroonga インデックスの作成 (ここがポイント！！)
CREATE INDEX pgroonga_articles_index ON articles USING pgroonga ((ARRAY[title, content]));
```

### テストデータの挿入
今回のテストデータは私の過去の Qiita 記事を雑にコピペして作成しました。
```sql
INSERT INTO articles VALUES (1, 'PGroonga で 1 つのテーブルの 2 つのカラムを合わせて全文検索を行う', '例えば articles という記事を表すテーブルがあるとします。そこには記事名を表す `title` と、記事本文である `content` があります。');
INSERT INTO articles VALUES (2, 'Vite + React で CSS Modules を使ってみる', '現在の問題点 Vite + React という構成で開発を行っており、以下のような問題に直面しました。');
INSERT INTO articles VALUES (3, 'dialog の背景色が変わらないときに読んで下さい (React サンプルコード付き)', 'CSS 書いても背景色が変わらない！モダールを作成するのに超便利な dialog 要素ですが、ハマりがちな罠があります。');
INSERT INTO articles VALUES (4, 'Docker コンテナ内でアプリケーションを終了させたい！！！', 'この記事が役立つかもしれない人 * npm run dev などでコンテナ起動時に実行するよう設定しているアプリケーションを停止させたい人 * 特定のポートで動作しているプロセスを停止したい人 * アプリケーションをビルドしてみて手元で正常に実行できるか試したい人');
INSERT INTO articles VALUES (5, '[GitHub Actions] 特定の文字列を含むブランチ名にエラーを突きつける', 'この記事が役立つかもしれない人 * 特定の文字列を含むブランチ名を使って欲しくない場合 * ビルド時の都合でブランチ名に特定の文字列を含めず作成して欲しい！ 等 * Rulesets を使いたかったけど何らかの理由で使えないので代替手段が知りたい人');
```

### 検索 & 結果
#### シンプルな全文検索
**例 1**
```sql: 'PGroonga' を検索
SELECT *
FROM articles
WHERE ARRAY[title, content] &@~ 'PGroonga';
```
```bash: 結果
| id | title                                    | content                                                                        |
| -- | ---------------------------------------- | ------------------------------------------------------------------------------ |
| 1  | PGroonga で 1 つのテーブルの 2 つのカラムを合わせて全文検索を行う | 例えば articles という記事を表すテーブルがあるとします。そこには記事名を表す `title` と、記事本文である `content` があります。 |
```

**例 2**
```sql: 'React' を検索
SELECT *
FROM articles
WHERE ARRAY[title, content] &@~ 'React';
```
```bash: 結果
| id | title                                        | content                                                      |
| -- | -------------------------------------------- | ------------------------------------------------------------ |
| 2  | Vite + React で CSS Modules を使ってみる            | 現在の問題点 Vite + React という構成で開発を行っており、以下のような問題に直面しました。          |
| 3  | dialog の背景色が変わらないときに読んで下さい (React サンプルコード付き) | CSS 書いても背景色が変わらない！モダールを作成するのに超便利な dialog 要素ですが、ハマりがちな罠があります。 |
```
本文に `'React'` が入っていない記事もヒットしました！


**例 3**
```sql: 'npm' を検索
SELECT *
FROM articles
WHERE ARRAY[title, content] &@~ 'npm';
```
```bash: 結果
| id | title                           | content                                                                                                                              |
| -- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| 4  | Docker コンテナ内でアプリケーションを終了させたい！！！ | この記事が役立つかもしれない人 * npm run dev などでコンテナ起動時に実行するよう設定しているアプリケーションを停止させたい人 * 特定のポートで動作しているプロセスを停止したい人 * アプリケーションをビルドしてみて手元で正常に実行できるか試したい人 |
```
タイトルに `'npm'` が入っていない記事もヒットしました！


#### 重み付けを行って全文検索
[pgroonga_score関数](https://pgroonga.github.io/ja/reference/functions/pgroonga-score.html) を用いて行います。
例では `ARRAY[title, content]` に対して `ARRAY[0, 1]` なので、検索キーワードが `title` に含まれていても無視し、`content` に含まれていたら 1 * 個数をスコアとして算出します。

**例 1**
```sql: 'PGroonga' を検索
SELECT *, pgroonga_score(tableoid, ctid) AS score
FROM articles
WHERE ARRAY[title, content] &@~ ('PGroonga', ARRAY[0, 1], 'pgroonga_articles_index')::pgroonga_full_text_search_condition;
```
```bash: 結果
Success. No rows returned
```
記事本文に `'PGroonga'` を含むデータがないので結果なし。

**例 2**
```sql: 'React' を検索
SELECT *, pgroonga_score(tableoid, ctid) AS score
FROM articles
WHERE ARRAY[title, content] &@~ ('React', ARRAY[0, 1], 'pgroonga_articles_index')::pgroonga_full_text_search_condition;
```
```bash: 結果
| id | title                             | content                                             | score |
| -- | --------------------------------- | --------------------------------------------------- | ----- |
| 2  | Vite + React で CSS Modules を使ってみる | 現在の問題点 Vite + React という構成で開発を行っており、以下のような問題に直面しました。 | 1     |
```
記事タイトルと本文に `'React'` を含むが、カウントは本文のみなので score は 1 になる。

---

例のような `ARRAY[0, 1]` 以外にも `ARRAY[5, 1]` のように設定することで、検索キーワードがタイトルに含まれる場合は本文よりも 5 倍の重みを付けてスコアリングすることも可能です。

## まとめ
PGroonga の全文検索は非常に便利です！
ただネット上での情報不足を少し感じたため自分がハマったポイントについて書きました！

この記事が役に立ったら 🩷 を頂けると嬉しいです！


## 参考資料
https://pgroonga.github.io/ja/reference/operators/query-v2.html