【问题标题】:How to limit collectionGroup query results to an ancestor?如何将集合组查询结果限制为祖先?
【发布时间】:2020-06-21 09:02:40
【问题描述】:

我一直在阅读博客文章 https://firebase.googleblog.com/2019/06/understanding-collection-group-queries.html 以更好地理解 collectionGroup 查询。

尽管如此,我仍然有一个问题:如何将结果限制为特定的祖先。让我自己解释一下。

想象我有companies 制造carstyres。我们有不同品牌的tyres,用于不同的cars。最后,我们有一个多对多的关系。我知道我不应该在 NoSQL 世界中使用这个术语,但我称狗为狗 :-)

无论如何,我的问题如下:如果某个特定tyre 品牌(比如米其林)的company A 短缺,您需要将此tyre 标记为缺货。我会考虑运行一个 collectionGroup 查询,例如:

db.collectionQuery("tyre")
  .where("brand", "==", "Michelin")
  .get()
  .then(function (querySnapshot) {
    // update flag accordingly
  })

但这会更新其他companies 的库存。

我的问题是:如何缩小 collectionGroup 查询结果的范围,以便只更新 A 公司的 tyres 信息?

我可以将公司 A docRef 包含在 tyres 集合中,并使用 where() 缩小结果范围。这似乎是一种有效的方法。虽然,它将是顶级集合和子集合之间的混合。这是最佳做法吗?

更新

实际上,我正在效仿餐厅的例子,将我的手放在火力基地/火力店上。一家餐厅可以有多个菜单。一个菜单可以有多个项目。项目可以重复使用,因此出现在多个菜单中。

collection('restaurants').doc(..).collection('menus').doc(..).collection('items')

我认为这是构建数据的最佳方式(相对于项目的顶级集合)。但是像咖啡这样的东西很容易在多家餐厅的多个菜单中找到。如果一家餐厅的咖啡短缺,我如何使用以下方式更新该特定餐厅的咖啡项目:

db.collectionQuery("items")
  .where("name", "==", "Coffee")
  .get()
  .then(function (querySnapshot) {
    // set available = false
  })

【问题讨论】:

  • 您能否提供有关您的数据库结构(集合和子集合及其父/子关系的确切列表)以及您要在其中执行查询的上下文的更多详细信息?
  • 嗨@RenaudTarnec 我已经更新了问题以避免评论大小限制
  • "如何将结果限制为特定的祖先?"你不能。集合组查询当前读取具有给定名称的所有集合。没有办法将它们限制在特定路径上。典型的解决方法是以不同的方式命名集合以允许用例。另见stackoverflow.com/a/58977074stackoverflow.com/a/61034013stackoverflow.com/a/56224106

标签: javascript firebase google-cloud-firestore


【解决方案1】:

如果一家餐厅的咖啡短缺,我该如何更新咖啡 该特定餐厅的项目?

通过使用 collectionGroup 查询,您可以这样做:

  db.collectionQuery('items')
    .where('name', '==', 'Coffee')
    .get()
    .then(function (querySnapshot) {
      querySnapshot.forEach(function (doc) {
        const itemQuantity = doc.data().itemQuantity;
        if (itemQuantity === 0) {
          const restaurantRef = doc.ref.parent.parent.parent.parent;
          return restaurantRef.update( {....})
        }
      });
    });

通过交替使用DocumentReferenceCollectionReferenceparent 属性。


但是,如果您有很多餐馆,这可能不是最有效且经济实惠的方式,因为您的 collectionGroup 查询将返回大量记录。

一种更有效的方法是保留一组 counters 并通过 Firestore 侦听器或 Cloud Functions 观察它们。


最后,请注意重要的一点:您写的是“一个菜单可以有多个项目。项目可以重复使用,因此出现在多个菜单中”。注意items文件在

collection('restaurants').doc('r1').collection('menus').doc('m1').collection('items')

collection('restaurants').doc('r1').collection('menus').doc('m2').collection('items')

是完全不同的文档。这与 SQL 世界不同,在 SQL 世界中,一张表的不同记录可以指向另一张表的相同记录。


结论:您很可能每个restaurant 都有一个itemsStock 集合,并且每次“消费/订购”其中一项时,您可以使用FieldValue.increment(-1) 减少其数量。

换句话说,我建议将组成菜单的项目集合与包含项目计数器的项目集合(即itemsStock 集合)分开。第一个专门用于菜单项选择,第二个专门用于管理餐厅的库存。当客人/客户选择/订购物品时,您只会减少持有物品柜台的集合。


根据您的评论更新:

如果您想更新餐厅所有菜单中的所有“烤宽面条”项目(例如添加成分,正如您在评论中提到的),一个非常常见的方法确实是修改所有相应的文档(这在 NoSQL 世界中称为数据复制)。

您将使用我答案顶部的确切代码:您查询餐厅所有菜单中的所有“烤宽面条”项目文档并更新它们。您可以通过云功能触发此过程,该功能将“监视”您具有参考项目的主集合:每次更改此集合的文档(即项目)时,您都会更新菜单中的所有相似/对应项目文档子集合。

【讨论】:

  • 我同意你的最后一点,我知道这是两个不同的集合。但从概念上讲,从老板的观点来看,周末和工作日菜单中的千层面是同一个千层面。我会在 NoSQL 中复制千层面,但是对一个千层面的操作(放置instock: false 标志)应该在所有千层面上复制。因此,我原来的问题。我确实相信保留子集合是最好的结构方法。我需要在每个项目中的餐厅 docRef。那样的话,如果我可以通过 collectionGroup 更新所有烤宽面条信息,但对于特定的餐厅(如果我说的有道理:-))
  • “对一个千层面的操作(放入库存:错误标志)应该在所有千层面上复制”-> 这就是为什么我建议将组成菜单的项目集合和包含的项目集合分开物品计数器。第一个专门用于菜单项选择,第二个专门用于管理餐厅的库存。当客人/客户选择/订购商品时,您只会减少持有商品柜台的收藏。
  • 这种方法似乎非常适用于特定情况。但是,如果我在食谱中添加坚果,我需要更新所有千层面的过敏信息。您是否也将此类信息保存在单独的集合中?
  • 这几乎是一个新问题 :-) 请参阅我的答案的更新。如果您认为我的整个回答对您有所帮助,请点赞,请参阅stackoverflow.com/help/someone-answers。谢谢。
【解决方案2】:

我可以在轮胎集合中包含公司 A docRef 并使用 where() 来缩小结果范围。这似乎是一种有效的方法。虽然,它将是顶级集合和子集合之间的混合。这是最佳做法吗?

这是一种常见的方法,因为在集合组查询中过滤文档的唯一方法是使用文档的字段。您不能将文档路径中的任何内容用作过滤器。为了方便查询,在 NoSQL 类型的数据库中复制数据是很常见的。

但是,如果您想将查询限制在子集合中,您可能不希望拥有与子集合同名的顶级集合。

【讨论】:

  • 确实,我只想要子集合items 持有他们祖先restaurants 的引用。这样我就可以在items 上使用restaurant ref 上的where 条件执行collectionGroup。我觉得重复这样的“概念关系”信息是错误的,因为items 嵌套在restaurant 中,因此已经具有内在关系。我想我希望得到类似db.forAncestor(restaurantRef).collectionGroup("items") :-D 但提醒“在 NoSQL 中重复数据很常见”也对我有用 :)
猜你喜欢
  • 2023-03-03
  • 1970-01-01
  • 2012-01-18
  • 2017-10-13
  • 1970-01-01
  • 2015-05-07
  • 1970-01-01
  • 1970-01-01
  • 2012-04-23
相关资源
最近更新 更多