【问题标题】:Why is it not possible to orderBy on different fields in Cloud Firestore and how can I work around it?为什么无法在 Cloud Firestore 中的不同字段上进行 orderBy,我该如何解决?
【发布时间】:2021-07-09 11:52:02
【问题描述】:

我在 firebase cloud firestore 中有一个名为“帖子”的集合,我想在我的网络应用程序上显示过去 24 小时内最喜欢的帖子。 发布文档有一个名为“like_count”(数字)的字段和另一个名为“time_posted”(时间戳)的字段。 我还希望能够限制结果以应用分页。

我尝试应用过滤器以仅获取过去 24 小时内发布的帖子,然后按“like_count”和“time_posted”对它们进行排序,因为我希望最喜欢的帖子首先出现。

postsRef.where("time_posted", ">", twentyFourHoursAgo)
        .orderBy("like_count", "desc")
        .orderBy("time_posted", "desc")
        .limit(10)

但是,我很快发现,无法过滤然后按两个不同的字段排序。 (请参阅Order and limit data with Cloud Firestore 文档的限制部分)

它说:

无效:范围过滤器和first orderBy在不同字段上

我曾考虑在前端按“like_count”对结果进行排序,但这无法正常工作,因为我没有所有文档。对于大量的日常帖子,获取所有文件是不可行的。

我是否缺少一个简单的解决方法或者我该如何解决这个问题?

【问题讨论】:

    标签: firebase google-cloud-firestore


    【解决方案1】:

    执行查询时,Firestore 必须能够以连续方式遍历索引。

    这个introduction video 有点过时(因为现在可以使用“in”运算符进行“OR”查询),但它确实很好地可视化了 Firestore 在运行查询时所做的事情。

    如果您的查询只是postsRef.orderBy("like_count", "desc").limit(10),Firestore 将加载它对降序"like_count" 的索引,提取前10 个条目并返回它们。

    要处理您的查询,它必须从降序的"like_count" 索引中提取一个条目,将其与您的"time_posted" 要求进行比较,然后丢弃它或将其添加到有效条目列表中。一旦它拥有所有最近的帖子,它就需要按照您的指定对结果进行排序。由于这些步骤不使用连续读取索引,因此不允许这样做。

    解决方案是根据最近的帖子建立自己的索引,然后从中提取结果。因为在客户端上这样做是不明智的,所以您应该使用云函数来为您完成工作。以下代码使用了Callable Cloud Function

    const MS_TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
    export getRecentTopPosts = function.https.onCall((data, context) => {
    
      // unless otherwise stated, return only 10 entries
      const limit = Number(data.limit) || 10;
    
      const postsRef = admin.firestore().collection("posts");
    
      // OPTIONAL CODE SEGMENT: Check Cached Index
    
      const twentyFourHoursAgo = Date.now() - MS_TWENTY_FOUR_HOURS;
      const recentPostsSnapshot = await postsRef
        .where("time_posted", ">", twentyFourHoursAgo)
        .get();
    
      const orderedPosts = recentPostsSnapshot.docs
        .map(postDoc => ({
          snapshot: postDoc,
          like_count: postDoc.get("like_count"),
          time_posted: postDoc.get("time_posted")
        })
        .sort((p1, p2) => {
          const deltaLikes = p2.like_count - p1.like_count; // descending sort based on like_count
          if (deltaLikes !== 0) {
            return deltaLikes;
          }
          return p2.time_posted - p1.time_posted; // descending sort based on time_posted
        });
    
      // OPTIONAL CODE SEGMENT: Save Cached Index
    
      return orderedPosts
        .slice(0, limit)
        .map(post => ({
          _id: post.snapshot.id,
          ...post.snapshot.data()
        }));
    })
    

    如果此代码预计会被许多客户端调用,您可能希望缓存索引以通过将以下段插入到上面的函数中来避免不断重建它。

    // OPTIONAL CODE SEGMENT: Check Cached Index
    
    if (!data.skipCache) { // allow option to bypass cache
      const cachedIndexSnapshot = await admin.firestore()
        .doc("_serverCache/topRecentPosts")
        .get();
      
      const oneMinuteAgo = Date.now - 60000;
    
      // if the index was created in the past minute, reuse it
      if (cachedIndexSnapshot.get("timestamp") > oneMinuteAgo) {    
        const recentPostMetadataArray = cachedIndexSnapshot.get("posts");
        const recentPostIdArray = recentPostMetadataArray
          .slice(0, limit)
          .map((postMeta) => postMeta.id)
        
        const postDocs = await fetchDocumentsWithId(postsRef, recentPostIdArray); // see https://gist.github.com/samthecodingman/aea3bc9481bbab0a7fbc72069940e527
        
        // postDocs is not ordered, so we need to be able to find each entry by it's ID
        const postDocsById = {};
        for (const doc of postDocs) {
          postDocsById[doc.id] = doc;
        }
        
        return recentPostIdArray
          .map(id => {
            // may be undefined if not found (i.e. recently deleted)
            const postDoc = postDocsById[id];
    
            if (!postDoc) {
              return null; // deleted post, up to you how to handle
            } else {
              return {
                _id: postDoc.id,
                ...postDoc.data()
              };
            }
          });
      }
    }
    
    // OPTIONAL CODE SEGMENT: Save Cached Index
    
    if (!data.skipCache) { // allow option to bypass cache
      await admin.firestore()
        .doc("_serverCache/topRecentPosts")
        .set({
          timestamp: Date.now(),
          posts: orderedPosts
            .slice(0, 25) // cache the maximum expected amount
            .map(post => ({
              id: post.snapshot.id,
              like_count: post.like_count,
              time_posted: post.time_posted,
            }))
        });
    }
    

    您可以添加到此功能的其他改进包括:

    • 字段掩码 - 即不返回发布文档的每个部分,而只返回标题,如计数、发布时间和作者。
    • 可变发布时间(而不是 24 小时)
    • 可变的最小喜欢计数
    • 按作者过滤

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-21
      • 1970-01-01
      • 1970-01-01
      • 2019-05-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多