Firestore搜索数组包含多个值

时间:2019-03-04 16:23:58

标签: swift firebase google-cloud-firestore

如果我有一个简单的收藏夹,例如:

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中甚至有可能吗?如果没有,还有其他替代方法吗?

7 个答案:

答案 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
}

要查找所有含有potassiumB6C维生素的水果,您应该使用如下查询:

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.b6vitamins.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')

好奇我是否在这里想念