如果我有一个简单的收藏夹,例如:
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中甚至有可能吗?如果没有,还有其他替代方法吗?
答案 0 :(得分:3)
通过在查询中链接条件来寻找匹配多个条件的文档将很诱人:
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)
答案 1 :(得分:2)
Firestore在2019年末推出了whereIn,arrayContains和arrayContainsAny方法。这些方法执行逻辑“或”查询,但是您可以传递10个子句(因此最多可以搜索10种维生素)。
一些解决方案已经提到了重组数据。利用重组方法的另一种解决方案是不将“维生素”包括在“水果”文档中,而是采用其他方法。这样,您将获得“香蕉”的所有文档。
let vitaminsRef = db.collection('Vitamins').where('fruits', arrayContains: 'banana');
此解决方案使您可以规避10个子句的限制。 只需一次读取操作即可获取所有在“水果”数组中具有“香蕉”的“维生素”文件(请考虑分页,如果太多的话)。
答案 2 :(得分:1)
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 :(得分:1)
无法像在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
搜索,仅进行and
。要查询含维生素X或维生素Y的水果,您必须更具创造力。此外,这种方法 不应用于可能值“很多”且需要与复合查询结合使用的情况。这是因为Firestore不允许使用超过200个综合指数,并且每个综合指数都需要指定确切的维生素。例如,您必须为vitamins.b6
,vitamins.potassium
等创建一个单独的复合索引,而总共只有200个。
答案 4 :(得分:0)
您可以轻松地使用array-contains-any
。
db.collection("Fruits")
.where('vitamins', 'array-contains-any', ["B6", "C"])
.get();
在那里,你有它。
答案 5 :(得分:0)
已对此进行了评论,但是如果您的查询要求您创建一个复合索引,则无法接受的答案是不可行的,因为它很可能导致您的复合索引数量迅速达到200个限制。但是我能想到的最好的解决方案是按照某种有保证的顺序进行存储,例如按字母顺序-给定水果的每种维生素组合的数组,每种组合串联在一起。例如,由于香蕉含有B6,C和钾,因此香蕉的一系列维生素组合为:
B6
B6||C
B6||C||potassium
B6||potassium
C
C||potassium
potassium
然后,如果您的用户正在搜索同时含有B6和钾的每种水果,那么您的查询将是:
db.collection("Fruits").where("vitamins", "array-contains", "B6||potassium")
答案 6 :(得分:0)
我认为我找到了一种行之有效的方法,并且避免为地图值创建所有索引
如果您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')
好奇我是否在这里想念