繁体   English   中英

Firebase 查询“IN”限制为 10 是否有解决方法?

[英]Is there a workaround for the Firebase Query "IN" Limit to 10?

我有一个 firebase 的查询,它有一个大小大于 10 的 ID 数组。Firebase 对一个 session 中要查询的记录数有限制。有没有办法一次查询超过 10 个?

[未处理的 promise 拒绝:FirebaseError:无效查询。 'in' 过滤器支持值数组中最多 10 个元素。]

https://cloud.google.com/firestore/docs/query-data/queries

在此处输入图像描述

  let query = config.db
    .collection(USER_COLLECTION_NAME)
    .where("id", "in", matchesIdArray);
  const users = await query.get();

(matchesIdArray.length 需要是无限的)

我发现这对我来说效果很好,不需要进行尽可能多的查询(循环和请求,每批 10 个)。

export async function getContentById(ids, path) {
  // don't run if there aren't any ids or a path for the collection
  if (!ids || !ids.length || !path) return [];

  const collectionPath = db.collection(path);
  const batches = [];

  while (ids.length) {
    // firestore limits batches to 10
    const batch = ids.splice(0, 10);

    // add the batch request to to a queue
    batches.push(
      collectionPath
        .where(
          firebase.firestore.FieldPath.documentId(),
          'in',
          [...batch]
        )
        .get()
        .then(results => results.docs.map(result => ({ /* id: result.id, */ ...result.data() }) ))
    )
  }

  // after all of the data is fetched, return it
  return Promise.all(batches)
    .then(content => content.flat());
}

您唯一的解决方法是对数组中的每个项目进行一次查询,您通常会使用单个“in”查询。 或者,批处理数组中的请求。

  let query = config.db
    .collection(USER_COLLECTION_NAME)
    .where("id", "==", matchesIdArray[0]);
  const users = await query.get();

您必须在matchesIdArray数组上的循环中使用上述代码,并在它们全部完成后合并结果。

解决此限制的一种常见方法是批量检索项目,然后按顺序或并行处理每个查询的结果。

另一个常见的解决方法是 model 以一种不需要您阅读数十个文档来处理用户的单个请求的方式来处理您的数据。 很难说如何减少这个数字,但它通常涉及将您需要的数据从这些单独的文档复制到单个聚合文档中。

举个例子:如果你有一个新闻网站并且需要为 5 个类别中的每一个显示最新的 10 篇文章标题,你可以这样做:

  1. 单独读取 50 次,每个文档读取一次。
  2. 为最新的 10 篇文章创建一个带有标题的文档,然后只需阅读 5 个文档(每个类别一个)。
  3. 创建一个包含所有 5 个类别的最新 10 个标题的文档,然后只需要阅读那个文档。

在最后两个场景中,您正在使写入数据库的代码更加复杂,因为它现在也需要编写聚合文档。 但作为回报,您要读取的数据要少得多,这会降低成本并提高应用程序的性能。 在使用 NoSQL 数据库时,这种类型的权衡非常常见,这些数据库往往用于数据读取多于写入的场景。

有关更多数据建模建议,我建议:

最佳方法

将列表转换为包含每个子列表 10 个项目的列表。 然后 for 循环遍历第二个列表并在每个循环中通过 firebase 查询。

例子:

List<String> phoneNumbers = ['+12313','+2323','1323','32323','32323','3232', '1232']; //CAN BE UPTO 100 or more

将电话号码转换为每个 10 项的子列表

List<List<String>> subList = [];
for (var i = 0; i < phoneNumbers.length; i += 10) {
    subList.add(
        phoneNumbers.sublist(i, i + 10> phoneNumbers.length ? phoneNumbers.length : i + 10));
}

现在运行 FIREBASE 查询

subList.forEach((element) {
      firestore
          .collection('Stories')
          .where('userPhone', whereIn: element)
      .get()
      .then((value) {
    
    value.docs.forEach((snapshot) {
      //handle the list
    });

  });

我遇到了同样的问题,我使用 typescript 的解决方案是:

  • 对于完整的 observables,我使用了 rxjs forkJoin
getPagesByIds(ids: string[]): Observable<Page[]> {
        ids = [...ids];
        if (ids.length) {
            let observables: Observable<Page[]>[] = [];
            while (ids.length) {
                let observable = this.afs.collection<Page>(PAGE, ref => ref.where('id', 'in', ids.splice(0, 10))).get().pipe(map(pages => pages.docs.map(page => page.data())))
                observables.push(observable)
            }
            return combineLatest(observables).pipe(map(pages => pages.flat(1)))
        }
        return of ([])
}
getPagesByIds(ids: string[]): Observable<Page[]> {
        ids = [...ids];
        if (ids.length) {
            let observables: Observable<Page[]>[] = [];
            while (ids.length) {
                let observable = this.afs.collection<Page>(PAGE, ref => ref.where('id', 'in', ids.splice(0, 10))).get().pipe(map(pages => pages.docs.map(page => page.data())))
                observables.push(observable)
            }
            return combineLatest(observables).pipe(map(pages => pages.flat(1)))
        }
        return of ([])
}

根据 firebase 文档,它仅在 where 字段中最多支持 10 个 id,要查询超过 10 个元素,我们需要单独查询每个文档或将数组拆分为 10 个 id 的块。

用于单独查询每个项目。 检查下面的代码,

let usersPromise = [];

usersIds.map((id) => {
    usersPromise.push(firestore.collection("users").doc(id).get());
});
    
Promise.all(usersPromise).then((docs) => {
    const users = docs.map((doc) => doc.data());

    // do your operations with users list
});

使用 Firebase 版本 9(2021 年 12 月更新):

使用此代码,您可以对超过 10 个 ID 的数组进行多次查询,然后将返回的多个快照的数组转换为多个文档的数组,当然,此代码适用于10 或小于 10 的数组身份证件 此外, “await Promise.all()”等待多个结果如果至少一个结果失败,则所有结果都失败

import { query, collection, where, getDocs } from "firebase/firestore";

let usersIDs = [
  'id1', 'id2', 'id3', 'id4', 'id5', 'id6', 'id7', 'id8', 'id9', 'id10',
  'id11', 'id12', 'id13'
];

let queries = [];

for(let i = 0; i < usersIDs.length; i += 10) {
  queries.push(query(
    collection(db, "users"),
    where("id", "in", usersIDs.slice(i, i + 10)),
  ));
}

let usersDocsSnaps = [];

for(let i = 0; i < queries.length; i++) {
  usersDocsSnaps.push(getDocs(queries[i]));
}

usersDocsSnaps = await Promise.all(usersDocsSnaps);

let usersDocs = [...new Set([].concat(...usersDocsSnaps.map((o) => o.docs)))];

console.log(usersDocs); // 13 documents

如果集合 "users" 的文档 ID"id1", "id2", "id3", ...,您可以使用"where() " 中的 "documentId()"获取用户文档

import { query, collection, where, getDocs, documentId } from "firebase/firestore";
                                           // Here ↑↑↑

for(let i = 0; i < usersIDs.length; i += 10) {
  queries.push(query(
    collection(db, "users"),
    where(documentId(), "in", usersIDs.slice(i, i + 10)),
  ));   // Here ↑↑↑
}
  async getInBatch(query, key, arr) {

      const promises = new Array(Math.ceil(arr.length / 10))
          .fill('')
          .map((_, i) => arr.slice(i * 10, (i + 1) * 10))
          .map((i) => query.where(key, 'in', i).get());
      const batches = await Promise.all(promises);
      const docsData = [];
      batches.forEach((snapshot) => {
          snapshot.forEach((doc) => {
              docsData.push([doc.id, doc.data()]);
          });
      });
      return docsData;
  }
  
  const data = await this.getInBatch(db.collection('collection'),
    'id',
    [1,2,3]
  );

对于 javascript,这是我的承诺实现,使用 Promise.all

代码的第一部分将一个数组分组到包含 10 个集合的 in 子句中,然后执行 Promise.all 中的每个 10 个集合。 结果数组以元组的形式返回,第一部分是 docid,第二部分是文档数据。

在这里添加我自己的解决方案,因为我对当前的答案并不完全满意:

docsSnap$(queryFn?: QueryFn): Observable<T[]> {
  return this.firestore.collection<T>(`${this.basePath}`, queryFn)
    .get()
    .pipe(map((i: QuerySnapshot<T>) => i.docs
      .map((d: QueryDocumentSnapshot<T>) => d.data())));
}
getIdChuncks(allIds: Array<string>): Array<Array<string>> {  
  const idBatches = [];
  while (allIds.length > 0)
    idBatches.push(allIds.splice(0, 10));
  return idBatches;
}
processBatches(allIds: string[]) {
  const batches$ = this.getIdChuncks(allIds)
    .map(ids => this
    .docsSnap$(ref => ref.where('id', 'in', ids)));
  this.items$ = forkJoin(batches$)
    .pipe(map(arr => arr.flat()));
}  

需要超过 10 个元素来匹配“in”查询的一个非常常见的场景是,在向所有相关用户显示群聊对话时,没有其他人。 因此,虽然您可能受限于 10 个可以与该聊天 ID 匹配的“成员”,但为什么不颠倒逻辑并使用“数组包含”查询,因为它不受限制。

您的伪代码:在每个名为“usersInvolved”的聊天文档上创建一个数组,列出每个相关用户的 ID,然后将其与当前用户的 ID 进行比较。

db
.collection('chats')
.where('usersInvolved', 'array-contains', myUserID)
.onSnapshot(...)

这将显示该组对话中所有用户的所有聊天对话,没有 10 个用户的限制。

这是 Dart Conrad Davis 的 Flutter 答案的实施,用于修复 firestore 查询“IN”(whereIn/whereNotIn)限制为 10

Future<List<QueryDocumentSnapshot<Map<String, dynamic>>>> getContentById(
      {required List<Object?> ids,
      required String path,
      required String field,
      bool whereIn = false}) {
    var collectionPath = store.collection(path);
    var batches = <Future<List<QueryDocumentSnapshot<Map<String, dynamic>>>>>[];

    var batch = ids;
    while (ids.length > 0) {
      // firestore limits batches to 10
      var end = 10;
      if (ids.length < 10) end = ids.length;
      batch = ids.sublist(0, end);
      ids.removeWhere((element) => batch.contains(element));

      if (whereIn) {
        // add the batch request to to a queue for whereIn
        batches.add(collectionPath
            .where(field, whereIn: [...batch])
            .get()
            .then((results) => results.docs));
      } else {
        // add the batch request to to a queue for whereNotIn
        batches.add(collectionPath
            .where(field, whereNotIn: [...batch])
            .get()
            .then((results) => results.docs));
      }
    }

    // after all of the data is fetched, return it
    return Future.wait(batches)
        .then((content) => content.expand((i) => i).toList());
  }

像这样使用:

getContentById(ids:["John", "Doe", "Mary"], path: "contacts", field: 'name', whereIn:true)
        .then((value) async {
      for (var doc in value) {
        var d = doc.data();
        //... Use your data Map<String, dynamic>
      }
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM