I'm trying to order by my Firestore query based on a timestamp named "issueTime" with pagination. Since I'm using a where condition with ">=" and "<=" on a field named as "tag_name", I'm forced to order by based on "tag_name" before the "issueTime" field. So, now the data first orders by tag_name and then orders by issueTime. But I'm trying to order it based on issueTime.
Is there any work around for this ?
let keyQuery = query(docRef,
where("buildingID", "==", buildingId),
where('tag_name', ">=", searchValue),
where('tag_name', "<=", searchValue + "\uf8ff"),
orderBy('tag_name', 'asc'),
orderBy('issueTime', 'asc'),
limit(PAGE_LIMIT));
I'm using Firestore to build a game and I'd like to show a list of high scores.
I'm currently getting the 10 highest scores with the following query:
const q = query(doc(db, "scores", title), orderBy("score", "desc"), limit(10));
In addition to this, I'd like to let the player know how they fared compared to global high scores. For example, if they got the 956th-highest score, I'd like them to know their relative position is 956.
I've seen that, for cursors, one can provide an offset with a given document, ie:
const q = query(doc(db, "scores", title), orderBy("score", "desc"), limit(10), startAt(myScoreDocRef));
Is there any way from this to get the score's logical index in the sorted result set?
Firestore recently added getCountFromServer() (API reference) function that is perfect for this use case. You can create a query that matches documents with score greater than current user's score and fetch the count as shown below:
const currentUserScore = 24;
const q = query(
collection(db, 'users'),
orderBy('score', 'desc'),
where('score', '>', currentUserScore)
)
// number of users with higher score
const snapshot = await getCountFromServer(q)
console.log(`Your rank: ${snapshot.data().count + 1}`)
However, in case multiple users have same score, they will all see same rank with this query. As a workaround you can add another parameter to the ranking apart from score, like querying users with same score and checking user age or so.
The count query costs only 1 read for each batch of up to 1000 index entries matched by the query (as per the documentation) so it's much for efficient than querying users with some offset and manually calculating the rank.
In Firebase (v. 9), I have this firestore DB collection named "users". Each user has these fields: "gender" (string: 'male' or 'female'), "age" (number) and "language" (string: 'en-EN' or 'fr-FR' or 'es-ES' or 'de-DE' ).
From a filter checkbox menu, a user can select the languages then execute a query, get the result and than applied another filter and get another result.
For example:
I check, from the language menu "English" and "French" --> get the result, for example 3 users (2 female and 1 male). Then, from the gender menu, I check "male" --> get the result: just that one male user from the previous query result.
But a user can also do the first query for the language and then, in the second one, check both 'male' and 'female'.
I'm trying to do the query combining 'array-contains' and 'in' operator but I have no luck.
The query
const q = query(
collection(db, 'users'),
where('gender', 'array-contains', ['male', 'female']),
where('language', 'in', ['en-EN', 'es-ES']),
where('age', '>', 14),
where('age', '<', 40)
);
EDIT: For that query I changed my DB structure: gender has become an array but with 'array-contains' I can't do:
where('gender', 'array-contains', ['male', 'female'])
It must be something like that:
where('gender', 'array-contains', 'male')
but I want to check for both gender.
What could solve my problem is doing two queries with 'in' operator but I can't do that. (Firebase allows me to have only 1 'in' operator in the query).
My goal is, for example, to get every users in the DB that speak English or French, both male or female and with an age between 14 and 40. Is that the correct way to do this? How can I do the first query for the language and then, do another query starting from that result in order to avoid redoing the first query (the language) when I query for the gender and then when I query for the age? I also create indexes as Firebase suggested me to do, but I still get an empty array.
I was reading the firebase 'Query limitation' from the doc:
Cloud Firestore provides limited support for logical OR queries. The in, and array-contains-any operators support a logical OR of up to 10 equality (==) or array-contains conditions on a single field. For other cases, create a separate query for each OR condition and merge the query results in your app.
In a compound query, range (<, <=, >, >=) and not equals (!=, not-in) comparisons must all filter on the same field.
You can use at most one array-contains clause per query. You can't combine array-contains with array-contains-any.
You can use at most one in, not-in, or array-contains-any clause per query. You can't combine in , not-in, and array-contains-any in the same query.
You can't order your query by a field included in an equality (==) or in clause.
The sum of filters, sort orders, and parent document path (1 for a subcollection, 0 for a root collection) in a query cannot exceed 100.
That says I can combine the 'in' operator only with 'array-contains'. It also says, that "for other cases, create a separate query for each OR condition and merge the query results in your app" but I can't find any example on how to do that.
I read an answer here > Firebase Firestore - Multiple array-contains in a compound query where someone suggest to change the structure of the data to query, from an array to a map and then query with the equal operator:
where('field.name1', '==', true),
where('field.name2', '==', true)
I still have no luck with this.
Edit2: I guess the only thing I could do, is to execute 2 different queries, get the results in two different arrays and than do whatever logic I need to implement using js..I mean, not with firebase query operator. Can someone guide me through the process?
Any help is appreciated.
Thank you
Google Cloud Firestore only allows one in condition per query. You'll need to do the second one in JavaScript processing the results. Probably, the best way you can do is to get the result from the original query and process the result using Javascript. See sample code below:
const q = query(
collection(db, 'users'),
where('language', 'in', ['en-EN', 'es-ES']),
where('age', '>', 14),
where('age', '<', 40)
);
// Pass the data from the checkboxes.
// Can be 'male', 'female', or ('male' and 'female')
const gender = ['male', 'female'];
let array = [];
const snapshot = await getDocs(q);
snapshot.forEach((doc) => {
if (gender.includes(doc.data().gender)) {
array.push(doc.data());
}
});
console.log(array);
The above code will return the processed result whatever you pass on the gender variable. You could do it vice-versa, if you want to query the gender first then just interchange the query and variables.
Another option is to have a compound string, for example:
Checked:
Male
Female
en_EN
en_ES
Compound strings will be ['en_male', 'en_female', 'es_male', 'es_female']. You can query this by only one in statement. See sample code below:
// Combined data passed from the checkboxes.
// Can only be one and up to 10 comparison values.
const compound = ['en_male', 'en_female', 'es_male', 'es_female'];
const q = query(
collection(db, 'users'),
where('compound', 'in', compound),
where('age', '>', 14),
where('age', '<', 40)
);
const snapshot = await getDocs(q);
snapshot.forEach((doc) => {
console.log(doc.id, doc.data());
});
The downside of this approach is you can only have up to 10 comparison values for the in operator.
For more relevant information, you may check this documentation.
I have a list with tags that I want to search in and retrieve data that matches the search.
I can retrieve all data correct with this query
searchTags(search: string): AngularFirestoreCollection<Tag> {
return this.afs.collection(this.dbPathTags, ref => ref.where('tag', "<=", search))
}
But the problem is that i want to limit the results to 5, since I'm planning to show them in a dropdown/autocomplete (like tags in SO)
After adding the limit, the query is just searching for the top 5 items from db and ONLY returns any of them if they matches. Wanted result is query filter ALL items then returns 5 items
searchTags(search: string): AngularFirestoreCollection<Tag> {
return this.afs.collection(this.dbPathTags, ref => ref.where('tag', "<=", search).limit(5))
}
When searching for "a" I get this result
But when searching "m" or "math" I don't get anything, note that "math" is also in the db and is displayed when the limit is off
Limit off
Have considered using startAt with the Limit rather than the where.
Firebase Pagination docs https://firebase.google.com/docs/firestore/query-data/query-cursors.
edit: .orderBy('field', 'order') when order is not present ascending order is default
searchTags(search: string): AngularFirestoreCollection<Tag> {
return this.afs.collection(this.dbPathTags, ref => ref.orderby('field', 'order').startAt(search).limit(5))
}
Maybe something like that?
I'm developing a app and need get a random user from my firebase user list. Whenever a user registers, the system updates the user count on an especific node. So, I draw a number from 1 to the total user. And now, how do I select a user based on that number?
Assuming all of your users are stored in a /users node with keys of their uid and assuming the uid's are ordered (which they always are), there are several options.
1) Load all of the users from the /users node into an array and select the one you want via it's index. Suppose we want the 4th user:
let usersRef = self.ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
let allUsersArray = snapshot.children.allObjects
let thisUserSnap = allUsersArray[3]
print(thisUserSnap)
})
While this works for a small amount of users, it could overwhelm the device if you have say, 10,000 users and lots of data stored in each node.
2) Create a separate node to just store the uid's. This is a significantly smaller dataset and would work the same way as 1)
uids
uid_0: true
uid_1: true
uid_2: true
uid_3: true
uid_4: true
uid_5: true
uid_6: true
uid_7: true
3) Reduce the size of your dataset further. Since you know how many users you have, split the dataset up into two sections and work with that.
using the same structure as 2)
let uidNode = self.ref.child("uids")
let index = 4 //the node we want
let totalNodeCount = 8 //the total amount of uid's
let mid = totalNodeCount / 2 //the middle node
if index <= mid { //if the node we want is in the first 1/2 of the list
print("search first section")
let q = uidNode.queryLimited(toFirst: UInt(index) )
q.observeSingleEvent(of: .value, with: { snapshot in
let array = snapshot.children.allObjects
print(array.last) //the object we want will be the last one loaded
})
} else {
print("search second section")
let q = uidNode.queryLimited(toLast: UInt(index) )
q.observeSingleEvent(of: .value, with: { snapshot in
let array = snapshot.children.allObjects
print(array.first) //the object we want will be the first one loaded
})
}
this method only returns 1/2 of the list so it's a much more manageable amount of data.
If you are talking about your authenticated users, the only way to retreive a list of them is by calling the corresponding admin function and applying your logic to it afterwards.
Another way could be writing a trigger for your authentication and store the userId with an incrementing number (and maybe save a totalUser field), then you only need to generate a random number and access said user.