multi query and pagination with firestore - firebase

I am trying to implement multi query and pagination with firestore, but once I add < or > to the query the cursor not working.
//working example:
the doc id i save as propery on the doc
ref.where('category','==' 'cats').where('status', '==', 1).orderBy('id').cursor('last doc id from the returned list').limit(3)
//not working exmple:
ref.where('category','==' 'cats').where('status', '==', 1).orderBy('price').where('price', '>=', 2).where('price', '<=', 55).orderBy('id').cursor('last doc id from the returned list').limit(3)
no error returned. is it bug with firestore or on my end.

There has documentation at firebase on Pagination & Query and query data. We have to use the startAt() or startAfter() methods to define the start point for a query. Similarly, use the endAt() or endBefore() methods to define an end point for your query results.
Example:
To get all cities with a population >= 1,000,000, ordered by population,
db.collection("cities")
.orderBy("population")
.startAt(1000000);
and to get all cities with a population <= 1,000,000, ordered by population,
db.collection("cities")
.orderBy("population")
.endAt(1000000);
So pagination should be done using this method like,
// Construct query for first 25 cities, ordered by population
Query first = db.collection("cities")
.orderBy("population")
.limit(25);
first.get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
// ...
// Get the last visible document
DocumentSnapshot lastVisible = documentSnapshots.getDocuments()
.get(documentSnapshots.size() -1);
// Construct a new query starting at this document,
// get the next 25 cities.
Query next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25);
// Use the query for pagination
// ...
}
});

A Firestore query can only have a single range condition.
From the documentation on queries:
You can combine where() filters with orderBy() and limit().
However, if you have a filter with a range comparison (<, <=, >, >=), your first ordering must be on the same field:
Invalid: Range filter and first orderBy on different fields
citiesRef.where("population", ">", 100000).orderBy("country")

Check this example App using FireStore with Angular 8 app having pagination
using queries
limit()
orderBy()
startAt()
endBefore()
startAfter()

As Frank pointed out so far firestore doesn't permit combining ranges on different properties. I hope google will fix this someday, having multiple range filters seems like a pretty essential feature for any database.
Also it's a rather ugly solution, but I guess you could leave out the .where('price', '>=', 2) part and filter out the data afterwards on the client.

Related

how we can run multiple queries on Firebase/firestore to get filtered data in flutter?

how we can run multiple queries on Firebase/ firestore to get filtered data in flutter or any other possible solution? there is a picture storing dummy data in goal and title and wanna users according to the goals and titles filters.
Where() is a function provided by Firebase to query data
FirebaseFirestore.instance.collection('yourCollectionName').where('field1', isEqual:'value1').where('field2', isEqual: 'value2').get();
you can read more about it in the querying section at https://firebase.flutter.dev/docs/firestore/usage/
By looking at your Firestore database it is clear that you want to filter the documents based on two array fields goals and titles.
Firestore supports the arrayContains filter in the where clause by which you can filter data from an array inside Firestore. So you may think of doing something like this -
var filter1 = 'Hire Employees';
var filter2 = 'CEO';
FirebaseFirestore.instance
.collection('collectionId')
.where('goals', arrayContains: filter1)
.where('titles', arrayContains: filter2)
.get()
.then((QuerySnapshot docs) => {
for (var element in docs.docs) {print(element.data())}
});
But the above will throw an error saying that You cannot use 'array-contains' filters more than once. This is because Firestore has a limitation that you can use at most one array-contains clause per query which is documented here. So it is not possible to do something like the above.
I am not very sure of your use case, but as a workaround, you can restructure the database structure by making any one of the array field to a map, something like the following -
By making the structure like the above you can use multiple where clauses with one arrayContains filter and one isEqualTo filter which will look something like this -
var filter1 = 'Hire Employees';
var filter2 = 'CEO';
FirebaseFirestore.instance
.collection('collectionId')
.where('goals.$filter1', isEqualTo: true)
.where('titles', arrayContains: filter2)
.get()
.then((QuerySnapshot docs) => {
for (var element in docs.docs) {print(element.data())}
});
As the idea of allowing multiple arrayContains filters is currently not supported, but could be valid, I would recommend you file a feature request.

how to filter orderby nested list firebase to flutter App with rest database realtime

I use firebase database filter orderBy seller_id in nested list, why is the result null? thank you 🙏
:rules Firebase
var url = Uri.parse(
'https://MeDatabase.firebasedatabase.app/orders.json?auth=$authToken&orderBy="list_produk/seller_id"&equalTo="$idUser"');
try {
final response = await http.get(url);
if (response.statusCode >= 400) {
throw EasyLoading.showError('Cek Koneksi Internetmu!');
}
final extractData =
convert.jsonDecode(response.body) as Map<String, dynamic>;
print('inii orderan');
print(extractData);
if (extractData == null) {
return _orders = [];
}
} catch (e) {
throw e;
}
You're trying to index and order each order on its list_produk/seller_id property.
But if I look at your JSON data, there is no list_produk/seller_id property under the order. There's only list_produk/1/seller_id, and presumably list_produk/0/seller_id. Those are not the same as what you use in your rules/query, so the query returns no results.
Firebase Realtime Database queries function on a single, flat list of nodes. The value you order/filter on must exist at a fixed path under each direct child node.
So in your example, you can search across all orders on for example id_user, or you can search across the seller_id of the products in a specific order, but you can't search across all sellers of products in all orders.
In other words: your current data structure makes it easy to find the products and sellers per order, it does however not make it each to find the orders per product across all users. To allow that you'll want to add an additional data structure, typically known as a reverse index, that maps from each seller_id to the orders that contain products from that seller.
For more on this, I recommend checking out:
Firebase query if child of child contains a value
Firebase Query Double Nested
Many to Many relationship in Firebase

Is there a work-around for the limited firebase query capabilities

I'm building a dating app that takes many filters. Here is the query:
findMatch(itinerary: IItinerary): void {
const findMatchQuery = this.matchSvc.getMyMatch()
.where('location', '==', itinerary.location)
.where('endDay', '>=', itinerary.startDay)
.where('gender', '==', itinerary.searchGender);
findMatchQuery.get()
.then(listSnapshot => {
this.matches = [];
listSnapshot.forEach(async snapshot => {
let age = moment().diff(snapshot.data().dateOfBirth, 'years');
if ((snapshot.data().startDay <= itinerary.endDay) && (!this.dontShowList.includes(snapshot.id))) {
if ((age <= itinerary.ageRange.upper) && (age >= itinerary.ageRange.lower) && (snapshot.id !== itinerary.id)) {
this.matches.push({
snapshot.data(),
photoUrl: await this.getUserPhoto(snapshot.data().userId)
});
}
}
});
});
}
I realize what I'm doing here isn't good but I don't know another way to do this because you can't use more than one inequality operator.
I have to use ('endDay', '>=', itinerary.startDay), but I also need to filter by age thus I'm using the if statement like so to filter by age:
let age = moment().diff(snapshot.data().dateOfBirth, 'years');
if ((age <= itinerary.ageRange.upper) && (age >= itinerary.ageRange.lower)
this is to make sure I'm only adding users to the list who fall within the desired age range. I'd like to be able to limit my query by 1 and when a user swipes left or right then query firebase again to get another user. I can't limit because I'm using the if statements outside of the query to further filter the list based on the user's desired search.
Say I have a limit of 5 from firebase. But then none of the 5 users fall within the age range. From firebase's perspective I have my 5 users...firebase isn't aware that those 5 were further filtered by the if statement and the user's list is empty. I need my entire query to take all of the filters in effect if I'm able to use the limit() option so I don't have to pull every record from firebase.
And in addition to the age filters I'm actually filtering for a date range...but again, I can only use one inequality operator in the query. So I have an if statement for the following as well:
if ((snapshot.data().startDay <= itinerary.endDay)
So what I'm doing here is looking for a date range. This is what I'd like to do:
.where('location', '==', itinerary.location)
.where('endDay', '>=', itinerary.startDay)
.where('gender', '==', itinerary.searchGender)
.where('startDay', '<=', itinerary.endDay)
.where('age', '>=', itinerary.minimumAge)
.where('age', '<=', itinerary.maximumAge);
Obviously I can't use the above query and I'm using the if statements to further filter but I guess there's no way to use the limit() function given those if statements but I thought I'd ask first just in case.
Thanks
Unless you have some way of combining all the range queries into a single field, you can't really bypass Firestore query limitations. What you can do instead is offload some of the filtering on to the client app. So, you can apply a range filter that removes most of the documents, then have the client app remove everything else that's unwanted.
You can possibly speed it up this process by having a backend make the query and further filter the results before sending them back to the client. This would save the time it takes to transfer the unwanted documents.

Fetch collection startAfter documentID

Is there a way to fetch document after documentID like
private fun fetchCollectoionnAfterDocumentID(limit :Long){
val db = FirebaseFirestore.getInstance()
var query:Query = db.collection("questionCollection")
.startAfter("cDxXGLHlP56xnAp4RmE5") //
.orderBy("questionID", Query.Direction.DESCENDING)
.limit(limit)
query.get().addOnSuccessListener {
var questions = it.toObjects(QuestionBO::class.java)
questions.size
}
}
I want to fetch sorted questions after a given Document ID. I know I can do it using DocumentSnapShot. In order to fetch the second time or after the app is resume I have to save this DocumentSnapshot in Preference.
Can It be possible to fetch after document ID?
startAfter - > cDxXGLHlP56xnAp4RmE5
Edit
I know I can do it using lastVisible DocumentSnapshot . But I have to save lastVisible DocumentSnapshot in sharedPreference.
When app launch first time 10 question are fetched from questionCollection. Next time 10 more question have to be fetched after those lastVisible. So for fetching next 10 I have to save DocumentSnapshot object in sharedPreference. Suggest me a better approach after seeing my database structure.
And one more thing questionID is same as Document reference ID.
There is no way you can pass only the document id to the startAfter() method and simply start from that particular id, you should pass a DocumentSnapshots object, as explained in the official documentation regarding Firestore pagination:
Use the last document in a batch as the start of a cursor for the next batch.
first.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
=// Get the last visible document
DocumentSnapshot lastVisible = documentSnapshots.getDocuments()
.get(documentSnapshots.size() -1);
// Construct a new query starting at this document,
Query next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible) //Pass the DocumentSnapshot object
.limit(25);
// Use the query for pagination
}
});
See, here the lastVisible is a DocumentSnapshot object which represents the last visible object. You cannot pass only a document id. For more information, you can check my answer from the following post:
How to paginate Firestore with Android?
It's in Java but I'm confident you can understand it and write it in Kotlin.
Edit:
Please consider defining an order of your results so that all your pages of data can exist in a predictable way. So you need to either specify a startAt()/startAfter() value to indicate where in the ordering to begin receiving ordered documents or use a DocumentSnapshot to indicate the next document to receive, as explained above.
Another solution might be to put the document id into the document itself (as a value of a property) and order on it, or you can use FieldPath.documentId() to order by the id without having to add one.
You can also check this and this out.
There is one way to let startAfter(documentID) works.
Making one more document "get", then using the result as startAfter input.
val db = FirebaseFirestore.getInstance()
// I use javascript await / async here
val afterDoc = await db.collection("questionCollection").doc("cDxXGLHlP56xnAp4RmE5").get();
var query:Query = db.collection("questionCollection")
.startAfter(afterDoc)
.orderBy("questionID", Query.Direction.DESCENDING)
.limit(limit)
A simple way to think of this: if you order on questionID you'll need to know at least the value of questionID of the document to start after. You'll often also want to know the key, to disambiguate between documents with the same values. But since it sounds like your questionID values are unique within this collection, that might not be needed here.
But just knowing the key isn't enough, as that would require Firestore to scan its entire index to find that document. Such an index scan would break the performance guarantees of Firestore, which is why it requires you to give you the information it needs to perform a direct lookup in the index.

Firestore: how to perform a query with inequality / not equals

I want select from Firestore collection just articles written NOT by me.
Is it really so hard?
Every article has field "owner_uid".
Thats it: I JUST want to write equivalent to "select * from articles where uid<>request.auth.uid"
TL;DR: solution found already: usages for languages/platforms: https://firebase.google.com/docs/firestore/query-data/queries#kotlin+ktx_5
EDIT Sep 18 2020
The Firebase release notes suggest there are now not-in and != queries. (Proper documentation is now available.)
not-in finds documents where a specified field’s value is not in a specified array.
!= finds documents where a specified field's value does not equal the specified value.
Neither query operator will match documents where the specified field is not present. Be sure the see the documentation for the syntax for your language.
ORIGINAL ANSWER
Firestore doesn't provide inequality checks. According to the documentation:
The where() method takes three parameters: a field to filter on, a comparison operation, and a value. The comparison can be <, <=, ==, >, or >=.
Inequality operations don't scale like other operations that use an index. Firestore indexes are good for range queries. With this type of index, for an inequality query, the backend would still have to scan every document in the collection in order to come up with results, and that's extremely bad for performance when the number of documents grows large.
If you need to filter your results to remove particular items, you can still do that locally.
You also have the option of using multiple queries to exclude a distinct value. Something like this, if you want everything except 12. Query for value < 12, then query for value > 12, then merge the results in the client.
For android it should be easy implement with Task Api.
Newbie example:
FirebaseFirestore db = FirebaseFirestore.getInstance();
Query lessQuery = db.collection("users").whereLessThan("uid", currentUid);
Query greaterQuery = db.collection("users").whereGreaterThan("uid", currentUid);
Task lessQuery Task = firstQuery.get();
Task greaterQuery = secondQuery.get();
Task combinedTask = Tasks.whenAllSuccess(lessQuery , greaterQuery)
.addOnSuccessListener(new OnSuccessListener<List<Object>>() {
#Override
public void onSuccess(List<Object> list) {
//This is the list of "users" collection without user with currentUid
}
});
Also, with this you can combine any set of queries.
For web there is rxfire
This is an example of how I solved the problem in JavaScript:
let articlesToDisplay = await db
.collection('articles')
.get()
.then((snapshot) => {
let notMyArticles = snapshot.docs.filter( (article) =>
article.data().owner_uid !== request.auth.uid
)
return notMyArticles
})
It fetches all documents and uses Array.prototype.filter() to filter out the ones you don't want. This can be run server-side or client-side.
Updating the answer of Darren G, which caused "TypeError: Converting circular structure to JSON". When we perform the filter operation, the whole firebase object was added back to the array instead of just the data. We can solve this by chaining the filter method with the map method.
let articles = []
let articlesRefs = await db.collection('articles').get();
articles = articlesRefs.docs
.filter((article) => article.data.uid !== request.auth.uid) //Get Filtered Docs
.map((article) => article.data()); //Process Docs to Data
return articles
FYI: This is an expensive operation because you will fetching all the articles from database and then filtering them locallly.
Track all user id in a single document (or two)
filter unwanted id out
Use "where in"
var mylistofidwherenotme = // code to fetch the single document where you tracked all user id, then filter yourself out
database.collection("articles").where("blogId", "in", mylistofidwherenotme)
let query = docRef.where('role','>',user_role).where('role','<',user_role).get()
This is not functioning as the "not equal" operation in firestore with string values
You can filter the array of objects within the javascript code.
var data=[Object,Object,Object] // this is your object array
var newArray = data.filter(function(el) {
return el.gender != 'Male';
});

Resources