単純な検索フィールドを追加したいのですが、次のようなものを使用したいと思います
collectionRef.where('name', 'contains', 'searchTerm')
を使ってみましたwhere('name', '==', '%searchTerm%')
が何も返りませんでした。
単純な検索フィールドを追加したいのですが、次のようなものを使用したいと思います
collectionRef.where('name', 'contains', 'searchTerm')
を使ってみましたwhere('name', '==', '%searchTerm%')
が何も返りませんでした。
回答:
そのような作業はありません、許可されたものです==
、<
、<=
、>
、>=
。
あなたは間を開始し、すべてのために、たとえば、プレフィクスだけでフィルタリングすることができますbar
し、foo
あなたが使用することができます
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
そのために、AlgoliaやElasticSearch などの外部サービスを使用できます。
tennis
、使用可能なクエリ演算子に基づいて、これらの結果を取得する方法はありません。組み合わせる>=
と<=
動作しません。もちろん、Algoliaを使用することもできますが、ほとんどのクエリを実行するためにFirebaseで使用することもでき、Firestoreに切り替える必要はありません...
Kubaの答えは制限に関する限り当てはまりますが、これをセットのような構造で部分的にエミュレートできます。
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
これで、クエリを実行できます
collectionRef.where('terms.tennis', '==', true)
これは、Firestoreがすべてのフィールドのインデックスを自動的に作成するため機能します。残念ながら、Firestoreは複合インデックスを自動的に作成しないため、これは複合クエリでは直接機能しません。
単語の組み合わせを保存することでこれを回避できますが、これは見苦しく速くなります。
おそらく、外部の全文検索を使用するほうがよいでしょう。
where
@Kubaの答えに同意しますが、それでも、接頭辞による検索で完全に機能するように小さな変更を加える必要があります。ここで私のために働いたもの
名前で始まるレコードを検索するには queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
。
\uf8ff
クエリで使用される文字は、Unicode範囲の非常に高いコードポイントです(これはPrivate Usage Area [PUA]コードです)。Unicodeのほとんどの通常の文字の後にあるため、クエリはで始まるすべての値に一致しますqueryText
。
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()
Search term
は、通常、両端がスペース、句読点などで区切られた用語全体を意味すると理解されています。あなたはグーグル場合はabcde
、今あなただけのようなものの結果でしょう%20abcde.
か,abcde!
ではなく、をabcdefghijk..
。入力されたアルファベット全体がインターネット上で見つかる方がはるかに一般的ですが、検索はabcde *ではなく、孤立したabcdeの検索です
'contains'
理解し、それに同意しますが、おそらく私は多くのプログラミング言語で私が言及していることを意味するという言葉によって誤解を招いたのかもしれません。'%searchTerm%'
SQLの観点からも同じことが言えます。
パーFirestoreドキュメント、クラウドFirestoreは、ネイティブのインデックス作成をサポートしていないか、または文書内のテキストフィールドを検索します。さらに、クライアント側でフィールドを検索するためにコレクション全体をダウンロードすることは現実的ではありません。
AlgoliaやElastic Searchなどのサードパーティの検索ソリューションをお勧めします。
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をフォローして検索を許可しないのですか?
更新-ソリューションを作成しました:
遅い答えですが、まだ答えを探している人のために、ユーザーのコレクションがあり、コレクションの各ドキュメントに「username」フィールドがあるとしましょう。したがって、ユーザー名が「al」で始まるドキュメントを検索したい場合私たちは次のようなことができます
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
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()
)
Algoliaのようなサードパーティのサービスを使用したくない場合は、Firebase Cloud Functionsが優れた代替手段となります。入力パラメータを受け取り、レコードをサーバー側で処理して、条件に一致するものを返すことができる関数を作成できます。
私はこの問題を抱えていて、かなり簡単な解決策を思いつきました。
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
isGreaterThanOrEqualToを使用すると、検索の先頭を除外できます。isLessThanOrEqualToの末尾に "z"を追加することで、次のドキュメントにロールオーバーしないように検索をキャップします。
選択した回答は正確な検索でのみ機能し、ユーザーの自然な検索動作ではありません(「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....
Firestoreを使用すると、全文検索を実装できますが、それ以外の場合よりも読み取りにコストがかかります。また、特定の方法でデータを入力してインデックスを付ける必要があるため、このアプローチでは、firebaseクラウド関数を使用してh(x)
次の条件を満たす線形ハッシュ関数を選択しながら、入力テキストをトークン化してハッシュしますx < y < z then h(x) < h (y) < h(z)
。トークン化のために、軽量のNLPライブラリを選択して、文から不要な単語を取り除くことができる関数のコールドスタート時間を低く保つことができます。次に、Firestoreで「より小さい」および「より大きい」演算子を使用してクエリを実行できます。データも保存する際は、保存する前にテキストをハッシュし、プレーンテキストも保存する必要があります。プレーンテキストを変更すると、ハッシュされた値も変更されます。
これは完璧に機能しましたが、パフォーマンスの問題を引き起こす可能性があります。
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);
})
};
今日のところ、質問に対する回答として、専門家によって提案された基本的に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」句を含めることができます。
一般的な制限:
すべてに当てはまるソリューションはありません。それぞれの回避策には制限があります。上記の情報が、これらの回避策間の選択プロセス中に役立つことを願っています。
Firestoreのクエリ条件のリストについては、ドキュメントhttps://firebase.google.com/docs/firestore/query-data/queriesをご覧ください。
@Jonathanから提案されたhttps://fireblog.io/blog/post/firestore-full-text-searchは試していません。
バックティックを使用して文字列の値を出力できます。これはうまくいくはずです:
where('name', '==', `${searchTerm}`)