How to get range of documents - firebase

Let's say I've got 100 documents in my collection. My goal is to get the documents between 5 and 33.
I tried startAt and endAt but it didn't work:
const db = firebase.firestore();
await db
.collection("pictures")
.startAt(start)
.endAt(end)
.get()

What you're trying to do isn't really possible. Documents don't have a natural index or position within a collection. To get ordering within a collection, you need to use at least one field on which you want to sort the documents. Only then do they have an order, and only then can you page through them.
The startAt and endAt methods on the query require that you define some order. You can see that in the example code provided in the API docs I linked to. Note the following statement in the docs for startAt:
Creates and returns a new Query that starts at the provided set of
field values relative to the order of the query. The order of the
provided values must match the order of the order by clauses of the
query.

Related

Firestore collection group query on document id [duplicate]

This question already has an answer here:
How to perform collection group query using document ID in Cloud Firestore
(1 answer)
Closed 1 year ago.
I am trying to run the following query:
this.db.firestore
.collectionGroup('members')
.orderBy('dateModified', 'desc')
.where(firebase.firestore.FieldPath.documentId(), '==', uid)
But I get the error:
Invalid query. When querying a collection group by
FieldPath.documentId(), the value provided must result in a valid
document path, but 'X6yL5Ko55jSMWhhoQUO91xVYdEH3' is not because it
has an odd number of segments (1).
Is the only way around this to add the document id to the document?
This is not ideal as I have a lot of existing data...
The document id is the uid of the firebase user.
As the error message says, for an index on a collection group the documentId() field values are actually stored as document paths to ensure unique lookups of those values in the index.
If you want to also query on document ID over a collection group, you will indeed have to store the ID as a field value in each document.
Also keep in mind that it is then possible to get multiple documents for the query, even though that is astronomically unlikely if you use the built-in add() operation.
Adding the uid to the document itself is the only possible way at the moment and then query on that field:
this.db.firestore
.collectionGroup('members')
.orderBy('dateModified', 'desc')
.where("uid", '==', uid)
There was a Github issue for the same and explains why that's not possible.
That's pretty much why I sometimes prefer to store a root level collection members. Each document in the collection will have contain the groupID (or whatever your parent collection is meant for). If you use userID as the key for documents in there then it goes easy.
this.db.firestore.collection("members").doc(uid)
So instead of having a path like: /groups/{groupID}/members/{memberID}, the structure will be like: /groups/{groupID} and all the members will be store in the root level collection 'members'. A document in that collection may look like:
// uid as doc key
{
groupId: "groupID",
...otherFields
}
The catch is if a member can join multiple groups you cannot use the userId as the key.

How can I limit and sort on document ID in firestore?

I have a collection where the documents are uniquely identified by a date, and I want to get the n most recent documents. My first thought was to use the date as a document ID, and then my query would sort by ID in descending order. Something like .orderBy(FieldPath.documentId, descending: true).limit(n). This does not work, because it requires an index, which can't be created because __name__ only indexes are not supported.
My next attempt was to use .limitToLast(n) with the default sort, which is documented here.
By default, Cloud Firestore retrieves all documents that satisfy the query in ascending order by document ID
According to that snippet from the docs, .limitToLast(n) should work. However, because I didn't specify a sort, it says I can't limit the results. To fix this, I tried .orderBy(FieldPath.documentId).limitToLast(n), which should be equivalent. This, for some reason, gives me an error saying I need an index. I can't create it for the same reason I couldn't create the previous one, but I don't think I should need to because they must already have an index like that in order to implement the default ordering.
Should I just give up and copy the document ID into the document as a field, so I can sort that way? I know it should be easy from an algorithms perspective to do what I'm trying to do, but I haven't been able to figure out how to do it using the API. Am I missing something?
Edit: I didn't realize this was important, but I'm using the flutterfire firestore library.
A few points. It is ALWAYS a good practice to use random, well distributed documentId's in firestore for scale and efficiency. Related to that, there is effectively NO WAY to query by documentId - and in the few circumstances you can use it (especially for a range, which is possible but VERY tricky, as it requires inequalities, and you can only do inequalities on one field). IF there's a reason to search on an ID, yes it is PERFECTLY appropriate to store in the document as well - in fact, my wrapper library always does this.
the correct notation, btw, would be FieldPath.documentId() (method, not constant) - alternatively, __name__ - but I believe this only works in Queries. The reason it requested a new index is without the () it assumed you had a field named FieldPath with a subfield named documentid.
Further: FieldPath.documentId() does NOT generate the documentId at the server - it generates the FULL PATH to the document - see Firestore collection group query on documentId for a more complete explanation.
So net:
=> documentId's should be as random as possible within a collection; it's generally best to let Firestore generate them for you.
=> a valid exception is when you have ONE AND ONLY ONE sub-document under another - for example, every "user" document might have one and only one "forms of Id" document as a subcollection. It is valid to use the SAME ID as the parent document in this exceptional case.
=> anything you want to query should be a FIELD in a document,and generally simple fields.
=> WORD TO THE WISE: Firestore "arrays" are ABSOLUTELY NOT ARRAYS. They are ORDERED LISTS, generally in the order they were added to the array. The SDK presents them to the CLIENT as arrays, but Firestore it self does not STORE them as ACTUAL ARRAYS - THE NUMBER YOU SEE IN THE CONSOLE is the order, not an index. matching elements in an array (arrayContains, e.g.) requires matching the WHOLE element - if you store an ordered list of objects, you CANNOT query the "array" on sub-elements.
From what I've found:
FieldPath.documentId does not match on the documentId, but on the refPath (which it gets automatically if passed a document reference).
As such, since the documents are to be sorted by timestamp, it would be more ideal to create a timestamp fieldvalue for createdAt rather than a human-readable string which is prone to string length sorting over the value of the string.
From there, you can simply sort by date and limit to last. You can keep the document ID's as you intend.

Firestore: Query Single Attribute Across all Documents

I have the following structure in a Firestore collection. The "ranks" collection is updated with documents named after the timestamps. In each document, I have the same fields and values. How can I query all documents for a specific field without parsing the entire document? I.e. I want all values in all documents where field is "aave"?
I am new to Firestore and I've been trying this for several weeks now. I tried limiting with where and considered using sub collection group queries but in my case data is not stored in sub collections. Sorry, for not being able to provide more context, since I couldn't get much closer.
Queries select specific values, or ranges of values, of a known field. There is no support for dynamic field names in a query in Firestore.
But if you want to get all documents where the field aave exists/has any value, you can make use of the fact that in the sort order of values they always start with null. So to get all documents where the field aave exists/has any value, you could do:
firebase.firebase().collection("ranks").where("aave", ">=", null)

Firebase queries and compound indexes

I have the following document structure in firebase:
{
typeId: number,
tripId: number,
locationId: string,
expenseId: number,
createtAt: timestamp
}
I want to query this collection using different 'where' statement everytime. Sometimes user wants to filter by type id and sometimes by locationId or maybe include all of the filters.
But it seems like I would need to create a compound index of each possible permutation? For example: typeId + expenseId, typeId + locationId, location + expenseId, etc, otherwise it doesn't work.
What if I have 20 fields and I want to make it possible to search across all of these?
Could you please help me to construct a query and indexes for the following requirement: Possibility to query across all fields, query can contain one, two, three, all or no fields included in where clause and always has to be ordered descending order by createdAt.
Cloud Firestore automatically creates indexes for the individual fields of your documents. So it can already filter on each field without you have to manually add these indexes.
In many cases it is able to combine these indexes to allow queries on field combinations, by performing a so-called zig-zag-merge-join.
Custom additional indexes are typically only needed once you add an ordering-clause to your query, in addition to filter clauses. If you have such a case, the Firestore client will log an error telling you exactly what index to create (with a link to the Firestore console that is prepopulated to created the index for you).

Firebase implementation of order by field and count method

I have a collection of schedules with documents with fields like this
I want to get the route with this highest traffic by running a query that orders by route field and do a count of the same field. My question is how can I count after ordering by a field? This is my query right now
scheduleByRoutes() {
return this.afs.collection('schedules', ref => ref.orderBy('route', 'asc')).snapshotChanges();
}
There is no direct way to count the number of documents of each route "category" returned by your orderBy() query.
You should either:
1/ Count them from your client, iterating on the query results.
or
2/ If you know the different routes upfront, issue a query for each route and use the size() method of each QuerySnapshot. You may use Promise.all() to make these calls in parallel.
or
3/ Maintain some counters for each route in, for example, another collection. For that you would use a set of Cloud Functions that would update the counters upon Creation/Modification/Deletion.
Be aware that approaches #1 and #2 will cost a document read for each document of the collection. If your collection contains a lot of documents, you may use approach #3.

Resources