1

Note: This is not a duplicate question, please read till the end and see the included image.

I have a nested object and an array field inside my collection/document in Firestore.

Main categories

  • Drinks
  • Snacks

Items for Drinks are

  • (Water, Energy, Milk, ...)

Items for Snacks are

  • (Chips, Biscuits, Corn, ..)

Image

The user may subscribe to both categories for multiple items with an expiration date:

  • Drinks->Energy
  • Drinks->Milk
  • Snack->Chips

I want to update the [expDate] field where [name] is equal to drinks and [type] is equal to [energy]

I have explored Firestore documentation more importantly compound queries in Cloud Firestore and read so many article(s) and questions on stackeoverflow but I couldn't find my answer, below is part of my code which I tr.

db.collection(this.dbName)
    .where("name", "==", "drinks")
    .where("subscriptions.data.type", "==", "energy")
    .get()
    .then((querySnapshot) => {
        querySnapshot.forEach((doc) => {
            let data = doc.data();
            if (data.email == email) {
                let subscriptions = data.subscriptions;
                subscriptions.forEach((subscription) => {
                    if (subscription.name == productName) {
                        let prodTypes = subscription.data;
                        prodTypes.forEach((prodType) => {
                            if (prodType.type == itemType) {
                                let docRef = fb.db.collection(this.dbName).doc(email);
                                fb.db
                                    .collection(this.dbName)
                                    .doc(email)
                                    .update(docRef, {
                                        subscriptions: [
                                            {
                                                data: [
                                                    {
                                                        expDate: expiration,
                                                        type: itemType,
                                                    },
                                                ],
                                                name: productName,
                                            },
                                        ],
                                    });
                            }
                        });
                    }
                });
            }
        });
    })
    .catch((error) => {
        console.log("Error getting documents: ", error);
    });

I don't get any console log result for the above query.

  • @mark-rotteveel, thank you for edit, but you deleted firebase keyword from question, I want that to be bolded and people with firebase expertise see the question. – Rafi Haidari Jan 13 '22 at 07:54
  • That is what the tag system is for. You should not just add tags to the title of your question. – Mark Rotteveel Jan 13 '22 at 08:08

2 Answers2

1

This query won't work:

db.collection(this.dbName)
    .where("name", "==", "drinks")
    .where("subscriptions.data.type", "==", "energy")

This returns documents that have a:

  • field name at their root with value "drinks" AND
  • have a field type nested under data nestedundersubscriptions**under the root** with the value"energy"`

Neither of those fields is present in the document you've shown, so the query won't return that document. If you add the fields under the root of the document, you'll see that the query returns it.


It looks like you're trying to return documents where the subscriptions array contains specific value in its items. For this you'll need to use the array-contains operator. This operation can test if an array field contains a specific complete value, like:

db.collection(this.dbName)
    .where("subscriptions", "array-contains", { ... })

But here you have to specify the entire object that must exist in the array. You can't check whether one property if the item exists with a specific value, and you also can't check for a nested array as you seem to have here.


The solution, as is usually the case when dealing with NoSQL databases, is to change/augment your data model to allow the use-case. Since you want to query for documents that have a specific name and type, add top-level fields for all names and types that exist in this document:

{
  names: ["drinks", "snacks"],
  types: ["energy", "water"]
  subscriptions: [
    ...
  ]
}

Now you can use (one of) the new top-level fields in your query:

db.collection(this.dbName)
    .where("names", "array-contains", "drinks")

You can't add a second array-contains clause though, as a query can only have one such clause in Firestore. So you'll have to filter the types in your application code after using a query to retrieve only documents that contain drinks.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Frank, thank you for your answer, main question is how to update the object where name is equal to drinks and type is equal to energy, scenario is as follow: Main categories are (Drinks, Snacks, ..) Items for Drinks are (Water, Energy, Milk, ...) Items for Snacks are (Chips, Biscuits, Corn, ..) The user may subscribe to both categories for multiple items with an expiration date: - Drinks->Energy - Drinks->Milk - Snack->Chips So my schema for inserting data is working properly, now I can't update expiration date of those items, please have a look the screenshot. – Rafi Haidari Jan 16 '22 at 15:23
  • You'll have to filter the `types` in your application code after using a query to retrieve only documents that contain `drinks`, then loop over the remaining documents and update each of them in turn (as you were already doing). – Frank van Puffelen Jan 16 '22 at 15:34
  • Frank, would you please write down the query and code it will be more helpful, I did what you said but I can't update the object, thank you in advance. – Rafi Haidari Jan 16 '22 at 16:19
  • If you're having trouble making the approach from my answer work, please provide an update at the bottom of your question that shows the code you wrote to implement it. – Frank van Puffelen Jan 16 '22 at 16:44
  • I am trying a combination of this and a new approach if I get the result I will post here, thank you for your inputs Frank. – Rafi Haidari Jan 16 '22 at 16:48
0

With my understanding firebase compound queries are not working for this kind of cases, I resolved the issue by changing the object with javascript map helper and update the document with return data of the function (changeExpDate), as follow:

changeExpDate(data, name, type, expDate) {
  data = [data];
  data.map((obj) => {
    let subscriptions = obj.subscriptions;
    for (var i = 0; i < subscriptions.length; i++) {
      let items = obj.subscriptions[i].data;
      for (var j = 0; j < items.length; j++) {
        if (obj.subscriptions[i].name == name) {
          if (items[j].type == type) {
            obj.subscriptions[i].data[j].expDate = expDate;
          }
        }
      }
    }
  });
  return data;
}

By considering your conditions, got with following code:

let v = this.changeExpDate(
    data, //The document objcet
    productName, //Product Name 
    itemType, //Item type
    expiration //Expiration date
);

try {
    fb.db
    .collection(this.dbName)
    .doc(email)
    .update(v[0])
    .then(function () {
        console.log("Updated!");
    })
    .catch(function (error) {
        console.error("Error Updating: ", error);
    });
} catch (error) {
    console.error(error);
}