Searching within collection collectionGroup query - javascript

So I came across this answer which allows limiting the collectionGroup query to a specific document: CollectionGroupQuery but limit search to subcollections under a particular document
However I also want to further filter results based on a specific field using where, which required an index. The query works without errors but it always returns empty snapshot:
const cityRef = firebase.firestore().doc('cities/cityId');
firebase.firestore().collectionGroup('attractions')
.where('name', '>=', keywords),
.where('name', '<=', keywords + '\uf8ff')
.orderBy('name')
.orderBy(firebase.firestore.FieldPath.documentId())
.startAt(cityRef.path),
.endAt(cityRef.path + "\uf8ff")
.get()
.then((querySnapshot) => {
console.log("Found " + querySnapshot.size + " docs");
querySnapshot.forEach((doc) => console.log("> " + doc.ref.path))
})
.catch((err) => {
console.error("Failed to execute query", err);
})

firebaser here
The problem is almost certainly that your query has range checks on two different fields (name and the document path), which isn't possible in Firestore's query model. As the documentation on query limitations says:
In a compound query, range (<, <=, >, >=) and not equals (!=, not-in) comparisons must all filter on the same field.
Your startAt and endAt clauses are just different ways of writing > and < as far as this limitation is concerned.
To understand why the SDK allow you to write this query, but doesn't give you the result you want, we'll have to dive a bit deeper into it, so... 👇
What is possible, is to pass all relevant fields to startAt and endAt so that it can determine the correct slice across all those field values.
Doing that would also remove the need to even have the where, so it'd be:
firebase.firestore().collectionGroup('attractions')
.orderBy('name')
.orderBy(firebase.firestore.FieldPath.documentId())
.startAt(keywords, cityRef.path),
.endAt(keywords + '\uf8ff', cityRef.path + "\uf8ff")
.get()
...
But this query now first looks for the documents starting at keywords and then if necessary for cityRef.path in there to disambiguate between multiple results.
What you want is the equivalent of this query:
const docId = firebase.firestore.FieldPath.documentId()l
firebase.firestore().collectionGroup('attractions')
.orderBy('name')
.where('name', '>=', keywords),
.where('name', '<=', keywords + '\uf8ff')
.orderBy(firebase.firestore.FieldPath.documentId())
.where(docId, '>=', cityRef.path),
.where(docId, '<=', cityRef.path + '\uf8ff')
Now it's immediately clear why this isn't possible, because we have range conditions on two fields.
I've been trying to get that working in this jsbin (https://jsbin.com/yiyifin/edit?js,console), so far without success, but I'll post back if I get it working or have a final verdict on why it doesn't work.

Related

Query firestore DB with array-contains and in (firebase 9)

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.

Cloud Firestore query and limit doesn't query all data

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?

Array-contains query not working react native [duplicate]

This question already has an answer here:
Firestore array-contains-any is not working properly
(1 answer)
Closed 3 years ago.
Hello i'm implementing the array-contain query but everytime i'm trying i'm Getting blank snapshot. My code is:
getStation = () => {
const query = firebase
.firestore()
.collection('routes')
.where('station', 'array-contains', 'station_name ');
query.get().then(snapshot => {
console.log("snapshot", snapshot);
snapshot.docs.forEach(doc => {
alert("eri")
console.log("eryh", doc.data);
})
})
.catch(err => {
console.log('Error getting documents', err);
});
}
I even tried to pass the static values but not able to get the desire value.
In your code there is a problem with typo mistake station must be stations
.where('station', 'array-contains', 'station_name ');
must be
.where('stations', 'array-contains', 'station_name ');
I hope it will fix your problem.
Update Answer
There is also typo mistake in station_name you just added the space after station_name
.where('station', 'array-contains', 'station_name ');
must be
.where('stations', 'array-contains', 'station_name');
You are trying to use array-contains to check for a field inside a map. This is not supported.
The array-contains can only search for a field inside the array and not a field inside a map which is inside that array.
A workaround I have tested is that you add an extra field inside the array that will be a boolean or a string and you can query based on it.
Using a boolean field in the array:
.where('stations', 'array-contains', true);
Using a String field in the array:
.where('stations', 'array-contains', 'station_name exists');
The array-contains operation checks if the precise object/value you specify exists in the array. It can't check on subsets of the array elements, only on complete values. So in your current structure, you'd have to pass all properties into the array-contains call:
.where('station', 'array-contains',
{ station_address_name: '...', station_lat_long: ..., status_name: '...' });
This typically is not feasible, in which case you'll want to store the elements that you want to filter on in a (additional) separate field in your documents. In this example, since you want to filter on station names, you'd add a field station_names where you store just the station names. Then you can query with:
.where('station_names', 'array-contains', 'name to search for')
Also see:
Firestore to query by an array's field value
Firestore array-contains-any is not working properly
How to make query spot only the some value in a Map inside an Array in flutter (dart)?

Firestore query 'array_contains' pass in array [duplicate]

This question already has answers here:
Firestore search array contains for multiple values
(6 answers)
Closed 3 years ago.
I am looking at Firebase, cloud firestore and I am not understanding how I would perform a query and pass in an array.
const friendIds = [1, 2, 3];
Firestone.collection('conversations')
.where('members', 'array-contains', friendIds)
.get()
.then(querySnapshot => console.log(querySnapshot))
.catch(error => console.log(error));
anyone know how to achieve this?
This is currently not possible with a single query. array-contains can only find a single item in the named array.
Your alternative is to perform three queries, one for each item in the friendsId array, then merge the results in your code.
Not sure if this is possible, but here's an example from the Firebase Node.js Snippets that can give you an idea:
let citiesRef = db.collection('cities');
// [START array_contains_filter]
let westCoastCities = citiesRef.where('regions', 'array-contains',
'west_coast');
// [END array_contains_filter]
westCoastCities.get()
.then(res => {
console.log('West Coast get: ', res);
});
Reference Documentation
According to the official documentation regarding query operators:
You can use the array-contains operator to filter based on array values.
As I can see in your code, your friendIds argument that is passed as the third parameter to the where() function is of type array. What you are actually doing, you are searching in the members property which is of type array for an array, which is actually not possible since I assume the members array in your database contains numbers.
An query like this:
Firestone.collection('conversations')
.where('members', 'array-contains', 3)
.get()
.then(querySnapshot => console.log(querySnapshot))
.catch(error => console.log(error));
Will work perfectly fine because we are searching within the members array for a number.
If you need to query for all those numbers, you should perform three separate queries and combine the result client side.

datastore get last key in range?

I have a set of objects that I created according to some google docs recommendation for objects you want to have locality.
So I have objects with keys of the form 'Name#00000001'. My thing is I have an operation I need to get the last one of these so I can get them all with the code below.
var query = datastore.createQuery('Post')
.select('__key__')
.filter('__key__', '>=', datastore.key(['Post', req.query["Thread"] + "#00000001"]))
.filter('__key__', '<=', datastore.key(['Post', req.query["Thread"] + "#99999999"]));
Things is when I do stuff like
var query = datastore.createQuery('Post')
.select('__key__')
.filter('__key__', '>=', datastore.key(['Post', req.query["Thread"] + "#00000001"]))
.filter('__key__', '<=', datastore.key(['Post', req.query["Thread"] + "#99999999"]))
.order('__key__',{descending: true})
.limit(1);
It fails. It seems I can't order by key at all. The items have time stamps in addition to this, but it seems you can't filter by 1 item and order by another. Is there anyway to get the last item with a query without querying everything?
In Google Cloud-Java see StructuredQuery. You can .setOrderBy(OrderBy.asc("__key__"))

Categories

Resources