Firestore - How to Get Around Array "Does-Not-Contain" Queries

FireStore - how to get around array does-not-contain queries

There is an accepted and good answer, however, it doesn't provide a direct solution to the question so here goes... (this may or may not be helpful but it does work)

I don't know exactly what your Firestore structure is so here's my assumption:

cards
card_id_0
usersWhoHaveSeenThisCard
0: uid_0
1: uid_1
2: uid_2
card_id_1
usersWhoHaveSeenThisCard
0: uid_2
1: uid_3
card_id_2
usersWhoHaveSeenThisCard
0: uid_1
1: uid_3

Suppose we want to know which cards uid_2 has not seen - which in this case is card_id_2

func findCardsUserHasNotSeen(uidToCheck: String, completion: @escaping ( ([String]) -> Void ) ) {
let ref = self.db.collection("cards")

ref.getDocuments(completion: { snapshot, err in
if let err = err {
print(err.localizedDescription)
return
}

guard let docs = snapshot?.documents else {
print("no docs")
return
}
var documentsIdsThatDoNotContainThisUser = [String]()
for doc in docs {
let uidArray = doc.get("usersWhoHaveSeenThisCard") as! [String]
let x = uidArray.contains(uidToCheck)
if x == false {
documentsIdsThatDoNotContainThisUser.append(doc.documentID)
}
}
completion(documentsIdsThatDoNotContainThisUser)
})
}

Then, the use case like this

func checkUserAction() {
let uid = "uid_2" //the user id to check
self.findCardsUserHasNotSeen(uidToCheck: uid, completion: { result in
if result.count == 0 {
print("user: \(uid) has seen all cards")
return
}
for docId in result {
print("user: \(uid) has not seen: \(docId)")
}
})
}

and the output

user: uid_2 has not seen: card_id_2

This code goes through the documents, gets the array of uid's stored within each documents usersWhoHaveSeenThisCard node and determines if the uid is in the array. If not, it adds that documentID to the documentsIdsThatDoNotContainThisUser array. Once all docs have been checked, the array of documentID's that do not contain the user id is returned.

Knowing how fast Firestore is, I ran the code against a large dataset and the results were returned very quickly so it should not cause any kind of lag for most use cases.

Firestore - Possible to query by array-not-contains?

There is no way to query Firestore for documents that don't have a certain field or value. It's not "very difficult", but simply not possible. To learn more on why that is, see:

  • Firestore get documents where value not in array?
  • Firestore: how to perform a query with inequality / not equals
  • The Get to know Cloud Firestore episode on how Firestore queries work.

Your workaround is possible, and technically not even very complex. The only thing to keep in mind is that you'll need to load all artists. So the performance will be linear to the number of artists you have. This may be fine for your app at the moment, but it's something to definitely do some measurements on.

Typically my workaround is to track not what releases were pushed to a user, but when the last push was sent to a user. Say that a release has a "notifications sent timestamp" and each user has a "last received notifications timestamp". By comparing the two you can query for users who haven't received a notification about a specific release yet.

The exact data model for this will depend on your exact use-case, and you might need to track multiple timestamps for each user (e.g. for each artist they follow). But I find that in general I can come up with a reasonable solution based on this model.

Firestore array-not-contains alternative solution

You're going to have a real hard time with this because Firestore can only give you documents where you know something about the contents of that document. That's how indexes work - by recording data present in the document. Indexes don't track data not present in a document because that's basically an infinite amount of data.

If you are trying to track documents seen by the user, you would think to mark the document as "seen" using a boolean per user, or tracking the document ID somewhere. But as you can see, you can only query for documents that the user has seen, because that's the data present in the system.

What you can do is query for all documents, then query for all the documents the user has seen, then subtract the seen documents from all documents in order to get the unseen documents. But this probably doesn't scale in a way you'd like. (It's essentially the same problem with Firestore indexes not being able to surface documents without some known data present. Firestore won't do the equivalent of a SQL table scan, since that would be a lot of reads you'd have to pay for.)

You can kind of fake it by making sure there is a creation timestamp in each document, and record for each user the timestamp of the most recent seen document. If you require that the user must view the documents in chronological order, then you can simply query for documents with a creation timestamp greater than the timestamp of the latest document seen by the user. This is really as good as it's going to get with Firestore, since you can't query for the absence of data.

Flutter - How to get documents only that certain value does not contain in a List in Cloud Firestore?

You're looking for the not-in operation, which allows you to specify up to 10 values in the array. If you have more than 10 IDs, you'll have to instead get all documents from the database and perform the exclusion in your application code.

If id is a field in the documents, that'd look something like this:

FirebaseFirestore.collection("users").where("id", whereNotIn: ids).get();

If you're actually trying to filter on the document ID, instead of a field, that'd be:

FirebaseFirestore.collection("users").where(FieldPath.documentId, whereNotIn: ids).get();


Related Topics



Leave a reply



Submit