【问题标题】:Firestore search array contains for multiple valuesFirestore 搜索数组包含多个值
【发布时间】: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 中是否可行?如果没有,有没有其他方法可以做到这一点?

【问题讨论】:

    标签: swift firebase google-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.b6vitamins.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
        }
        

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

        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.potassiumvitamins.b6 等。
        • @bsod 你确定这里需要复合索引吗? firebase.google.com/docs/firestore/query-data/…
        • @DougStevenson 说得对,我们不知道 OP 是否正在执行复合查询,但如果此查询是复合查询并且需要复合索引,他将不得不创建一个索引对于每种特定的维生素。这是在他的数据模型扩展之前需要考虑的事情。
        • @bsod 我展示的查询不需要复合索引,因为它只检查相等性。所以这应该不是问题,除非我错过了什么。
        猜你喜欢
        • 1970-01-01
        • 2021-05-17
        • 2019-03-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多