全文検索
Prisma Clientは、PostgreSQLデータベースではバージョン2.30.0以降、MySQLデータベースではバージョン3.8.0以降で全文検索をサポートしています。全文検索 (FTS) を有効にすることで、データベースのカラム内のテキストを検索してアプリケーションに検索機能を追加できます。
Prisma v6では、FTSがMySQLで一般提供に昇格しました。PostgreSQLでは引き続きプレビュー版であり、fullTextSearchPostgres
プレビュー機能フラグの使用が必要です。
PostgreSQLの全文検索を有効にする
全文検索APIは現在プレビュー機能です。この機能を有効にするには、以下の手順を実行します
-
スキーマ内の
previewFeatures
ブロックを更新し、fullTextSearchPostgres
プレビュー機能フラグを含めますschema.prismagenerator client {
provider = "prisma-client-js"
previewFeatures = ["fullTextSearchPostgres"]
} -
Prisma Clientを生成する
npx prisma generate
クライアントを再生成すると、モデルに作成されたすべてのString
フィールドで新しいsearch
フィールドが利用可能になります。たとえば、次の検索では「cat」という単語を含むすべての投稿が返されます。
// All posts that contain the word 'cat'.
const result = await prisma.posts.findMany({
where: {
body: {
search: 'cat',
},
},
})
注: 現在、PostgreSQLの全文検索機能には既知の問題があります。検索クエリが遅い場合は、生のSQLでクエリを最適化できます。
データベースをクエリする
search
フィールドは、内部でデータベースのネイティブなクエリ機能を使用しています。つまり、利用できる正確なクエリオペレーターはデータベース固有です。
PostgreSQL
以下の例は、PostgreSQLの'and' (&
) および'or' (|
) オペレーターの使用法を示しています
// All posts that contain the words 'cat' or 'dog'.
const result = await prisma.posts.findMany({
where: {
body: {
search: 'cat | dog',
},
},
})
// All drafts that contain the words 'cat' and 'dog'.
const result = await prisma.posts.findMany({
where: {
status: 'Draft',
body: {
search: 'cat & dog',
},
},
})
クエリ形式がどのように機能するかを理解するために、以下のテキストを考えてみましょう
"素早い茶色のキツネが怠惰な犬を飛び越える"
以下のクエリがそのテキストとどのように一致するかを以下に示します
クエリ | 一致? | 説明 |
---|---|---|
fox & dog | はい | テキストに「fox」と「dog」が含まれる |
dog & fox | はい | テキストに「dog」と「fox」が含まれる |
dog & cat | いいえ | テキストに「dog」は含まれるが「cat」は含まれない |
!cat | はい | テキストに「cat」が含まれない |
fox | cat | はい | テキストに「fox」または「cat」が含まれる |
cat | pig | いいえ | テキストに「cat」も「pig」も含まれない |
fox <-> dog | はい | テキストで「fox」の後に「dog」が続く |
dog <-> fox | いいえ | テキストで「dog」の後に「fox」が続かない |
サポートされている操作の全範囲については、PostgreSQL全文検索ドキュメントを参照してください。
MySQL
以下の例は、MySQLの'and' (+
) および'not' (-
) オペレーターの使用法を示しています
// All posts that contain the words 'cat' or 'dog'.
const result = await prisma.posts.findMany({
where: {
body: {
search: 'cat dog',
},
},
})
// All posts that contain the words 'cat' and not 'dog'.
const result = await prisma.posts.findMany({
where: {
body: {
search: '+cat -dog',
},
},
})
// All drafts that contain the words 'cat' and 'dog'.
const result = await prisma.posts.findMany({
where: {
status: 'Draft',
body: {
search: '+cat +dog',
},
},
})
クエリ形式がどのように機能するかを理解するために、以下のテキストを考えてみましょう
"素早い茶色のキツネが怠惰な犬を飛び越える"
以下のクエリがそのテキストとどのように一致するかを以下に示します
クエリ | 一致? | 説明 |
---|---|---|
+fox +dog | はい | テキストに「fox」と「dog」が含まれる |
+dog +fox | はい | テキストに「dog」と「fox」が含まれる |
+dog -cat | はい | テキストに「dog」は含まれるが「cat」は含まれない |
-cat | いいえ | マイナス演算子は単独では使用できません (以下の注を参照) |
fox dog | はい | テキストに「fox」または「dog」が含まれる |
quic* | はい | テキストに「quic」で始まる単語が含まれる |
quick fox @2 | はい | 「fox」が「quick」から2単語以内に始まる |
fox dog @2 | いいえ | 「dog」が「fox」から2単語以内に始まらない |
"jumps over" | はい | テキストに「jumps over」というフレーズ全体が含まれる |
注: マイナス演算子 (
-
) は、他の検索語によって一致した行を除外するためにのみ機能します。したがって、-
が付加された語のみを含むブールモード検索は空の結果を返します。「除外された語のいずれかを含む行を除くすべての行」を返すわけではありません。
MySQLには、検索結果のランキング順序を変更するための>
、<
、および~
演算子もあります。例として、次の2つのレコードを考えてみましょう
1. "素早い茶色のキツネが怠惰な犬を飛び越える"
2. "素早い茶色のキツネが怠惰な猫を飛び越える"
クエリ | 結果 | 説明 |
---|---|---|
fox ~cat | まず1を返し、次に2を返す | 「fox」を含むすべてのレコードを返す。ただし、「cat」を含むレコードはランクを低くする |
fox (<cat >dog) | まず1を返し、次に2を返す | 「fox」を含むすべてのレコードを返す。ただし、「cat」を含むレコードは「dog」を含む行よりもランクを低くする |
サポートされている操作の全範囲については、MySQL全文検索ドキュメントを参照してください。
_relevance
による結果のソート
関連度によるソートは、PostgreSQLおよびMySQLでのみ利用可能です。
Prisma ClientのデフォルトのorderBy
動作に加えて、全文検索では指定された文字列または文字列に対する関連度によるソートも追加されます。たとえば、投稿をタイトルの'database'
という用語への関連度で並べ替える場合は、次のように使用できます
const posts = await prisma.post.findMany({
orderBy: {
_relevance: {
fields: ['title'],
search: 'database',
sort: 'asc'
},
},
})
インデックスの追加
PostgreSQL
Prisma Clientは現在、全文検索を高速化するためのインデックスの使用をサポートしていません。これに関する既存のGitHub Issueがあります。
MySQL
MySQLの場合、schema.prisma
ファイルの@@fulltext
引数を使用して、検索するすべてのカラムにインデックスを追加する必要があります。
以下の例では、Blog
モデルのcontent
フィールドに1つの全文インデックスが追加され、もう1つはcontent
フィールドとtitle
フィールドの両方にまとめて追加されています
generator client {
provider = "prisma-client-js"
}
model Blog {
id Int @unique
content String
title String
@@fulltext([content])
@@fulltext([content, title])
}
最初のインデックスは、content
フィールド内で「cat」という単語の出現を検索することを可能にします
const result = await prisma.blogs.findMany({
where: {
content: {
search: 'cat',
},
},
})
2番目のインデックスは、content
フィールドで「cat」、title
フィールドで「food」という単語の出現を、content
とtitle
の両方のフィールドで検索することを可能にします
const result = await prisma.blogs.findMany({
where: {
content: {
search: 'cat',
},
title: {
search: 'food',
},
},
})
しかし、title
のみで検索しようとすると、「Cannot find a fulltext index to use for the search」というエラーで検索が失敗し、メッセージコードはP2030
となります。これは、検索に両方のフィールドに対するインデックスが必要であるためです。
生のSQLによる全文検索
全文検索は現在プレビュー版であり、既知の問題により、検索クエリが遅くなる場合があります。その場合、TypedSQLを使用してクエリを最適化できます。
PostgreSQL
TypedSQLを使用すると、PostgreSQLのto_tsvector
とto_tsquery
を使用して検索クエリを表現できます。
- fullTextSearch.sql
- index.ts
SELECT * FROM "Blog" WHERE to_tsvector('english', "Blog"."content") @@ to_tsquery('english', ${term});
import { fullTextSearch } from "@prisma/client/sql"
const term = `cat`
const result = await prisma.$queryRawTyped(fullTextSearch(term))
注: 言語設定によっては、SQLステートメント内の
english
を別の言語に置き換えることができます。
検索語にワイルドカードを含めたい場合は、次のようにします
- fullTextSearch.sql
- index.ts
SELECT * FROM "Blog" WHERE to_tsvector('english', "Blog"."content") @@ to_tsquery('english', ${term});
const term = `cat:*`
const result = await prisma.$queryRawTyped(fullTextSearch(term))
MySQL
MySQLでは、次のように検索クエリを表現できます
- fullTextSearch.sql
- index.ts
SELECT * FROM Blog WHERE MATCH(content) AGAINST(${term} IN NATURAL LANGUAGE MODE);
const term = `cat`
const result = await prisma.$queryRawTyped(fullTextSearch(term))