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.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 我展示的查询不需要复合索引,因为它只检查相等性。所以这应该不是问题,除非我错过了什么。

以上是关于Firestore 搜索数组包含多个值的主要内容,如果未能解决你的问题,请参考以下文章

Firebase Firestore - 使用多个“数组包含”过滤数据

更新 Firestore 中 Array 的 Map 数据 - Flutter

我可以在Firestore搜索中过滤多个字段吗?

如何在 Firestore 中一次创建/更新多个文档

如何从firestore的路径中获取布尔值

如何使用 SwiftUI 将值数组写入 Firestore