メインコンテンツにスキップ

フルテキスト検索

Prisma Clientは、バージョン2.30.0以降のPostgreSQLデータベースと、バージョン3.8.0以降のMySQLデータベースのフルテキスト検索をサポートしています。フルテキスト検索(FTS)を有効にすると、データベース列内のテキストを検索して、アプリケーションに検索機能を追加できます。

情報

Prisma v6では、FTSはMySQLで一般提供になりました。PostgreSQLではプレビューのままであり、fullTextSearchPostgresプレビュー機能フラグを使用する必要があります。

PostgreSQLのフルテキスト検索の有効化

フルテキスト検索APIは現在プレビュー機能です。この機能を有効にするには、次の手順を実行してください。

  1. スキーマのpreviewFeaturesブロックを更新して、fullTextSearchPostgresプレビュー機能フラグを含めます。

    schema.prisma
    generator client {
    provider = "prisma-client-js"
    previewFeatures = ["fullTextSearchPostgres"]
    }
  2. 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',
},
},
})

クエリ形式の仕組みを理解するために、次のテキストを考えてみましょう。

"The quick brown fox jumps over the lazy 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はいテキストで「dog」が「fox」の後に続きます
dog <-> foxいいえテキストで「fox」が「dog」の後に続きません

サポートされている操作の全範囲については、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',
},
},
})

クエリ形式の仕組みを理解するために、次のテキストを考えてみましょう。

"The quick brown fox jumps over the lazy 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. "The quick brown fox jumps over the lazy dog"

2. "The quick brown fox jumps over the lazy cat"

クエリ結果説明
fox ~cat1を最初に、次に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`引数を使用して検索する任意の列にインデックスを追加する必要があります。

次の例では、1つのフルテキストインデックスが`Blog`モデルの`content`フィールドに追加され、別のインデックスが`content`フィールドと`title`フィールドの両方に一緒に追加されます。

schema.prisma
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」の出現を検索できます。

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`を使用して検索クエリを表現できます。

SELECT * FROM "Blog" WHERE to_tsvector('english', "Blog"."content") @@ to_tsquery('english', ${term});

注意:言語の好みに応じて、SQLステートメントで`english`を別の言語に置き換えることができます。

検索語句にワイルドカードを含める場合は、次のようにすることができます。

SELECT * FROM "Blog" WHERE to_tsvector('english', "Blog"."content") @@ to_tsquery('english', ${term});

MySQL

MySQLでは、次のように検索クエリを表現できます。

SELECT * FROM Blog WHERE MATCH(content) AGAINST(${term} IN NATURAL LANGUAGE MODE);