Querying multiple collections of firestore using array-contains-any

Tags: , , ,



I am trying query a number of documents in different collections with array-contains-any, by using Promise.all() to get all the documents at once.

I’ve checked whether the path is correct, whether the documents exist, whether the key exists in the document and everything is ok.

The querySnapshots and snapshots have no data retrieved. At some point of the log says: “_size”:0,”_materializedDocs”:null.

  let promises = []
  depIds.forEach(id => {
    const prodIds = groupedProducts[id].reduce((acc, val) => [...acc, val.prodId], []);
    console.log("All prodIds: ", prodIds, "; also id is: ", id);
    promise = admin.firestore()
      .collection('Products')
      .doc('Departments')
      .collection(id)
      .where('key', 'array-contains-any', prodIds)
      .get();
    promises.push(promise)

  })

  const querySnapshots = await Promise.all(promises);
  const snapshots = querySnapshots.map(doc => {
    console.log("docs: ", JSON.stringify(doc))
    return doc;
  });

So my questions are:

  1. Is it possible to query as above?

  2. How to get the actual data after the Promise.all() command?

I appreciate any help!

Answer

If the key field in your document is a string, you should be using the in operator.

The array-contains-any operator checks if any of the values you have given are in the array of the named field. As key is a string, this operator will always return no results.

To get all documents where key matches a given ID, while also ensuring that you can fetch more than 10 documents at a time, you can use:

/** splits array `arr` into chunks of max size `n` */
function chunkArr(arr, n) {
  if (n <= 0) throw new Error("n must be greater than 0");
  return Array
    .from({length: Math.ceil(arr.length/n)})
    .map((_, i) => arr.slice(n*i, n*(i+1)))
}

/** Fetch all given product IDs (if they exist) for the given department */
fetchDepartmentProducts(depId, prodIdList) {
  const prodIdListInBatches = chunkArr(prodIdList, 10);
  const departmentCollectionRef = admin.firestore()
    .collection('Products')
    .doc('Departments')
    .collection(depId);
  
  const promises = prodIdListInBatches.map((prodIdListBatch) => {
    return departmentCollectionRef
      .where('key', 'in', prodIdListBatch)
      .get();
  });
  
  return Promise.all(promises) // waits for all get requests
    .then((allQuerySnapshots) => {
      // flatten the found documents of the query snapshots into one array
      const allDocSnapshots = [];
      allQuerySnapshots.forEach((querySnapshot) =>
        allFoundDocSnapshots.push(...querySnapshot.docs)
      );
      return allDocSnapshots;
    });
}

Working this into your code, gives:

const promises = depIds.map((id) => {
  const prodIds = groupedProducts[id].map((product) => product.prodId);
  return fetchDepartmentProducts(id, prodIds);
}

const productsByDepartment = await Promise.all(promises);

productsByDepartment.forEach((docsInDeparment, i) => {
  console.log(`Found ${docsInDeparment.length} products in department #${depId[i]}.`);
});


Source: stackoverflow