在集合组查询中访问每个DocumentReference的父数据

时间:2020-03-24 03:52:11

标签: javascript firebase google-cloud-firestore

我有一个companies的集合,并且在每个公司文档中,我都有一个appointments的集合。我想在一个云函数中循环访问所有appointments中的所有companies,所以我在使用以下集合组查询:

db.collectionGroup('appointments')
    .get()
    .then((querySnapshot: any) => {
        querySnapshot.forEach((appointmentDoc: any) => {
            const appointment: Appointment = appointmentDoc.data();
            appointmentDoc.ref.parent.parent.get().then((companyDoc: any) => {
                const company: Company = companyDoc.data();
                ...
            });
        });
     });

如您所见,在每次迭代中,我也都获得了约会来自的公司的数据。这可行,但我担心性能。如果我有500个约会,那么此方法基本上不是对数据库进行501调用(对约会进行1次调用,然后获取所有500个约会的公司数据)吗?有没有一种更好的方法可以访问该父数据,所以我不会进行所有这些额外的调用?如果我能够以一种可扩展的方式做到这一点,那就太好了。

4 个答案:

答案 0 :(得分:1)

无法与db.collection.aggregate([ /** Filter docs based on criteria */ { $match: { passport: 123 } }, /** Transform fields into required form */ { $project: { name: "$nombre", lastname: "$apellido" } } ]) 集合中的文档同时获取父文档。

您唯一可以做的就是将文档ID分成10个批次,然后对它们进行IN query。但是我怀疑是否值得付出努力,因为电汇流量可能几乎相同。

请注意,尽管性能通常与通话次数并不线性相关,所以请在尝试对其进行优化之前进行测试。另请参见Google Firestore - how to get document by multiple ids in one round trip?

也:考虑一下为什么一次需要500个文档。通常,您将需要加载筛选的数据,这似乎还很多。有关Firestore中数据建模的一般提示,我推荐Getting to know Cloud Firestore的第一集。

答案 1 :(得分:1)

Firestore实际上不会根据查询数向您收费。它基于文档读取次数。因此,如果您有500个约会,那么您的代码将要读取1000个文档,因为对于每个约会文档,它都读取一次公司文档。

您可以做的只是对每个公司文档只读取一次总计,而对于该公司的每次约会都不会读取一次 。您可以使用以下方法在内存中维护缓存:

// cache of companies identified by their document ID
const companies: { [key: string]: Company } = {}

db.collectionGroup('appointments')
    .get()
    .then((querySnapshot: any) => {
        querySnapshot.forEach((appointmentDoc: any) => {
            const appointment: Appointment = appointmentDoc.data();
            const parentRef = appointmentDoc.ref.parent.parent
            const companyId = parentRef.id
            let company: Company
            if (companies[companyId]) {
                company = companies[companyId]
                // work with cached company here
            }
            else {
                parentRef.get().then((companyDoc: any) => {
                    company: Company = companyDoc.data();
                    companies[companyId] = company
                    // work with queried company here
                });
            }
        });
     });

尽管这是不完整的,因为内部查询仍然是异步的,并且将继续以约会迭代器可以运行的速度查询公司。您将必须以某种方式对内部查询进行序列化,或者按公司ID对约会进行分组,然后对这些组进行迭代,以免多次获取公司文档。

但是我希望您能想到使用内存缓存可以节省文档读取次数。

答案 2 :(得分:1)

我使用的是非常分层的结构,看起来会遇到类似的问题,但是...

...对于像Firestore这样的NoSQL数据库,您必须放弃DRY的SQL要求。如果数据是静态的(例如,您实际需要进行约会的任何“公司”数据),则您绝对可以并且应该复制该数据。

例如,您可以很简单地在约会文档中添加以下结构:

appointmentSchema = {
  ....
  ....
  company: {
    id: {string},
    name: {string},
    location: {string}
  }
}

是的,这使用存储空间。所以? Firestore通常不收取少量额外存储空间的费用,而收取新副本的费用 。由于此数据不会动态更改,因此在创建约会文档时将其添加到约会文档中效率更高。

文件提取应保留用于动态数据。

答案 3 :(得分:0)

另一点:文档的refPath是一个字符串,代表完全限定的'/'分隔的文档路径:

root/topcollection/topdocumentId/nextcollection/nextdocumentId/bottomcollection/bottomdocumentId

...,您可以直接解析此字符串以在文档路径的 up 任意位置找到集合名称和documentId。我也经常使用它。