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.
Im really confused on how to query a single record in firebase by the uid.
I have a user id and want to query the following table. I am using the following code.
However, it is returning both the records instead of just the 'this.selectedId' one. Is there 1) A way to just return the record being queried 2) keep the key instead of array of index 0, 1, 2, 3 etc...
Code used
const itemsRef = this.afs.collection<any>(`/profiles/`);
itemsRef.valueChanges(this.selectedId).subscribe(
x => {
console.log("Value change", x);
}
);
Image
Returned result
The valueChanges method doesn't take any parameters. If you want to monitor a single document
const itemRef = this.afs.doc<any>(`/profiles/`+this.selectedId);
itemRef.valueChanges()...
I have a firebase collection called activity with 4 documents inside, with timestamps of 2, 3, 4, and 5 o'clock.
When I query it like so
let postsQuery = firebase
.firestore()
.collection(`activity`)
.orderBy("timestamp", "desc")
.limit(2);
I correctly get the 5 and 4 o'clock posts.
However, when I call the function again with the following conditional:
// oldest post is the 4 o'clock document
if (oldestPost) {
postsQuery = postsQuery.startAfter(oldestPost);
}
I get the 4 and 5 documents again. If I change startAFter to be any other filter, I get 0 documents returned.
So my question is, am I just doing it wrong, OR can I not pass in a document.data() as the filter, and i need to pass in the original document instead?
So answering my own questions:
when i got my posts, I was just storing them in an idMap
postsQuery
.get()
.then((documents) => {
const typeIdToDocument = {};
documents.forEach((document) => {
const data = document.data();
typeIdToDocument[document.id] = { id: document.id, ...data };
});
Then when I passed in an startAfter, I used the .data() result of the post.
However, it worked when-
A) I passed in the original firebase document, not the .data() version
OR B) I passed in the Firebase Timestamp, rather than the full document
postsQuery = postsQuery.startAfter(oldestFirebaseTimestamp);
There are two overloads of startAfter:
startAfter ( snapshot : DocumentSnapshot < any > ), which is the one I recommend you use whenever you can
startAfter ( ... fieldValues : any [] ), into which you can pass just the field values of the document to start after.
The reason I recommend the first overload is that there may be cases where you have multiple documents with the same field value. The first overload is smart enough in that case to use the document ID to disambiguate between those and not return any overlapping documents. The second overload can't guarantee that.
Is there any way to query through an array of string values in firebase firestore which should contain two strings a and b BUT string b has to be listed after string a?
Example:
I have two string values I would like to query for:
a = "12345"
b = "67890"
And I have stored multiple documents in firebase firestore which could look like this:
document1 = {
id: "randomId1",
// ...other stuff
array: ["12345","345", "67890"]
}
document2 = {
id: "randomId2",
// ...other stuff
array: ["67890","345","12345"]
}
Now I only want to get document1 in my query results, because value b is listed after value a in the array section. How can I do this?
There is no way to do such queries in firestore, you can only define if it has this value or not.
Possible solutions:
Store a boolean inside your document which defines if this doc suits your needs.
Load all documents that have both values and check for their order on the client side.
Probably you should restructure your database, it is usually help in such cases.
If in sql, we can get it with:
select Max(Column_name) from Table_name
but still can't find simple way to get Max value of Column in Parse.com JS API.
Please explain me how to get Max Value of Column in JS API?
The best way to do this is to use Parse.Query (api) and order by descending, then obtain the first item in the result.
Edit:
Maybe, it's not a good idea to use order by in the situation that there are thousands(or even more) items as it's time consuming(complexity at least O(nlogn)). An alternative is to write a function of your own to choose the maximum value yourself with complexity of O(n).
Write a query for descending order and fetch the first object out of it. You will have the max value of the column there.
var query = new Parse.Query("something");
query.descending("column_name");
query.first({
success: function(result){
var max = result.get("column_name");
},
error: function(){
},
})
I am not sure if you can do it directly with a max function. You can create your own max function by doing a query to get all entries from that column and sorting them in descending order. There are APIs for this. Then choose the first value from the sorted list.
You can basically follow the same pattern from all other queries:
// Query for ID
ParseQuery<ParseObject> query = ParseQuery.getQuery("Table_Name");
// Condition
query.orderByDescending("ID");
// First object will be retrieved, this will be the max_value
query.getFirstInBackground(new GetCallback<ParseObject>() {
public void done(ParseObject object, ParseException e) {
if (object == null) {
Log.d("score", "The getFirst request failed.");
} else {
Log.d("score", "Retrieved the object.");
}
Now you can get the value using something like:
String mensagem = object.getString("ID");
Hopefully this will help you!