Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase Firestore - Filter data with multiple 'array-contains'

I am struggling to find good material on best practices for filtering data using firebase firestore. I want to filter my data based on the categories selected by the user. I have a collection of documents stored on my firestore database and each document have an array which has all the appropriate categories for that single document. For the sake of filtering, I'm keeping a local array with a user's preferred categories as well. All I want to do is to filter the data based on the user's preferred categories.

firestore categories field

consider I have the user's preferred categories stored as an array of strings ( ["Film", "Music"] ) .I was planning on using firestore's 'array-contains' method like

db.collection(collectioname)
.where('categoriesArray', 'array-contains', ["Film", "Music"])

Later I found out that I can't use 'array-contains' against an array itself and after investigating on this issue, I decided to change my data structure as mentioned here.

categories changed to Map

Once I changed the categories from an array to map, I thought I could use multiple where conditions to filter the documents

let query = db.collection(collectionName)
      .where(somefield, '==', true)

this.props.data.filterCategories.forEach((val) => {
  query = query.where(`categories.${val}`, '==', true);
});

query = query
        .orderBy(someOtherField, "desc")
        .limit(itemsPerPage)

const snapshot = await query.get()

Now problem number 2, firebase requires to add indexes for compound queries. The categories I have saved within each document is dynamic and there's no way I can add these indexes in advance. What would be the ideal solution in such cases? Any help would be deeply appreciated.

like image 932
nithinpp Avatar asked Jun 11 '19 13:06

nithinpp


2 Answers

This is a new feature of Firebase JavaScript SDK launched at November 7, 2019:

  • Version 7.3.0 - November 7, 2019

  • array-contains-any

"array-contains-any operator to combine up to 10 array-contains clauses on the same field with a logical OR. An array-contains-any query returns documents where the given field is an array that contains one or more of the comparison values"

citiesRef.where('regions', 'array-contains-any',
    ['west_coast', 'east_coast']);
like image 174
narcello Avatar answered Sep 17 '22 18:09

narcello


Instead of iterating through each category that you wish to query and appending clauses to a single query object, each iteration should be its own independent query. And you can keep the categories in an array.

<document>
    - itemId: abc123
    - categories: [film, music, television]

If you wish to perform an OR query, you would make n-loops where each loop would query for documents where array-contains that category. Then on your end, you would dedup (remove duplicates) from the results based on the item's identifier. So if you wanted to query film or music, you would make 2 loops where the first iteration queried documents where array-contains film and the second loop queried documents where array-contains music. The results would be placed into the same collection and then you would simply remove all duplicates with the same itemId.

This also does not pose a problem with the composite-index limit because categories is a static field. The real problem comes with pagination because you would need to keep a record of all fetched itemId in case a future page of results returns an item that was already fetched and this would create an O(N^2) scenario (more on big-o notation: https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/). And because you're deduping locally, pagination blocks as the user sees them are not guaranteed to be even. If each pagination block is set to 25 documents, for example, some pages may end up displaying 24, some 21, others 14, depending on how many duplicates were removed from each block.

like image 36
liquid LFG UKRAINE Avatar answered Sep 20 '22 18:09

liquid LFG UKRAINE