Google Firestore:プロパティ値の部分文字列に対するクエリ(テキスト検索)


103

単純な検索フィールドを追加したいのですが、次のようなものを使用したいと思います

collectionRef.where('name', 'contains', 'searchTerm')

を使ってみましたwhere('name', '==', '%searchTerm%')が何も返りませんでした。


2
悲しみは、任意の解決策を見つけました。私は何日も
欠かさ

1
Firebaseはこれをサポートするようになりました。回答を更新してください:stackoverflow.com/a/52715590/2057171
Albert Renshaw

1
最善の方法は、各ドキュメントに手動でインデックスを付けるスクリプトを作成することだと思います。次に、それらのインデックスをクエリします。このアウトをチェック:angularfirebase.com/lessons/...
Kiblawi_Rabee

回答:


34

そのような作業はありません、許可されたものです==<<=>>=

あなたは間を開始し、すべてのために、たとえば、プレフィクスだけでフィルタリングすることができますbarし、fooあなたが使用することができます

collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')

そのために、AlgoliaやElasticSearch などの外部サービスを使用できます。


5
それは私が探しているものではありません。タイトルの長い製品のリストがたくさんあります。「リボックメンズテニスラケット」。ユーザーはを検索する可能性がありますがtennis、使用可能なクエリ演算子に基づいて、これらの結果を取得する方法はありません。組み合わせる>=<=動作しません。もちろん、Algoliaを使用することもできますが、ほとんどのクエリを実行するためにFirebaseで使用することもでき、Firestoreに切り替える必要はありません...
tehfailsafe

4
@tehfailsafeさて、あなたの質問は「フィールドに文字列が含まれているかどうかを照会する方法」であり、応答は「それを行うことはできません」です。
2017年

20
@ A.Chakroun私の答えで失礼なことは何ですか?
クバ

17
これは本当に必要なことです。Firebaseのチームは、これには思わなかった理由を私は理解できない
ダニ

2
Firebaseのクエリが非常に弱いことに本当に驚きます。このような単純なクエリをサポートできない場合、多くの人がそれを使用しているとは信じられません。
Bagusflyer

43

Kubaの答えは制限に関する限り当てはまりますが、これをセットのような構造で部分的にエミュレートできます。

{
  'terms': {
    'reebok': true,
    'mens': true,
    'tennis': true,
    'racket': true
  }
}

これで、クエリを実行できます

collectionRef.where('terms.tennis', '==', true)

これは、Firestoreがすべてのフィールドのインデックスを自動的に作成するため機能します。残念ながら、Firestoreは複合インデックスを自動的に作成しないため、これは複合クエリでは直接機能しません。

単語の組み合わせを保存することでこれを回避できますが、これは見苦しく速くなります。

おそらく、外部の全文検索を使用するほうがよいでしょう。


cloud.google.com/appengine/docs/standard/java/searchでクラウド機能を使用することは可能ですか?
Henry

1
この回答のフォローアップとしてこれを求めている場合:AppEngineの全文検索はFirestoreから完全に分離されているため、これは直接役立ちません。クラウド機能を使用してデータを複製することもできますが、それは本質的に、外部全文検索を使用するための提案です。他に質問がある場合は、新しい質問を開始してください。
ギルギルバート

1
Firestoreでは、使用する前にすべての用語にインデックスをwhere
付ける

4
Husamが述べたように、これらのフィールドすべてにインデックスを付ける必要があります。私の製品名に含まれる用語を検索できるようにしたいと思いました。したがって、ドキュメントに「オブジェクト」タイプのプロパティを作成し、キーに製品名の一部を割り当て、それぞれに「true」値を割り当て、where( 'nameSegments.tennis'、 '=='、true)を検索して機能しますが、Firestoreは、nameSegments.tennisのインデックスを作成することをお勧めします。無限の数の用語が存在する可能性があるため、すべての検索用語が前もって定義されている場合、この回答は非常に限られたscennarioに対してのみ使用できます。
Slawoj

2
@epelegクエリは、インデックスを作成した後は機能しますが、製品名に含まれる可能性のある用語ごとにインデックスを作成することはできないため、製品名の用語のテキスト検索では、この方法は私のケースでは機能しませんでした。
Slawoj

43

@Kubaの答えに同意しますが、それでも、接頭辞による検索で完全に機能するように小さな変更を加える必要があります。ここで私のために働いたもの

名前で始まるレコードを検索するには queryText

collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')

\uf8ffクエリで使用される文字は、Unicode範囲の非常に高いコードポイントです(これはPrivate Usage Area [PUA]コードです)。Unicodeのほとんどの通常の文字の後にあるため、クエリはで始まるすべての値に一致しますqueryText


1
正解です。これは、接頭辞テキストの検索に最適です。テキスト内の単語を検索するには、この投稿で説明されている「array-contains」実装を試してみてください。medium.com
@ ken11zer01 /

考えただけですが、理論的には、別のフィールドを作成してデータを反転させることで、queryTestで終わるすべての値を一致させることができます...
Jonathan

はい、@ジョナサン、それも可能です。
Ankit Prajapati

30

Firebaseは文字列内の用語の検索を明示的にサポートしていませんが、

Firebaseは、(現在)以下をサポートします。これにより、ケースや他の多くの問題が解決されます。

2018年8月現在、array-containsクエリをサポートしています。参照:https : //firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html

すべての主要な用語をフィールドとして配列に設定し、 'X'を含む配列を持つすべてのドキュメントを照会できます。論理ANDを使用して、追加のクエリをさらに比較できます。(これは、Firebase が複数の配列を含むクエリの複合クエリをネイティブでサポートしていないため、クライアント側で「AND」ソートクエリを実行する必要があるためです)

このスタイルで配列を使用すると、配列を同時書き込み用に最適化できるので便利です。バッチリクエストをサポートするかどうかはテストしていません(ドキュメントには記載されていません)が、公式の解決策なのでサポートしたいと思っています。


使用法:

collection("collectionPath").
    where("searchTermsArray", "array-contains", "term").get()

12
これは素晴らしいソリューションです。ただし、私が間違っている場合は修正してください。ただし、@ tehfailsafeが要求したことを実行することはできないと思います。たとえば、「abc」という文字列を含むすべての名前を取得したい場合、「abc」という正確な名前を持つドキュメントだけが返され、「abcD」または「0abc」は出ます。
ユリアン

1
@Yulianプログラミングの世界でSearch termは、通常、両端がスペース、句読点などで区切られた用語全体を意味すると理解されています。あなたはグーグル場合はabcde、今あなただけのようなものの結果でしょう%20abcde.,abcde!ではなく、をabcdefghijk..。入力されたアルファベット全体がインターネット上で見つかる方がはるかに一般的ですが、検索はabcde *ではなく、孤立したabcdeの検索です
Albert Renshaw

1
私はあなたの意見を'contains'理解し、それに同意しますが、おそらく私は多くのプログラミング言語で私が言及していることを意味するという言葉によって誤解を招いたのかもしれません。'%searchTerm%'SQLの観点からも同じことが言えます。
Yulian

2
@ユリアンええ私はそれを得ます。ただし、FirebaseはNoSQLであるため、ワイルドカード文字列検索などの範囲外の問題で制限されている場合でも、これらのタイプの操作を高速かつ効率的に行うのに非常に適しています。
アルバートレンショー2018年

2
ドキュメントを更新するたびに、titleArray:['this'、 'is'、 'a'、 'title']のように分割された単語の表現を使用して、フィールドごとに個別のフィールドを作成できます。そして、検索はタイトルではなくそのフィールドに基づいて行われます。このフィールドを作成するには、トリガーのonUpdateをコールド作成します。検索ベースのテキストには多くの作業が必要ですが、NoSQLのパフォーマンスが向上しています。
sfratini

14

パーFirestoreドキュメント、クラウドFirestoreは、ネイティブのインデックス作成をサポートしていないか、または文書内のテキストフィールドを検索します。さらに、クライアント側でフィールドを検索するためにコレクション全体をダウンロードすることは現実的ではありません。

AlgoliaElastic Searchなどのサードパーティの検索ソリューションをお勧めします。


46
私はドキュメントを読みましたが、それは理想的ではありません。欠点は、AlgoliaとFirestoreの価格モデルが異なることです... Firestoreには600,000のドキュメントを含めることができます(1日あたりのクエリ数が多すぎない限り)。検索するためにそれらをAlgoliaにプッシュすると、Firestoreドキュメントでタイトル検索を実行できるようにするためだけに、毎月$ 310をAlgoliaに支払う必要があります。
tehfailsafe

2
問題は、これが無料ではないということです
Dani

これは、提起された質問に対する正解であり、最良のものとして受け入れられるべきです。
briznad

11

ここにいくつかのメモ:

1.) \uf8ffと同じように機能します~

2.) where句またはstart end句を使用できます。

ref.orderBy('title').startAt(term).endAt(term + '~');

とまったく同じです

ref.where('title', '>=', term).where('title', '<=', term + '~');

3.)いいえ、反転startAt()endAt()てすべての組み合わせで使用しても機能しませんが、反転した2番目の検索フィールドを作成して結果を組み合わせると、同じ結果を得ることができます。

例:まず、フィールドを作成するときに、フィールドの反転バージョンを保存する必要があります。このようなもの:

// collection
const postRef = db.collection('posts')

async function searchTitle(term) {

  // reverse term
  const termR = term.split("").reverse().join("");

  // define queries
  const titles = postRef.orderBy('title').startAt(term).endAt(term + '~').get();
  const titlesR = postRef.orderBy('titleRev').startAt(termR).endAt(termR + '~').get();

  // get queries
  const [titleSnap, titlesRSnap] = await Promise.all([
    titles,
    titlesR
  ]);
  return (titleSnap.docs).concat(titlesRSnap.docs);
}

これにより、文字列フィールドの最後の文字と最初のを検索できます。ランダムな中間文字や文字のグループではありません。これは、望ましい結果により近いものです。ただし、ランダムな中間文字または単語が必要な場合、これは実際には役立ちません。また、すべて小文字または検索用に小文字のコピーを保存することを忘れないでください。そうすれば、大文字小文字は問題になりません。

4.)単語が少ししかない場合でも、Ken Tanのメソッドは、必要に応じて、または少なくとも少し変更した後で、すべて実行します。ただし、テキストの段落のみでは、指数的に1 MBを超えるデータが作成されます。これは、Firestoreのドキュメントサイズの制限よりも大きくなります(わかっています。テストしました)。

5.)配列を含む(または何らかの形式の配列)を\uf8ffトリックと組み合わせることができれば、制限に達しない実行可能な検索になる可能性があります。私はすべての組み合わせを試してみました。だれでもこれを見つけたら、ここに投稿してください。

6.) ALGOLIAおよびELASTIC SEARCHを回避する必要があり、私が何も責めない場合は、Google Cloudで常にmySQL、postSQL、またはneo4Jを使用できます。いずれも簡単にセットアップでき、無料枠があります。データを保存する1つのクラウド関数onCreate()と、データを検索する別のonCall()関数があります。シンプル... では、なぜmySQLに切り替えないのですか?もちろんリアルタイムデータ!誰かがリアルタイムデータ用のwebsocksでDGraphを書くとき、私を数えてください!

AlgoliaとElasticSearchは検索専用のデータベースとして構築されているので、それほど速いことはありません... グーグル、なぜグーグルから私たちを引き離し、そしてMongoDB noSQLをフォローして検索を許可しないのですか?

更新-ソリューションを作成しました:

https://fireblog.io/blog/post/firestore-full-text-search


素晴らしい概要と非常に役立ちます。
RedFilter

驚くばかり!よく構造化された有益な応答に賛成票を投じます。
ジャングルの王

すばらしいです。ありがとう
Giang

10

遅い答えですが、まだ答えを探している人のために、ユーザーのコレクションがあり、コレクションの各ドキュメントに「username」フィールドがあるとしましょう。したがって、ユーザー名が「al」で始まるドキュメントを検索したい場合私たちは次のようなことができます

 FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")

これはすばらしい簡単な解決策です。ありがとうございます。しかし、複数のフィールドをチェックしたい場合はどうでしょう。ORで接続された「名前」と「説明」のように?
それを試して

私はそれが助け、それはクエリに来るとき、あなたはこの希望を確認することができます悪い悲しげfirebase、あなたは二つのフィールドに基づいて照会することができないと思いますstackoverflow.com/questions/26700924/...
MoTahir

1
確認しました、@ MoTahir。Firestoreには「OR」はありません。
ラップ

このソリューションは、「al」で始まるユーザー名と一致しません...たとえば、「hello」は一致します(「hello」>「al」)
antoine129

ORによるクエリは、単に2つの検索結果を結合することです。これらの結果の並べ替えは別の問題です...
ジョナサン

7

Firebaseはすぐに "string-contains"を使用して文字列内のインデックス[i] startAtをキャプチャすると確信しています...しかし、私はWebを調査し、このソリューションが他の誰かがあなたのようなデータを設定したと考えていることがわかりましたこの

state = {title:"Knitting"}
...
const c = this.state.title.toLowerCase()

var array = [];
for (let i = 1; i < c.length + 1; i++) {
 array.push(c.substring(0, i));
}

firebase
.firestore()
.collection("clubs")
.doc(documentId)
.update({
 title: this.state.title,
 titleAsArray: array
})

ここに画像の説明を入力してください

このようなクエリ

firebase
.firestore()
.collection("clubs")
.where(
 "titleAsArray",
 "array-contains",
 this.state.userQuery.toLowerCase()
)

まったくお勧めしません。文書は20K行の制限を持っているとして、あなただけのウル文書は、このような限界に到達することはありませんことをウル確認されるまで、それをこのように利用することはできませんので
サンディープ

それは現時点で最良のオプションですが、他に何が推奨されますか?
Nick Carducci

1
@Sandeep私は、サイズがドキュメントあたり1MBのサイズと20レベルの深度に制限されていることを確信しています。2万行とはどういう意味ですか?これは、現在、AlgoliaまたはElasticSearchを使用できない場合の最良の回避策です
ppicom

5

Algoliaのようなサードパーティのサービスを使用したくない場合は、Firebase Cloud Functionsが優れた代替手段となります。入力パラメータを受け取り、レコードをサーバー側で処理して、条件に一致するものを返すことができる関数を作成できます。


1
アンドロイドはどうですか?
Pratik Butani

コレクションのすべてのレコードを繰り返し処理することを提案していますか?
DarkNeuron

あんまり。私はArray.prototype。*を使用します-.every()、. some()、. map()、. filter()などのようにします。これは、値を返す前に、Firebase関数のサーバーのノードで行われます。クライアント。
ラップ

3
それでも、すべてのドキュメントを読んで検索する必要がありますが、これには費用がかかり、時間もかかります。
ジョナサン

3

Firestore内でこれを行う最善の解決策は、実際にはすべての部分文字列を配列に入れて、array_containsクエリを実行することだと思います。これにより、部分文字列マッチングを行うことができます。すべての部分文字列を格納するには少しやりすぎですが、検索語句が短い場合は非常に合理的です。


2

私はこの問題を抱えていて、かなり簡単な解決策を思いつきました。

String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")

isGreaterThanOrEqualToを使用すると、検索の先頭を除外できます。isLessThanOrEqualToの末尾に "z"を追加することで、次のドキュメントにロールオーバーしないように検索をキャップします。


3
私はこの解決策を試しましたが、私にとっては完全な文字列が入力されたときにのみ機能します。たとえば、「無料」という用語を取得したい場合、「fr」と入力し始めると何も返されません。「無料」と入力すると、その用語でスナップショットが得られます。
クリス

同じコード形式を使用していますか?そして、用語はfirestoreの文字列ですか?documentIdでフィルタリングできないことを知っています。
Jacob Bonk

2

選択した回答は正確な検索でのみ機能し、ユーザーの自然な検索動作ではありません(「Joe ate a apple today」で「apple」を検索しても機能しません)。

上記のDan Feinの回答は上位にランク付けされるべきだと思います。検索している文字列データが短い場合は、文字列のすべての部分文字列をドキュメントの配列に保存してから、Firebaseのarray_containsクエリで配列を検索できます。Firebaseドキュメントは1 MiB(1,048,576バイト)(Firebaseの割り当てと制限)に制限されています。これは、ドキュメントに保存される約100万文字です(1文字〜= 1バイトだと思います)。ドキュメントが100万マークに近くない限り、部分文字列の保存は問題ありません。

ユーザー名を検索する例:

ステップ1:プロジェクトに次の文字列拡張を追加します。これにより、文字列を部分文字列に簡単に分割できます。(私はここでこれを見つけました)。

extension String {

var length: Int {
    return count
}

subscript (i: Int) -> String {
    return self[i ..< i + 1]
}

func substring(fromIndex: Int) -> String {
    return self[min(fromIndex, length) ..< length]
}

func substring(toIndex: Int) -> String {
    return self[0 ..< max(0, toIndex)]
}

subscript (r: Range<Int>) -> String {
    let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
                                        upper: min(length, max(0, r.upperBound))))
    let start = index(startIndex, offsetBy: range.lowerBound)
    let end = index(start, offsetBy: range.upperBound - range.lowerBound)
    return String(self[start ..< end])
}

ステップ2:ユーザーの名前を保存するときは、この関数の結果も同じドキュメントの配列として保存します。これにより、元のテキストのすべてのバリエーションが作成され、配列に格納されます。たとえば、「Apple」というテキスト入力では、次の配列が作成されます。["a"、 "p"、 "p"、 "l"、 "e"、 "ap"、 "pp"、 "pl"、 "le 「、「app」、「ppl」、「ple」、「appl」、「pple」、「apple」]。これは、ユーザーが入力する可能性のあるすべての検索条件を包含する必要があります。すべての結果が必要な場合は、maximumStringSizeをnilのままにしておくことができますが、長いテキストがある場合は、ドキュメントのサイズが大きくなりすぎる前に上限を設定することをお勧めします。 )。

func createSubstringArray(forText text: String, maximumStringSize: Int?) -> [String] {

    var substringArray = [String]()
    var characterCounter = 1
    let textLowercased = text.lowercased()

    let characterCount = text.count
    for _ in 0...characterCount {
        for x in 0...characterCount {
            let lastCharacter = x + characterCounter
            if lastCharacter <= characterCount {
                let substring = textLowercased[x..<lastCharacter]
                substringArray.append(substring)
            }
        }
        characterCounter += 1

        if let max = maximumStringSize, characterCounter > max {
            break
        }
    }

    print(substringArray)
    return substringArray
}

ステップ3:Firebaseのarray_contains関数を使用できます。

[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....

0

Firestoreを使用すると、全文検索を実装できますが、それ以外の場合よりも読み取りにコストがかかります。また、特定の方法でデータを入力してインデックスを付ける必要があるため、このアプローチでは、firebaseクラウド関数を使用してh(x)次の条件を満たす線形ハッシュ関数を選択しながら、入力テキストをトークン化してハッシュしますx < y < z then h(x) < h (y) < h(z)。トークン化のために、軽量のNLPライブラリを選択して、文から不要な単語を取り除くことができる関数のコールドスタート時間を低く保つことができます。次に、Firestoreで「より小さい」および「より大きい」演算子を使用してクエリを実行できます。データも保存する際は、保存する前にテキストをハッシュし、プレーンテキストも保存する必要があります。プレーンテキストを変更すると、ハッシュされた値も変更されます。


0

これは完璧に機能しましたが、パフォーマンスの問題を引き起こす可能性があります。

firestoreをクエリするときにこれを行います。

   Future<QuerySnapshot> searchResults = collectionRef
        .where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
        .getDocuments();

これをFutureBuilderで行います。

    return FutureBuilder(
          future: searchResults,
          builder: (context, snapshot) {           
            List<Model> searchResults = [];
            snapshot.data.documents.forEach((doc) {
              Model model = Model.fromDocumet(doc);
              if (searchQuery.isNotEmpty &&
                  !model.property.toLowerCase().contains(searchQuery.toLowerCase())) {
                return;
              }

              searchResults.add(model);
            })
   };

0

今日のところ、質問に対する回答として、専門家によって提案された基本的に3つの異なる回避策があります。

私はそれらすべてを試しました。それぞれについての経験を文書化しておくと便利だと思いました。

メソッドA:使用:(dbField "> =" searchString)&(dbField "<=" searchString + "\ uf8ff")

@Kubaおよび@Ankit Prajapatiが提案

.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");

A.1 Firestoreクエリは、単一のフィールドに対して範囲フィルター(>、<、> =、<=)のみを実行できます。複数のフィールドで範囲フィルターを使用するクエリはサポートされていません。この方法を使用すると、日付フィールドなど、dbの他のフィールドに範囲演算子を含めることはできません。

A.2。この方法は、同時に複数のフィールドを検索する場合には機能しません。たとえば、検索文字列がフィールド(名前、メモ、住所)のいずれかにあるかどうかを確認することはできません。

方法B:マップ内の各エントリに「true」を指定した検索文字列のMAPを使用し、クエリで「==」演算子を使用する

@ギルギルバートが提案

document1 = {
  'searchKeywordsMap': {
    'Jam': true,
    'Butter': true,
    'Muhamed': true,
    'Green District': true,
    'Muhamed, Green District': true,
  }
}

.where(`searchKeywordsMap.${searchString}`, "==", true);

B.1明らかに、この方法では、データがデータベースに保存されるたびに追加の処理が必要であり、さらに重要なことに、検索文字列のマップを格納するために追加のスペースが必要です。

B.2 Firestoreクエリに上記のような単一の条件がある場合、事前にインデックスを作成する必要はありません。この場合、このソリューションは問題なく機能します。

B.3ただし、クエリに別の条件がある場合(たとえば、(status === "active"))、ユーザーが入力する「検索文字列」ごとにインデックスが必要なようです。つまり、あるユーザーが「Jam」を検索し、別のユーザーが「Butter」を検索する場合、「Jam」という文字列と「Butter」などに別のインデックスを事前に作成する必要があります。ユーザーの検索文字列、これは機能しません-クエリに他の条件がある場合!

.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");

** メソッド-C:検索文字列のARRAYと「array-contains」演算子を使用する

@Albert Renshawによって提案され、@ Nick Carducciによって実証されました

document1 = {
  'searchKeywordsArray': [
    'Jam',
    'Butter',
    'Muhamed',
    'Green District',
    'Muhamed, Green District',
  ]
}

.where("searchKeywordsArray", "array-contains", searchString); 

C.1メソッドBと同様に、このメソッドはデータがデータベースに保存されるたびに追加の処理を必要とし、さらに重要なことに、検索文字列の配列を格納するために追加のスペースを必要とします。

C.2 Firestoreクエリでは、複合クエリに最大1つの「array-contains」または「array-contains-any」句を含めることができます。

一般的な制限:

  1. これらのソリューションは、部分的な文字列の検索をサポートしていないようです。たとえば、dbフィールドに「1 Peter St、Green District」が含まれている場合、文字列「strict」を検索することはできません。
  2. 予想される検索文字列のすべての可能な組み合わせをカバーすることはほとんど不可能です。たとえば、dbフィールドに「1 Mohamed St、Green District」が含まれている場合、「Green Mohamed」という文字列を検索できない場合があります。これは、dbで使用されている順序とは異なる順序の単語を含む文字列です。フィールド。

すべてに当てはまるソリューションはありません。それぞれの回避策には制限があります。上記の情報が、これらの回避策間の選択プロセス中に役立つことを願っています。

Firestoreのクエリ条件のリストについては、ドキュメントhttps://firebase.google.com/docs/firestore/query-data/queriesをご覧ください

@Jonathanから提案されたhttps://fireblog.io/blog/post/firestore-full-text-searchは試していません。


-10

バックティックを使用して文字列の値を出力できます。これはうまくいくはずです:

where('name', '==', `${searchTerm}`)

ありがとう、しかしこの質問は正確でない値を得ることについてです。たとえば、問題の例は、名前が正確である場合に機能します。「Test」という名前のドキュメントがある場合、「Test」を検索すると機能します。しかし、「tes」または「est」を検索しても、「Test」の結果が得られることを期待しています。本のタイトルのユースケースを想像してみてください。人々はしばしば正確なタイトル全体ではなく部分的なタイトルを検索します。
tehfailsafe 2017年

13
@suulisinあなたは正しい、私が見つけたものを共有したいと思っていたので、私はそれを注意深く読みませんでした。それを指摘するためにあなたの努力をありがとう、そして私はより慎重になります
Zach J
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.