Google Firestore-如何在一次往返中通过多个ID获取文档?


98

我想知道是否有可能在一次到Firestore的往返(网络调用)中通过ID列表获取多个文档。


4
您似乎以为往返导致您的应用程序出现性能问题。我不会这么认为。在这种情况下,Firebase具有良好的执行历史,因为它可以流水线处理请求。尽管我没有检查Firestore在这种情况下的行为,但我很想在假设性能问题存在之前先查看其性能证明。
弗兰克·范·普菲伦

1
比方说,我需要的文件abc做一些事情。我在单独的请求中同时请求所有三个。a需要100毫秒,b150 毫秒和c3000 毫秒。结果,我需要等待3000毫秒才能完成任务。将会是max他们的。当要获取的文档数量很大时,这将更具风险。取决于网络状态,我认为这可能会成为问题。
Joon

1
难道不是一次全部发送它们会SELECT * FROM docs WHERE id IN (a,b,c)花费相同的时间吗?我看不出有什么区别,因为连接仅建立一次,其余的通过管道建立。该时间(在初始建立连接之后)是所有文档的加载时间+ 1次往返,两种方法都相同。如果您的行为有所不同,可以共享一个样本(如我的链接问题所示)吗?
弗兰克·范·普菲伦

我想我失去了你。当您说它正在流水线化时,是否表示Firestore在一次往返数据库的过程中自动将查询分组并发送查询到他们的服务器?
Joon

仅供参考,往返是指从客户端到数据库的一个网络调用。我在问Firestore是否将多个查询自动分组为一次往返,或者将多个查询并行执行为多次往返。
Joon

Answers:


87

如果您位于Node中:

https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L701

/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
*   console.log(`First document: ${JSON.stringify(docs[0])}`);
*   console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/

这是专门针对服务器SDK

更新: “ Cloud Firestore [客户端sdk]现在支持IN查询!”

https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])


28
对于希望使用动态生成的文档引用数组调用此方法的任何人,您都可以这样操作:firestore.getAll(... arrayOfReferences).then()
Horea

1
对不起,@ KamanaKisinga ...近一年来我没有做过任何火力发烧的工作,但现在真的无济于事(嘿,我实际上是一年前今天发布了这个答案!)
Nick Franceschina

2
客户端SDK现在也提供了此功能。参见jeodonara的示例示例:stackoverflow.com/a/58780369
Frank van Puffelen

4
警告:in过滤器当前限制为10个项目。因此,当您要投入生产时,您可能会发现它毫无用处。
马丁·克雷默

6
实际上您需要使用firebase.firestore.FieldPath.documentId()而不是'id'
Maddocks

20

他们刚刚通过https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html宣布了此功能 。

现在您可以使用类似的查询,但是请注意输入大小不能大于10。

userCollection.where('uid', 'in', ["1231","222","2131"])


有一个whereIn查询而不是where。而且我不知道如何设计属于特定集合的文档ID列表中的多个文档查询。请帮忙。
编译错误

16
@Compileerrorend你可以试试吗?db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara

谢谢,尤其是firebase.firestore.FieldPath.documentId()
切尔诺夫

10

否,目前无法使用Cloud Firestore SDK批处理多个读取请求,因此无法保证您可以一次读取所有数据。

但是,正如弗兰克·范·普菲伦(Frank van Puffelen)在上述评论中所说,这并不意味着获取3个文档的速度将比获取一个文档的速度慢3倍。最好在这里得出结论之前进行自己的测量。


1
问题是我想在迁移到Firestore之前了解Firestore性能的理论限制。我不想迁移,然后意识到这对我的用例来说还不够好。
Joon

2
嗨,这里也有cose的提议。假设我已经存储了所有朋友ID的列表,并且数量为500。我可以用1次读取的成本获得该列表,但是为了显示其Name和photoURL,我需要进行500次读取。
Tapas Mukherjee

1
如果您要读取500个文档,则需要进行500次读取。如果将所有500个文档中所需的信息组合到一个额外的文档中,则只需读取一次即可。所谓的数据重复排序在包括Cloud Firestore在内的大多数NoSQL数据库中都是很正常的。
弗兰克·范·普菲伦

1
@FrankvanPuffelen例如,在mongoDb中,您可以使用像stackoverflow.com/a/32264630/648851这样的ObjectId 。
Sitian Liu

1
就像@FrankvanPuffelen所说的那样,数据复制在NoSQL数据库中非常普遍。在这里,您必须问自己,需要多久读取一次这些数据,以及它们需要如何更新。如果您确实存储了500个用户信息,则假设他们的姓名+照片+身份证,您可以一次阅读。但是,如果您需要它们是最新的,则每次用户更新其名称/照片时,可能都必须使用云功能来更新这些引用,因此要运行云功能+进行一些写操作。没有“正确” /“更好”的实现,它仅取决于您的用例。
schankam

9

在实践中,您将像这样使用firestore.getAll

async getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    const users = await this.firestore.getAll(...refs)
    console.log(users.map(doc => doc.data()))
}

或使用promise语法

getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}

2
这实际上应该是选择的答案,因为它可以让您使用10个以上的ID
sshah98

9

您可以使用如下功能:

function getById (path, ids) {
  return firestore.getAll(
    [].concat(ids).map(id => firestore.doc(`${path}/${id}`))
  )
}

可以使用一个ID来调用它:

getById('collection', 'some_id')

或ID数组:

getById('collection', ['some_id', 'some_other_id'])

5

当然,最好的方法是在Cloud Function中实现Firestore的实际查询?从客户端到Firebase只会有一个往返呼叫,这似乎正是您所要的。

无论如何,您确实想保持所有数据访问逻辑,例如服务器端。

在内部,对Firebase本身的调用次数可能是相同的,但它们都是通过Google的超快速互连而不是外部网络进行的,并结合Frank van Puffelen解释的流水线,您应该从中获得出色的性能这种方法。


3
在逻辑复杂的某些情况下,将实现存储在Cloud Function中是正确的决定,但在您只想合并具有多个ID的列表的情况下,可能就不合适。您失去的是常规调用的客户端缓存和标准化的返回格式。使用此方法时,这导致的性能问题比某些情况下在我的应用程序中解决的问题多。
耶利米

3

如果使用颤振,则可以执行以下操作:

Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();

这将返回一个Future,List<DocumentSnapshot>其中包含您可以根据需要进行迭代的内容。


2

使用Android SDK在Kotlin中执行以下操作的方法如下。
不一定要进行一次往返,但是它确实可以有效地对结果进行分组并避免了许多嵌套的回调。

val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }

Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
    //Do what you need to with the document list
}

请注意,获取特定文档比获取所有文档并过滤结果要好得多。这是因为Firestore向您收取查询结果集的费用。


1
效果很好,正是我想要的!
Georgi

0

目前,这在Firestore中似乎还不可能。我不明白为什么亚历山大的答案会被接受,他提出的解决方案只是返回“用户”集合中的所有文档。

根据您需要执行的操作,您应该研究复制所需显示的相关数据,并仅在需要时请求完整的文档。


0

您能做的最好的事情就是使用Promise.all您的客户端,然后必须等待.all读取才能继续。

重复读取并让它们独立解析。在客户端,这可能归结为具有多个进度加载程序映像独立解析为值的UI。但是,这比冻结整个客户端直到.all读取解析更好。

因此,立即将所有同步结果转储到视图中,然后让异步结果在解析时分别进入。这似乎有些微不足道,但是如果您的客户的互联网连接状况不佳(例如我目前在这家咖啡店),那么将整个客户体验冻结几秒钟可能会导致“此应用很烂”的体验。


2
它是异步的,有很多使用案例可供使用Promise.all……它不一定必须“冻结”任何内容–在执行有意义的操作之前,您可能需要等待所有数据
Ryan Taylor

在某些情况下,您确实需要加载所有数据,因此Promise.all完全需要等待(例如带有适当消息的微调器,无需“冻结”您说的任何UI)。 。这实际上取决于您在这里生产什么样的产品。我个人认为,这些评论是无关紧要的,其中不应有任何“最佳”字眼。这实际上取决于一个人可能面对的每一种不同用例,以及您的应用对用户的影响。
schankam

0

希望对您有帮助,对我有用。

getCartGoodsData(id) {

    const goodsIDs: string[] = [];

    return new Promise((resolve) => {
      this.fs.firestore.collection(`users/${id}/cart`).get()
        .then(querySnapshot => {
          querySnapshot.forEach(doc => {
            goodsIDs.push(doc.id);
          });

          const getDocs = goodsIDs.map((id: string) => {
            return this.fs.firestore.collection('goods').doc(id).get()
              .then((docData) => {
                return docData.data();
              });
          });

          Promise.all(getDocs).then((goods: Goods[]) => {
            resolve(goods);
          });
        });
    });
  }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.