programing

Firebase 쿼리 "IN" 제한을 10으로 설정할 수 있는 해결 방법이 있습니까?

abcjava 2023. 6. 5. 23:29
반응형

Firebase 쿼리 "IN" 제한을 10으로 설정할 수 있는 해결 방법이 있습니까?

크기가 10보다 큰 ID 배열을 가진 파이어베이스에 대한 쿼리가 있습니다.Firebase에는 한 세션에서 쿼리할 레코드 수에 제한이 있습니다.한 번에 10개 이상을 조회할 수 있는 방법이 있습니까?

[처리되지 않은 약속 거부:Firebase 오류:잘못된 쿼리입니다.'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();

(matchIdArray.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());
}

최상의 방법

목록을 각각 10개 항목의 하위 목록이 포함된 목록으로 변환한 다음 두 번째 목록을 통과하고 각 루프의 파이어베이스를 통해 쿼리를 수행합니다.

예:

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
    });

  });

해결 방법은 일반적으로 단일 "in" 쿼리와 함께 사용하는 어레이의 각 항목에 대해 하나의 쿼리를 만드는 것입니다.또는 배열에서 요청을 배치합니다.

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

당신은 위의 코드를 사용해야 할 것입니다.matchesIdArray모든 결과가 완료된 후 결과를 병합합니다.

이 제한을 해결하는 일반적인 방법 중 하나는 항목을 일괄적으로 검색한 다음 각 쿼리의 결과를 순차적으로 또는 병렬로 처리하는 것입니다.

또 다른 일반적인 해결 방법은 사용자의 개별 요청을 처리하기 위해 수십 개의 문서를 읽을 필요가 없는 방식으로 데이터를 모델링하는 것입니다.이 수를 줄일 수 있는 방법을 설명하기는 어렵지만, 이러한 개별 문서에서 필요한 데이터를 하나의 통합 문서로 복제해야 하는 경우가 많습니다.

이에 대한 예: 뉴스 사이트가 있고 5개 범주별로 최신 10개 기사 헤드라인을 표시해야 하는 경우 다음 작업을 수행할 수 있습니다.

  1. 각 문서에 대해 하나씩 50개의 개별 읽기를 수행합니다.
  2. 최신 10개 기사의 제목이 있는 문서를 만든 다음 5개의 문서만 읽으면 됩니다(범주당 1개).
  3. 5개의 카테고리 모두에 대해 최신 10개의 제목이 있는 문서를 만든 다음 해당 문서를 하나만 읽으면 됩니다.

이 마지막 두 시나리오에서는 데이터베이스에 쓰는 코드를 더 복잡하게 만듭니다. 이제 통합 문서도 작성해야 하기 때문입니다.그러나 그 대신 읽을 데이터가 훨씬 적어 비용이 절감되고 앱의 성능이 향상됩니다.이러한 유형의 트레이드오프는 NoSQL 데이터베이스를 사용할 때 매우 일반적이며, 이는 데이터 쓰기보다 읽기 수가 훨씬 많은 시나리오에서 사용되는 경향이 있습니다.

자세한 데이터 모델링 조언은 다음과 같습니다.

저는 같은 문제에 직면했고 타자기를 사용한 해결책은 다음과 같습니다.

  • 완전한 관찰을 위해 나는 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 ([])
}
  • 불완전한 관찰 가능한 경우 rxjs combineLatest를 사용했습니다.
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 ([])
}

'in' 쿼리와 일치하기 위해 10개 이상의 요소가 필요한 매우 일반적인 시나리오는 관련된 모든 사용자에게 그룹 채팅 대화를 표시하는 경우입니다.따라서 해당 채팅 ID와 일치할 수 있는 10개의 "구성원"으로 제한될 수 있지만, 제한되지 않으므로 논리를 뒤집고 "어레이 포함" 쿼리를 사용하는 것은 어떨까요?

의사 코드: "users"라는 이름의 모든 대화 문서에 배열을 만듭니다.관련된 모든 사용자의 ID를 나열한 후 현재 사용자의 ID와 비교합니다.

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

그러면 해당 그룹 대화의 모든 사용자에 대한 모든 대화 대화가 10명의 사용자로 제한되지 않고 표시됩니다.

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
});
  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의 경우, 이것은 약속을 사용한 저의 구현이었습니다.모든.

코드의 첫 번째 부분은 배열을 10개의 집합이 있는 절로 그룹화한 다음 Promise.all에서 10개의 각 집합을 실행합니다.결과 배열은 첫 번째 부분에 docid가 있고 두 번째 부분에 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"(여기서 In/whereNotIn)을 수정하기 위한 Flutter에 대한 Dart Implementation of Conrad Davis 답변입니다.

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>
      }
});

지금 테스트 중인데 in 연산자가 어레이에서 30개 항목을 지원합니다.

const ids = ['1'...'30'] const query = query(collection(db,'posts'))

N개의 쿼리를 만들기 위해 OR을 사용하는 것이 가능합니까?아마도 누군가가 예시를 사용하는 것을 보여줄 수 있습니다.

최근에 한도가 30으로 늘었습니다.분할과 모든 것의 문제는 페이지와 순서에 있습니다.분할을 통해 수행하면 어려운 테이크입니다.

언급URL : https://stackoverflow.com/questions/61354866/is-there-a-workaround-for-the-firebase-query-in-limit-to-10

반응형