My Firebase Setup is basically like this:
Collection Users (documents with field: userID) -> Collection wishlists (documents with field: name) -> Collection wünsche
What I would like is query for wünsche but for that I would like to use this code, which is not allowed:
db
.collection("users")
.document(userID)
.collection("wishlists")
.document.whereField("name", isEqualTo: list.name) // <- this is the crucial part which I would like to call but is not possible
.collection("wünsche")
.order(by: "wishCounter")
.getDocuments() {( querySnapshot, error) in }
Is there any workaround for this so I can use .collection after a whereField() ?
Basically: you can't do this, it's not how Firestore Queries work. You can query from a single collection at a time (with the exception of Collection Group Queries).
When you use whereField you're forming a Firestore Query object where you will retrieve a list of Documents in the original Collection.
So this is alone valid Firestore Query object, which you can then call get() on:
db.collection("users").document(userID)
.collection("wishlists").document.whereField("name", isEqualTo: list.name)
You cannot then query Sub-Collections inside that original query, unless you are querying a sub-collection for a specific Document or are performing a Collection Group Query. (It sounds like Collection Group Queries will best fit your use-case, so give the documentation a look).
Related
I am trying to fetch project data from FireStore and here is my structure on firestore:
And here is my code:
FirestoreReferenceManager.rootProjects.whereField(
FirebaseKeys.ProjectDetails.inviteData,
arrayContains: [
FirebaseKeys.ProjectDetails.otherUserId: userID,
FirebaseKeys.ProjectDetails.inviteStatus: FirebaseKeys.ProjectDetails.pending
])
If I don't have isInvoiceAccess data on firestore then above code is working fine but when I add that key on firestore then above query is not giving me any results and I can not add that key because it can be true or false.
If I use below code then I am getting the results:
FirestoreReferenceManager.rootProjects.whereField(
FirebaseKeys.ProjectDetails.inviteData,
arrayContains: [
FirebaseKeys.ProjectDetails.otherUserId: userID,
FirebaseKeys.ProjectDetails.inviteStatus: FirebaseKeys.ProjectDetails.pending,
FirebaseKeys.ProjectDetails.invoiceAccess: true
])
So I want data from FireStore without adding isInvoiceAccess in my query.
The arrayContains operation only checks whether there is exact match in the array, it cannot check for partial matches.
The common workaround for your use-case is to add an additional array field for each specific values/combination of values that you want to query on. So in your case, add a field invites_uid_and_status with just those two values for each, and then use that for this query.
I need to make a Firestore query in a collection that:
is ordered by field "timestamp"
the field "uid" is equal to any of the elements of a strings array (more than 10)
I need to paginate the query so that I'm able to perform later more queries starting from a precise timestamp
The code should look like this:
Firestore.firestore()
.where(field: "uid", in: arrayOfUids)
.order(by: "timestamp", descending: false)
.limit(to: 5)
or in case of starting from a particular timestamp
Firestore.firestore()
.where(field: "uid", in: arrayOfUids)
.startAt("timestmap", lastTimestampFetched)
.order(by: "timestamp", descending: false)
.limit(to: 5)
How can I achieve this result?
In this topic was suggested to perform multiple calls filtering with up to 10 elements in the array in each call (the maximum number of array elements for Firestore to be able to compare) but I didn't have the constraint of ordering by timestamp.
EDIT:
I cannot order the query result client-side, it wouldn't be helpful for my use case.
Let's say that we have 30 uids to query for:
First, we have to divide these uids into groups of 10.
So we'll have 3 groups of 10 uids each (the max array lenght to for querying with Firestore); for each of this groups we'll do a separate query which looks like this:
Firestore.firestore()
.where(field: "uid", in: arrayOfTenUids)
.startAt("timestmap", lastTimestampFetched)
.order(by: "timestamp", descending: false)
.limit(to: 5)
For each query, we save the last document timestamp in order to be able to perform successive queries with pagination.
Here is the problem, we are going to have multiple last timestamps, since we are performing multiple queries, which timestamp are we going to save?
1st option: the last timestamp of all last timestamps
2nd option: the first timestamp of all last timestamps
1st case -> in successive queries we are going to fetch some documents that have been previously fetched
2nd case -> in successive queries we are going to miss some documents that have been previously fetched (because the last timestamp of one query could be after the last timestamp of another one)
This topic is really hard to explain but I did my best.
Based on the comments let me try to create the current Firestore structure and re-state the objective, then suggest a solution. I am ignoring the timestamp for the moment:
Posts (collection)
post_0
uid: uid_4
post_1:
uid: uid_1
post_2:
uid: uid_3
post_3:
uid: uid_2
and then
Matches (collection)
uid_0
uid_1
uid_2
uid_3
uid_5
uid_4
The objective is for the current user, say uid_0, to read their node within the Matches collection which would be this
uid_0
uid_1
uid_2
uid_3
and then get the posts from the Post nodes where the uid field matches the uid's in that list. The result should be post_1, post_2 and post_3.
The issue is the users node, uid_0, could have dozens of child uid's and since Firestore can only match up to 10 at a time using the 'in' function
.where(field: "uid", in: arrayOfUids)
it would take multiple reads - and then there's the issue of the timestamp and sorting along with paginating.
My suggestion is to simplify; if the above stated goal is correct, instead of storing those different uid's with each post, just store the uid of the current user, like this
Posts (collection)
post_0
uid: uid_2
post_1:
uid: uid_0
post_2:
uid: uid_0
post_3:
uid: uid_0
Then query the Posts node for uid = uid_0. The result will be exactly the same and much simplier to implement and maintain and then the timestamp/pagination issue is no longer and issue.
Obviously this is a simple example; I would imagine you want to store multiple uid's within each post, so whichever user is logged in can retrieve their selection of posts. Do to that, use array contains to retrieve the posts that match for this user.
Posts (collection)
post_0
uid_array:
uid_2
uid_9
uid_21
I have the next structure of content in firestore (a channels collection, and a followers sub-collection in every channel):
channels (is a collection):
- {channel id} (channel document id)
- name,
- description, ...
- followers (subcollection in every channel)
- {user id} (follower document id)
- state (user attribute) = 1 (is active),
I'm trying a query to get all channels of one follower. something similar to:
// dart
db.collection('channels').where('followers.$uid.state', isEqualTo: 1).snapshots();
Where $uid is a valid user id. Then, query result must return all channels where the user is as a follower.
I could do it with an array of user ids in channel, but I'll have a big number of followers and in arrays, I have to read and write complete array in every modification, when I add or remove followers.
Thanks!
The only way to do this is not going to be just a simple query. You can use a collection group query to find all the follower documents among all channels that match some criteria, but you will have to extract the channel IDs out of the paths of those documents using the references in the document snapshots.
db.collectionGroup('followers').where('$uid.state', isEqualTo: 1)
Run that query, then iterate each DocumentSnapshot. Each snapshot will have a reference property that contains the full path of the document. Use the parent property of each reference to work your way up to the DocumentReference that refers to the channel, and add its documentID to a set. After you're done iterating, that set will contain everything you need.
I'm trying to do three things using Firestore:
fetch documents whose "contentType" field is "basic",
fetch documents that are created past a certain point in time. (5AM in the example.)
order documents according to the "like" count.
The documents in the contents collection look like this (leaving out unnecessary details):
{
date: Timestamp;
contentType: string;
response: {
like: Number;
};
}
And here is the iOS code:
let dateKey = "date"
let likeKey = "response.like"
let startDate = Date().setLocalHour(5)
let timestamp = Timestamp(date: startDate)
Firestore.firestore()
.collection(path: .contents)
.whereField(.contentType, isEqualTo: "basic")
.whereField(dateKey, isGreaterThanOrEqualTo: timestamp)
.order(by: dateKey, descending: true)
.order(by: likeKey, descending: true)
.limit(to: Constant.fetchLimit)
The order(by: dateKey) part is only necessary because Firebase demands it. Otherwise, an exception will be thrown, complaining that the where clause and the orderby clauses don't match.
I already created a composite index that says contents
contentType Ascending date Descending response.like Descending.
Expectation & Results
I'm expecting the documents to be ordered by the like count, and all documents to be "basic" type and created past 5 a.m. of today.
Instead, only the first two conditions are applied, and the third is completely ignored. Different mixes of two conditions work. It's the three conditions combined that is not working.
So my questions is, since Firebase documents don't say anything about having more than two multiple orderby & where combinations, is this a bug or something that's just not possible?
I found a workaround to the problem.
The original query required a composite index of three fields. So there is only one range comparison on date--contentType is used only for equality check--and two ordering on date and response.like, both of which compose the composite index.
Instead I decided to add a field in the contents document like so:
{
tags: string[]; // the new field.
date: Timestamp;
contentType: string;
response: {
like: Number;
};
}
And the new query looks like this:
Firestore.firestore()
.collection(path: .contents)
.whereField(.tags, arrayContains: Date.getDatabaseKey())
.whereField(.contentType, isEqualTo: "basic")
.order(by: likeKey, descending: true)
.limit(to: Constant.fetchLimit)
(Date.getDatabaseKey() just creates a yyyy-MM-dd string based on the current date.)
This query requires two composite indexes:
tags Arrays response.like Descending and contentType Ascending response.like Descending.
Fortunately, this works like a charm.
Added Info
The original query checked the collection for documents created after 5 AM of a certain day, and to me the range check seemed to be the problem.
As long as the Date.getDatabaseKey() method above generates a key with the same day for hours 5:00:00 to 4:59:59 of the next day, this new query has basically the same effect.
I have a requirement for sorting Contacts records by primary_contact_no.
My Contact fields contain primary_contact_no ,email , mobile_no.
this is no brainier....
BUT my view requires me to show mobile_no under Contact Number(view label) when primary_contact_no is not present.
Contacts.find(:all, :order => "primary_contact_no")
Now When i sort it by primary_contact , in the view , the records where these fields are absent get replaced with mobile_no but since they are already sorted by contact_no they appear at the bottom of the search result.
How can i combine the two results ( in case primary_contact is not present and carry out search on the combined record )
Is there any other solution to the problem where i can combine the row search records or something like that???
P.S.
I have used will paginate.
You could order once you retrieve them from the database.
So
contacts = Contact.all
u.sort!{|a,b| a.con_number<=> b.con_number}
Then in your Contact Model
def con_number
primary_contact_no||mobile_no
end
MySQL and PostgreSQL both have COALESCE function, so you can do something like:
Contacts.find(:all, :order => "COALESCE(primary_contact_no,mobile_no)")
to sort the records as you want. But beware, using sql functions and raw sql has its caveats. If you decide to switch databases, you have to check if each raw sql and sql function you used like this is supported in your new RDBMSI.
I would not sort the records in my application, as that means, I can not use pagination of will paginate to select limited data and have to retrieve full set of records, sort them and then use the relevant records based on pagination parameters. It will increase the response time consistently as the contacts table grows.