我希望添加一个简单的搜索字段,想要使用像
这样的东西 where('name', '==', '%searchTerm%')
我尝试使用{{1}},但它没有返回任何内容。
答案 0 :(得分:29)
虽然就限制而言Kuba的答案是正确的,但您可以使用类似集合的结构来部分模仿:
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
现在您可以使用
进行查询collectionRef.where('terms.tennis', '==', true)
这是有效的,因为Firestore会自动为每个字段创建索引。不幸的是,这并不能直接用于复合查询,因为Firestore不会自动创建复合索引。
你仍然可以通过存储单词组合来解决这个问题,但这很快就会变得难看。
你可能还有更好的外线full text search。
答案 1 :(得分:19)
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's Method会做您想做的所有事情,或者至少在您稍加修改之后。但是,仅用一段文本,您将以指数方式创建超过1MB的数据,这超出了Firestore的文档大小限制(我知道,我已经测试过)。
5。)。如果您可以将{strong>包含数组的(或某种形式的数组)与\uf8ff
技巧结合使用,则可以进行可行的搜索没有达到极限。我尝试了所有组合,即使使用地图也没有尝试。任何人都可以解决,将其发布在这里。
6。)。如果您必须远离ALGOLIA和ELASTIC SEARCH,而我一点也不怪,您可以在Google Cloud上始终使用mySQL,postSQL或neo4J。它们都是3种易于设置的,并且具有免费等级。您将具有一个云函数来保存数据onCreate()和另一个onCall()函数来搜索数据。简单...有点。那么为什么不直接切换到mySQL呢?当然是实时数据!当有人用网络袜写DGraph来获取实时数据时,请指望我!
Algolia和ElasticSearch被构建为仅搜索数据库,因此没有什么比这快的了,但是您需要为此付费。 Google,为什么您将我们带离Google,又不遵循MongoDB noSQL并允许搜索?
更新-我创建了一个解决方案:
答案 2 :(得分:17)
没有此类运营商,允许的运营商为==
,<
,<=
,>
,>=
。
您只能按前缀进行过滤,例如,对于在bar
和foo
之间展开的所有内容,您都可以使用
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
您可以使用Algolia或ElasticSearch等外部服务。
答案 3 :(得分:9)
Firebase不明确支持在字符串中搜索术语...
Firebase现在确实支持以下内容,这将解决您的情况以及许多其他情况:
截至2018年8月,它们支持array-contains
查询。参见:https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
现在,您可以将所有关键术语设置为一个数组作为字段,然后查询具有包含“ X”的数组的所有文档。您可以使用逻辑AND对附加查询进行进一步的比较。(这是因为firebase does not currently natively support compound queries for multiple array-contains queries,因此“ AND”排序查询必须在客户端进行)
使用这种样式的数组将使它们针对并发写入进行优化,这很好!尚未测试它是否支持批处理请求(文档未说),但我敢打赌,因为它是官方解决方案,所以支持。
collection("collectionPath").
where("searchTermsArray", "array-contains", "term").get()
答案 4 :(得分:7)
我确定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()
)
答案 5 :(得分:6)
根据Firestore docs,Cloud Firestore不支持本机索引或搜索文档中的文本字段。此外,下载整个集合以搜索客户端字段是不切实际的。
建议使用Algolia和Elastic Search等第三方搜索解决方案。
答案 6 :(得分:5)
所选答案仅适用于精确搜索,并且不是自然的用户搜索行为(在“今天吃个苹果”中搜索“苹果”是行不通的。)
我认为,丹·费恩(Dan Fein)的上述回答应该排名更高。如果要搜索的字符串数据很短,则可以将字符串的所有子字符串保存在文档中的数组中,然后使用Firebase的array_contains查询搜索该数组。 Firebase文档限制为1 MiB(1,048,576字节)(Firebase Quotas and Limits),这大约是文档中保存的100万个字符(我认为1个字符〜= 1个字节)。只要您的文档不接近一百万个标记,就可以存储子字符串。
搜索用户名的示例:
步骤1:将以下String扩展名添加到您的项目中。这使您可以轻松地将字符串分解为子字符串。 (I found this here)。
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....
答案 7 :(得分:2)
最新答案,但对于仍在寻找答案的任何人,假设我们有一个用户集合,并且在该集合的每个文档中都有一个“用户名”字段,因此,如果要查找用户名开头为的文档“ al”我们可以做类似
的操作 FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
答案 8 :(得分:2)
截止到今天,专家们提出了3种不同的解决方法,作为对问题的解答。
我已经尝试了全部。我认为记录每个人的经历可能会有用。
方法A:使用:(dbField“> =” searchString)&(dbField“ <=” searchString +“ \ uf8ff”)
由@Kuba和@Ankit Prajapati建议
.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");
A.1 Firestore查询只能在单个字段上执行范围过滤器(>,<,> =,<=)。不支持在多个字段上使用范围过滤器的查询。通过使用此方法,您不能在数据库的任何其他字段中使用范围运算符,例如日期字段。
A.2。此方法不适用于同时在多个字段中搜索。例如,您无法检查搜索字符串是否在任何文件名(名称,注释和地址)中。
方法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。
我还没有尝试过{@ 3ona}建议的https://fireblog.io/blog/post/firestore-full-text-search。
答案 9 :(得分:2)
我像乔纳森所说的那样使用了trigram。
三元组是存储在数据库中的 3 个字母组,用于帮助搜索。所以如果我有用户的数据,我假设我想查询唐纳德特朗普的“trum”,我必须以这种方式存储它
我只是这样回忆
onPressed: () {
//LET SAY YOU TYPE FOR 'tru' for trump
List<String> search = ['tru', 'rum'];
Future<QuerySnapshot> inst = FirebaseFirestore.instance
.collection("users")
.where('trigram', arrayContainsAny: search)
.get();
print('result=');
inst.then((value) {
for (var i in value.docs) {
print(i.data()['name']);
}
});
无论如何都会得到正确的结果
答案 10 :(得分:1)
我实际上认为在Firestore中执行此操作的最佳解决方案是将所有子字符串放入数组中,然后执行array_contains查询。这使您可以进行子字符串匹配。存储所有子字符串有点矫kill过正,但是如果您的搜索词很短,那是非常非常合理的。
答案 11 :(得分:1)
我同意@Kuba的回答,但是,它仍然需要添加一个小的更改以完美地用于前缀搜索。这对我有用
用于搜索名称为queryText
的记录
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
。
查询中使用的字符\uf8ff
是Unicode范围内的一个很高的代码点(它是专用使用区[PUA]代码)。因为查询是在Unicode中大多数常规字符之后,所以该查询将匹配以queryText
开头的所有值。
答案 12 :(得分:1)
从其他答案中提取了一些内容。这包括:
区分大小写有点限制,您可以通过以大写形式存储重复属性来解决此问题。例如:query.toUpperCase()
user.last_name_upper
// query: searchable terms as string
let users = await searchResults("Bob Dylan", 'users');
async function searchResults(query = null, collection = 'users', keys = ['last_name', 'first_name', 'email']) {
let querySnapshot = { docs : [] };
try {
if (query) {
let search = async (query)=> {
let queryWords = query.trim().split(' ');
return queryWords.map((queryWord) => keys.map(async (key) =>
await firebase
.firestore()
.collection(collection)
.where(key, '>=', queryWord)
.where(key, '<=', queryWord + '\uf8ff')
.get())).flat();
}
let results = await search(query);
await (await Promise.all(results)).forEach((search) => {
querySnapshot.docs = querySnapshot.docs.concat(search.docs);
});
} else {
// No query
querySnapshot = await firebase
.firestore()
.collection(collection)
// Pagination (optional)
// .orderBy(sortField, sortOrder)
// .startAfter(startAfter)
// .limit(perPage)
.get();
}
} catch(err) {
console.log(err)
}
// Appends id and creates clean Array
const items = [];
querySnapshot.docs.forEach(doc => {
let item = doc.data();
item.id = doc.id;
items.push(item);
});
// Filters duplicates
return items.filter((v, i, a) => a.findIndex(t => (t.id === v.id)) === i);
}
注意:Firebase 调用的次数等于查询字符串中的单词数 * 您正在搜索的键数。
答案 13 :(得分:0)
如果您不想使用像Algolia这样的第三方服务,Firebase Cloud Functions是一个不错的选择。您可以创建一个函数,该函数可以接收输入参数,在服务器端处理记录,然后返回与您的条件匹配的参数。
答案 14 :(得分:0)
我只是遇到了这个问题,并提出了一个非常简单的解决方案。
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
isGreaterThanOrEqualTo使我们可以过滤掉搜索的开始,并在isLessThanOrEqualTo的末尾添加一个“ z”,以限制搜索范围,而不是下一个文档。
答案 15 :(得分:0)
使用Firestore,您可以实施全文搜索,但与其他方法相比,它的读取费用仍然更高,而且您还需要以特定方式输入数据并为其建立索引,因此,使用这种方法可以使用firebase cloud函数标记并随后对输入文本进行哈希处理,同时选择满足以下条件的线性哈希函数h(x)
-如果x < y < z then h(x) < h (y) < h(z)
。对于标记化,您可以选择一些轻量级的NLP库,以使函数的冷启动时间保持在较低水平,这样可以从句子中去除不必要的单词。然后,您可以在Firestore中使用小于和大于运算符来运行查询。
在存储数据的同时,还必须确保在存储文本之前对文本进行哈希处理,并且还要存储纯文本,就好像您更改了纯文本一样,哈希值也会发生变化。
答案 16 :(得分: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);
})
};
答案 17 :(得分:0)
以下代码段从用户那里获取输入,并从键入的代码开始获取数据。
样本数据:
在Firebase集合“用户”下
user1:{name:“ Ali”,年龄:28},
user2:{name:'Khan',age:30},
user3:{名称:“哈桑”,年龄:26},
user4:{name:'Adil',年龄:32}
TextInput :A
结果:
{姓名:“阿里”,年龄:28},
{姓名:“阿迪尔”,年龄:32}
let timer;
// method called onChangeText from TextInput
const textInputSearch = (text) => {
const inputStart = text.trim();
let lastLetterCode = inputStart.charCodeAt(inputStart.length-1);
lastLetterCode++;
const newLastLetter = String.fromCharCode(lastLetterCode);
const inputEnd = inputStart.slice(0,inputStart.length-1) + lastLetterCode;
clearTimeout(timer);
timer = setTimeout(() => {
firestore().collection('Users')
.where('name', '>=', inputStart)
.where('name', '<', inputEnd)
.limit(10)
.get()
.then(querySnapshot => {
const users = [];
querySnapshot.forEach(doc => {
users.push(doc.data());
})
setUsers(users); // Setting Respective State
});
}, 1000);
};
答案 18 :(得分:0)
与@nicksarno 相同,但代码更精致,不需要任何扩展:
第一步
func getSubstrings(from string: String, maximumSubstringLenght: Int = .max) -> [Substring] {
let string = string.lowercased()
let stringLength = string.count
let stringStartIndex = string.startIndex
var substrings: [Substring] = []
for lowBound in 0..<stringLength {
for upBound in lowBound..<min(stringLength, lowBound+maximumSubstringLenght) {
let lowIndex = string.index(stringStartIndex, offsetBy: lowBound)
let upIndex = string.index(stringStartIndex, offsetBy: upBound)
substrings.append(string[lowIndex...upIndex])
}
}
return substrings
}
第 2 步
let name = "Lorenzo"
ref.setData(["name": name, "nameSubstrings": getSubstrings(from: name)])
步骤 3
Firestore.firestore().collection("Users")
.whereField("nameSubstrings", arrayContains: searchText)
.getDocuments...
答案 19 :(得分:-2)
Firebase 建议使用 Algolia 或 ElasticSearch 进行全文搜索,但更便宜的替代方案可能是 MongoDB。最便宜的集群(大约 10 美元/月)允许您为全文编制索引。
答案 20 :(得分:-8)
我们可以使用back-tick打印出字符串的值。这应该有效:
where('name', '==', `${searchTerm}`)