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

Posted

技术标签:

【中文标题】Firebase Firestore - 使用多个“数组包含”过滤数据【英文标题】:Firebase Firestore - Filter data with multiple 'array-contains' 【发布时间】:2019-10-25 22:04:48 【问题描述】:

我正在努力寻找有关使用 Firebase Firestore 过滤数据的最佳做法的好材料。我想根据用户选择的类别过滤我的数据。我有一个存储在我的 firestore 数据库中的文档集合,每个文档都有一个数组,其中包含该单个文档的所有适当类别。为了过滤,我还保留了一个包含用户首选类别的本地数组。我要做的就是根据用户的首选类别过滤数据。

firestore categories field

考虑我将用户的首选类别存储为字符串数组( ["Film", "Music"] )。我正计划使用 firestore 的 'array-contains' 方法,例如

db.collection(collectioname)
.where('categoriesArray', 'array-contains', ["Film", "Music"])

后来我发现我不能对数组本身使用'array-contains',在调查了这个问题之后,我决定改变我提到的here的数据结构。

categories changed to Map

一旦我将类别从数组更改为映射,我想我可以使用多个 where 条件来过滤文档

let query = db.collection(collectionName)
      .where(somefield, '==', true)

this.props.data.filterCategories.forEach((val) => 
  query = query.where(`categories.$val`, '==', true);
);

query = query
        .orderBy(someOtherField, "desc")
        .limit(itemsPerPage)

const snapshot = await query.get()

现在问题 2,firebase 需要为复合查询添加索引。我在每个文档中保存的类别是动态的,我无法提前添加这些索引。在这种情况下,理想的解决方案是什么?任何帮助将不胜感激。

【问题讨论】:

你说不能高级索引是什么意思? 在我的用例中,每个文档中的类别字段是不同的,或者我不能简单地预先定义这些类别的主集。创建新文档后,用户将能够从类别列表中选择适合当前上下文的类别。此列表来自另一个集合,例如类别,并且此集合中的文档每次都可能不同。表示新类别可能会添加到此集合中,或者现有类别可能会被删除。在这种情况下,我将无法跟上整个索引的步伐。 你是说 Firestore 拒绝查询?你能更具体一点吗?尝试在没有任何循环的情况下编写查询(您当前的 forEach 循环看起来不起作用 - 它实际上并没有正确构建查询对象)。 这是 OR 查询还是 AND 查询?您是要获取类别为音乐或电影的文档,还是获取类别包括音乐和电影的文档?而且,是的,复合索引限制是一个真正的障碍,但如果您正确地对数据进行非规范化,则不应成为障碍。 @bsod 我正在寻找类别包括音乐或电影或两者兼有的所有文档,以及其他一些过滤条件和对我的数据进行分页的设置。您能否指导我更详细地了解如何克服这种限制? 【参考方案1】:

这是 2019 年 11 月 7 日推出的 Firebase javascript SDK 的一项新功能:

Version 7.3.0 - November 7, 2019

array-contains-any

"array-contains-any 运算符可将同一字段上的最多 10 个数组包含子句与逻辑 OR 组合。数组包含任何查询返回文档,其中给定字段是包含一个或多个比较的数组价值观”

citiesRef.where('regions', 'array-contains-any',
    ['west_coast', 'east_coast']);

【讨论】:

这似乎是一个干净的解决方案。我很高兴 Firebase 确实添加了这样的功能。肯定会试试这个。【参考方案2】:

您的查询包含一个 orderBy 子句。这与任何相等过滤器相结合,要求您创建一个索引来支持该查询。没有办法避免这种情况。

如果您删除 orderBy,您将能够使用文档中的地图属性来灵活、动态地过滤相等性。这是您无需创建索引即可拥有动态过滤器的唯一方法。这当然意味着您必须在客户端对查询结果进行排序和分页。

【讨论】:

嗯,我有点希望那里有更好的东西。现在我知道我不能完全做所有这些事情,在客户端过滤数据对我来说似乎是一个不错的选择,我将继续这样做。【参考方案3】:

不是遍历您希望查询的每个类别并将子句附加到单个query 对象,而是每次迭代都应该是其自己的独立查询。您可以将类别保存在一个数组中。

<document>
    - itemId: abc123
    - categories: [film, music, television]

如果您希望执行OR 查询,您将创建 n 循环,其中每个循环将查询数组包含该类别的文档。然后在您的最后,您将根据项目的标识符从结果中删除(删除重复项)。因此,如果您想查询电影或音乐,您将创建 2 个循环,其中第一次迭代查询数组包含电影的文档,第二个循环查询数组包含音乐的文档。结果将被放入同一个集合中,然后您只需删除所有具有相同 itemId 的重复项。

这也不会对复合索引限制造成问题,因为categories 是一个静态字段。真正的问题来自分页,因为您需要记录所有获取的 itemId -o 表示法:https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/)。而且由于您在本地进行重复数据删除,因此不能保证用户看到的分页块是均匀的。例如,如果每个分页块设置为 25 个文档,则某些页面可能最终显示 24 个,一些 21 个,另一些 14 个,具体取决于从每个块中删除了多少重复项。

【讨论】:

感谢您提供详细信息。所以我必须从 firestore 读取相同的文档几次,然后从客户端执行所有业务逻辑? 在这种情况下,是的,因为 Firestore 无法执行 OR 查询。 我想我会保持我的分页和排序内容不变,并根据客户端的用户偏好类别过滤数据。在这个阶段,这对我来说似乎是更好的方法。感谢您的支持顺便说一句【参考方案4】:

您是否打算检索具有确切类别数组的文档?假设您的用户偏好列为["Film", "Music"]。您希望仅检索那些包含 Film AND Music 的文档,还是希望检索包含 Film OR music 的文档?

如果是后者,那么也许你可以用“Film”查询所有文档,然后用“Music”查询所有文档,然后合并。然而,这里的缺点是一些冗余文档读取,当此类文档在 categoryArray 字段中同时包含“电影”和“音乐”时。

您还可以使用Algolia 进行探索以启用全文搜索。在这种情况下,您可能会将类别列表存储为一个可能用逗号分隔的字符串,然后在用户更改其偏好时更新整个字符串。

对于前一种情况,除了按字母顺序将其存储为串联字符串之外,我还没有遇到过可行的解决方案?其他人可能有比我更可靠的解决方案。

希望这会有所帮助!

【讨论】:

嘿,谢谢你的信息。我正在寻找所有具有电影或音乐或两者的文档以及其他一些过滤条件和设置来对我的数据进行分页。

以上是关于Firebase Firestore - 使用多个“数组包含”过滤数据的主要内容,如果未能解决你的问题,请参考以下文章

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

Flutter Firebase/Firestore 一次写入多个表

如何在firebase firestore中准确合并多个流

Vuejs + Firebase Storage 将多个图像上传到 Firebase Storage 并将所有 downloadURL 推送到 Firestore

Xcode 10 iOS firebase firestore SDK -- 多个命令在 Firebase 中产生 gRPCCertificates.bundle 错误

如何遍历 Firestore(React 和 Firebase Web 应用程序)中的多个用户子集合?