我想添加一个简单的搜索字段,想使用类似
collectionRef.where('name', 'contains', 'searchTerm')
我尝试使用where('name', '==', '%searchTerm%')
,但未返回任何内容。
我想添加一个简单的搜索字段,想使用类似
collectionRef.where('name', 'contains', 'searchTerm')
我尝试使用where('name', '==', '%searchTerm%')
,但未返回任何内容。
Answers:
有没有这样的运营商,允许的有==
,<
,<=
,>
,>=
。
您可以通过前缀只能过滤,例如用于一切之间的启动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范围内的一个很高的代码点(它是专用使用区[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
现在使用google,您只会找到类似%20abcde.
或,abcde!
否之类的结果abcdefghijk..
。即使可以肯定在互联网上找到键入的整个字母更为普遍,但搜索不是针对abcde *,而是针对孤立的abcde
'contains'
理解了您的观点,但我同意这一点,但是我可能会误认为单词,这恰恰意味着我在许多编程语言中所指的含义。这同样适用于'%searchTerm%'
从SQL立场。
根据Firestore文档,Cloud 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()
以每种组合方式都无法使用,但是,可以通过创建另一个反向的搜索字段并组合结果来获得相同的结果。
示例:首先,您必须在创建字段时保存字段的反向版本。像这样:
// 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的方法将完成您想要的所有事情,或者至少在您稍加修改之后。但是,仅用一段文本,您将以指数方式创建超过1MB的数据,这超出了Firestore的文档大小限制(我知道,我已经测试过)。
5)如果您可以将包含数组的数组(或某种形式的数组)与\uf8ff
技巧结合使用,则可能进行的搜索没有达到限制。我尝试了所有组合,即使使用地图也没有尝试。任何人都可以解决,将其发布在这里。
6.)如果您必须远离ALGOLIA和ELASTIC SEARCH,而我一点也不怪,您可以在Google Cloud上始终使用mySQL,postSQL或neo4J。它们都是3种易于设置的,并且具有免费等级。您将具有一个云函数来保存数据onCreate()和另一个onCall()函数来搜索数据。简单...有点。那么为什么不直接切换到mySQL呢?当然是实时数据!当有人用websocks为实时数据编写DGraph时,请指望我!
Algolia和ElasticSearch被构建为仅搜索数据库,因此没有什么比这快的了,但是您需要为此付费。Google,为什么您将我们带离Google,又不遵循MongoDB noSQL并允许搜索?
更新-我创建了一个解决方案:
答案较晚,但对于仍在寻找答案的任何人,假设我们有一个用户集合,并且在该集合的每个文档中都有一个“用户名”字段,因此,如果要查找用户名以“ al”开头的文档我们可以做类似的事情
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
我确定Firebase很快就会出现“字符串包含”,以捕获字符串中的任何索引[i] startAt ...但是我研究了网络,发现这种解决方案是由其他人想到的,例如这个
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”,以限制搜索范围而不移至下一个文档。
所选答案仅适用于精确搜索,并且不是自然的用户搜索行为(在“今天吃个苹果”中搜索“苹果”将不起作用)。
我认为Dan Fein在上述问题上的答案应该排名更高。如果您要搜索的字符串数据很短,则可以将字符串的所有子字符串保存在文档中的数组中,然后使用Firebase的array_contains查询搜索该数组。Firebase文档限制为1 MiB(1,048,576字节)(Firebase配额和限制),即文档中保存的大约100万个字符(我认为1个字符〜= 1个字节)。只要您的文档不接近一百万个标记,就可以存储子字符串。
搜索用户名的示例:
步骤1:将以下String扩展名添加到您的项目中。这使您可以轻松地将字符串分解为子字符串。(我在这里找到了)。
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步:存储用户名时,还要将此函数的结果作为数组存储在同一Document中。这将创建原始文本的所有变体,并将它们存储在数组中。例如,文本输入“ Apple”将创建以下数组:[“ a”,“ p”,“ p”,“ l”,“ e”,“ ap”,“ pp”,“ pl”,“ le “,” app“,” ppl“,” ple“,” appl“,” pple“,” apple“]],其中应包含用户可能输入的所有搜索条件。如果需要所有结果,可以将maximumStringSize保留为nil,但是,如果文本较长,我建议在文档大小过大之前将其设置为上限-大约15个对我来说很好(大多数人都不会搜索长短语) )。
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查询只能在单个字段上执行范围过滤器(>,<,> =,<=)。不支持在多个字段上使用范围过滤器的查询。通过使用此方法,您不能在数据库的任何其他字段(例如,日期字段)中使用范围运算符。
A2。此方法不适用于同时在多个字段中搜索。例如,您无法检查搜索字符串是否在任何文件名(名称,注释和地址)中。
方法B:对地图中的每个条目使用带有“ true”的搜索字符串MAP,并在查询中使用“ ==”运算符
由@Gil Gilbert建议
document1 = {
'searchKeywordsMap': {
'Jam': true,
'Butter': true,
'Muhamed': true,
'Green District': true,
'Muhamed, Green District': true,
}
}
.where(`searchKeywordsMap.${searchString}`, "==", true);
B.1显然,此方法每次将数据保存到db时都需要额外的处理,更重要的是,需要额外的空间来存储搜索字符串的映射。
B.2如果Firestore查询具有上述单个条件,则无需事先创建索引。在这种情况下,此解决方案会很好用。
B.3但是,如果查询还有其他条件,例如(状态===“活动”),则似乎用户输入的每个“搜索字符串”都需要一个索引。换句话说,如果一个用户搜索“ Jam”,而另一个用户搜索“ Butter”,则应事先为字符串“ Jam”创建一个索引,为“ Butter”创建另一个索引,依此类推。除非可以预测所有可能用户的搜索字符串,这不起作用-如果查询还有其他条件!
.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");
** 方法C:使用搜索字符串的数组和“数组包含”运算符
由@Albert Renshaw建议和由@Nick Carducci演示
document1 = {
'searchKeywordsArray': [
'Jam',
'Butter',
'Muhamed',
'Green District',
'Muhamed, Green District',
]
}
.where("searchKeywordsArray", "array-contains", searchString);
C.1与方法B相似,此方法每次将数据保存到db时都需要额外的处理,更重要的是,需要额外的空间来存储搜索字符串数组。
C.2 Firestore查询在复合查询中最多可以包含一个“ array-contains”或“ array-contains-any”子句。
一般限制:
没有一种解决方案能适合所有人。每个解决方法都有其局限性。我希望以上信息可以在选择这些变通办法期间为您提供帮助。
有关Firestore查询条件的列表,请查看文档https://firebase.google.com/docs/firestore/query-data/queries。
我没试过https://fireblog.io/blog/post/firestore-full-text-search,这是由@Jonathan建议。
我们可以使用反引号来打印出字符串的值。这应该工作:
where('name', '==', `${searchTerm}`)