Firestore 搜索数组包含多个值
Posted
技术标签:
【中文标题】Firestore 搜索数组包含多个值【英文标题】:Firestore search array contains for multiple values 【发布时间】:2019-07-26 00:15:43 【问题描述】:如果我有一个简单的集合,例如:
Fruits:
Banana:
title: "Banana"
vitamins: ["potassium","B6","C"]
Apple:
title: "Apple"
vitamins: ["A","B6","C"]
如果我想搜索含有维生素 B6 的水果,我会执行以下操作:
db.collection("Fruits").whereField("vitamins", arrayContains: "A").getDocuments() (querySnapshot, err) in
if let err = err
print("Error getting documents: \(err)")
else
for document in querySnapshot!.documents
print("\(document.documentID) => \(document.data())")
这将显示我收藏中包含维生素 A 的所有水果。但是,我如何才能搜索包含多种维生素的水果,例如维生素 B6 和 C?我不能简单地搜索["B6","C"]
,因为它会寻找一个数组而不是独立的字符串。
这在 Cloud Firestore 中是否可行?如果没有,有没有其他方法可以做到这一点?
【问题讨论】:
【参考方案1】:我想我找到了一种行之有效的方法,并且避免为地图值创建所有索引
如果你create all array combinations possible from an array of values
[a, b] => [[a],[b],[a,b]]
您可以使用逗号或其他分隔符对它们进行排序和连接
[[a],[b],[a,b]].map => ['a','b','a,b']
然后存储字符串数组(必须排序)并使用已排序、分隔的搜索查询执行array-contains
where('possible_value_array', 'array-contains', 'a,b')
很好奇我是否在这里遗漏了什么
【讨论】:
我喜欢这个答案,因为您实际上是在创建自己的索引。但是,您必须事先知道所有值和组合,或者让 FB 函数在每次添加时生成它们。【参考方案2】:它已被评论,但如果您的查询要求您创建复合索引,则接受的答案是不可行的,因为它可能会导致您的复合索引计数很快达到 200 的限制。这很 hacky,但我能想到的最佳解决方案是以某种有保证的顺序存储,例如按字母顺序 - 给定水果的每种维生素组合的数组,每种组合都连接为一个字符串。例如,由于香蕉含有 B6、C 和钾,香蕉的一系列维生素组合将是:
B6
B6||C
B6||C||potassium
B6||potassium
C
C||potassium
potassium
那么,如果您的用户正在搜索同时包含 B6 和钾的每种水果,您的查询将是:
db.collection("Fruits").where("vitamins", "array-contains", "B6||potassium")
【讨论】:
【参考方案3】:无法在 Firestore 中执行多个包含数组的查询。但是,您可以尽可能多地组合where
条件。因此,请考虑:
[Fruits]
<Banana>
title: "Banana"
vitamins:
potassium: true
b6: true
c: true
Firestore.firestore().collection("Fruits").whereField("vitamins.potassium", isEqualTo: true).whereField("vitamins.b6", isEqualTo: true)
但是,这仍然不允许干净的OR
搜索。要查询含有维生素 X 或维生素 Y 的水果,您必须更有创意。此外,如果可能的值很多并且需要与复合查询结合使用,则不应使用此方法。这是因为 Firestore 不允许超过 200 个综合指数,并且每个综合指数都需要指定确切的维生素。这意味着您必须为 vitamins.b6
、vitamins.potassium
等创建单独的复合索引,而您总共只有 200 个。
【讨论】:
是否有任何替代方法或数据库允许“或”搜索而不是“和”搜索? 太棒了,谢谢,这很有帮助。 Firestore 显然处于测试阶段,因此毫无疑问会有很多改进 如果不先创建索引,您也无法按不同的字段排序(如果您事先不知道结果,则无法这样做)【参考方案4】:Firestore 在 2019 年底推出了 whereIn、arrayContains 和 arrayContainsAny 方法。这些方法执行逻辑“或”查询,但您可以传入的子句限制为 10 个(因此您最多可以搜索 10 种维生素)。
一些解决方案已经提到重组您的数据。另一个利用重组方法的解决方案是不将维生素包含在水果文档中,而是反过来。通过这种方式,您可以获得“香蕉”所属的所有文档。
let vitaminsRef = db.collection('Vitamins').where('fruits', arrayContains: 'banana');
此解决方案允许您绕过 10 个子句的限制。 它在一次读取操作中获取所有在其“水果”数组中包含“香蕉”的“维生素”文档(考虑分页,如果太多)。
【讨论】:
【参考方案5】:在 Firestore 中无法查询具有多个 arrayContains
的 Firestore 数据库。如果您将使用多个,可能会出现这样的错误:
无效的查询。查询仅支持具有单个包含数组的过滤器。
如果您需要过滤多种维生素,则需要通过为您拥有的每种维生素创建一个属性,然后链接.whereField()
函数调用来更改构建数据库的逻辑。我知道这听起来有点奇怪,但这就是 Cloud Firestore 的工作原理。
你的架构应该是这样的:
vitamins:
"potassium": true,
"B6": true,
"C": true
要查找所有含有potassium
、B6
和C
维生素的水果,您应该使用如下所示的查询:
let fruitsRef = db.collection("Fruits")
.whereField("vitamins.potassium", isEqualTo: true)
.whereField("vitamins.B6", isEqualTo: true)
.whereField("vitamins.C", isEqualTo: true)
【讨论】:
有什么办法可以让这个动态化。例如,如果我每次都在搜索不同的维生素,是否有一种方法可以在不硬编码的情况下搜索它们?在这种情况下,我每次都必须准确地搜索 3 种维生素。 请参阅 Doug 的建议。【参考方案6】:通过在查询中链接条件来查找匹配多个条件的文档是很诱人的:
db.collection("Fruits")
.whereField("vitamins", arrayContains: "B6")
.whereField("vitamins", arrayContains: "C")
但documentation on compound queries 建议您目前不能在单个查询中拥有多个 arrayContains 条件。
所以,你今天拥有的东西是不可能的。考虑使用映射结构而不是数组:
Fruits:
Banana:
title: "Banana"
vitamins:
"potassium": true,
"B6": true,
"C": true
那么你可以这样查询:
db.collection("Fruits")
.whereField("vitamins.B6", isEqualTo: true)
.whereField("vitamins.C", isEqualTo: true)
【讨论】:
谢谢,这很有帮助,看来我仍然可以用这种方法做任何事情 @LucasAzzopardi 唯一的缺陷是 Firestore 不允许超过 200 个复合索引,这意味着如果您只对维生素执行复合查询,您应该没问题,因为没有数百个的维生素。但是,例如,如果有 100 种维生素,并且您执行的复合查询需要为每个查询创建一个复合索引,那么您将很快达到最大值,因为每个复合索引都必须指定特定的维生素。您不能简单地将vitamins
包含在复合索引中,它必须是vitamins.potassium
、vitamins.b6
等。
@bsod 你确定这里需要复合索引吗? firebase.google.com/docs/firestore/query-data/…
@DougStevenson 说得对,我们不知道 OP 是否正在执行复合查询,但如果此查询是复合查询并且需要复合索引,他将不得不创建一个索引对于每种特定的维生素。这是在他的数据模型扩展之前需要考虑的事情。
@bsod 我展示的查询不需要复合索引,因为它只检查相等性。所以这应该不是问题,除非我错过了什么。以上是关于Firestore 搜索数组包含多个值的主要内容,如果未能解决你的问题,请参考以下文章
Firebase Firestore - 使用多个“数组包含”过滤数据