I have the following simplified data schema in Cloud Firestore:
[Users Collection]
-> "UserID1" { "displayName": "Luke", "profileImage": "someURL" }
-> "UserID2" { "displayName": "Han", "profileImage": "someURL" }
[Teams Collection]
-> "TeamId1" { "name": "Some Team Name", "members": ["UserId1", "UserId2"] }
--> [Todos Collection]
I want to show the user now all "Todo" documents of his teams and I don't know what is the best way.
1. Collection group queries - query by team ID
Two queries are needed for this. Each Todo document could have the parent team ID and be retrieved by Collection Group Query
e.g.
-> collectionGroup("todos").where("teamId", "in", ["teamId1", "teamId2", "teamId3"])
This looked like the best solution for me so far, but the disadvantage is that there is a limit of 10 values for the "IN" query https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html
2. Collection group queries - query by user-id
Here each todo document would get the user array from the team document. The query would be simple:
-> collectionGroup("todos").where("members", "arrayContains", "userId1")
but you have to maintain the array after a user leaves the team e.g. or when new team members join.
3. denormalized data - each user gets a subcollection of the todos
Ok, this is a bit over the top :) and the maintenance effort is even higher than with 2.
Is there another way for data modeling or some kind of best practice for my problem?
There is no best practice for data. You need to know that there is no "perfect", "the best" or "the correct" solution for structuring a Cloud Firestore database. However, we are usually structuring a Firestore database according to the queries that we want to perform.
So if one of the queries does the job, then you can go ahead with that. If the first one has some constraints, then use the secone one.
Related
I have this data model.
/tests/testId
{
"photos":{
79075240-f6c3-11ea-9d76-c328c656dbfc:{
"url":"",
"votes":0
},
7a394290-f6c3-11ea-bd51-5d216a9dfad9:{
"url":"urlperPhoto"
"votes":0
}
},
"moderated":false,
"owner":o8SIEjIByyaNciEgCFH5Kfh4ngh2,
"active":false,
"votes":0
}
/tests/testId/votes
{
photoId: 'xxx',
birthday: null,
sex: false,
votedDate: null
}
I would like to get a list of posts without which I voted. Because I have voted in other collections so I can add additional field for the post model.
Example:
votedUsers: [user1, user2, user3] or votedUsers: {user1: true, user2: true}
But... I don't have in firebase filter like "not exists". How can I display posts for the user, without this which he voted?
This sort of query is not possible with Firestore, as there are no indexes for data that doesn't exist. You can only query for data that does exist, and is indexed. This means that you will need to execute one query to get some possible items that the user has not voted on, then compare that to the results of another query that checks to see if that user has voted on, and remove those from the result set. Yes, this is difficult, and potentially expensive. But this is just not the sort of problem that Firestore is good at.
You might want to consider using another data along with Firestore in order to maintain this sort of relationship between users and things they have not yet seen or done. (It just won't scale like Firestore.)
See also:
Firebase Firestore Structure for getting un-seen trending posts - Social
How to query Cloud Firestore for non-existing keys of documents
I have an Orders collection. It contains a field called venueId. And I'm querying against this field using isEqualTo. The venueId is the firebase user uid. I also have a venues collection. It contains this venueId and also has a list of VenueAdmins ids(These ids are also firebase user uids )The app is a point of sales app(pos). I need to query the orders collections so that valueAdmins and venueId see the correct stream. Is quite easy to query with venueId.. venueId,isEqualto, uid. I'm wondering what's the best approach to allow the venueAdmins see the stream as well.
|-Orders // collection
order. //doc
venueId:'2344567788999999'
|-Venues // collection
venue. //doc
venueAdmin: ['3333333333333','55555555555555555']
venueId:'2344567788999999'
My query builder so far: queryBuilder: (query) => query.where('venue.id', isEqualTo: uid)
Firestore does not have the capability to "join" documents from different collections in a single query. A single query can only consider documents in single collection at a time. The way you have your data structured now, it will require at least two queries. First, to find a venue, then second, to find the orders for an admin in a venue.
The only way to make this easier from the perspective of queries is to denormalize your data by duplicating venue data into the order documents. If each order also had a list of admins, then you could reduce this down to a single query.
I am building an iOS app that is using Cloud Firestore (not Firebase realtime database) as a backend/database.
Google is trying to push new projects towards Cloud Firestore, and to be honest, developers with new projects should opt-in for Firestore (better querying, easier to scale, etc..).
My issue is the same that any relational database developer has when switching to a no-SQL database: data modeling
I have a very simple scenario, that I will first explain how I would configure it using MySQL:
I want to show a list of posts in a table view, and when the user clicks on one post to expand and show more details for that post (let say the user who wrote it). Sounds easy.
In a relational database world, I would create 2 tables: one named "posts" and one named "users". Inside the "posts" table I would have a foreign key indicating the user. Problem solved.
Poor Barry, never had the time to write a post :(
Using this approach, I can easily achieve what I described, and also, if a user updates his/her details, you will only have to change it in one place and you are done.
Lets now switch to Firestore. I like to think of RDBMS's table names as Firestore's collections and the content/structure of the table as the documents.
In my mind i have 2 possible solutions:
Solution 1:
Follow the same logic as the RDBMS: inside the posts collection, each document should have a key named "userId" and the value should be the documentId of that user. Then by fetching the posts you will know the user. Querying the database a second time will fetch all user related details.
Solution 2:
Data duplication: Each post should have a map (nested object) with a key named "user" and containing any user values you want. By doing this the user data will be attached to every post it writes.
Coming from the normalization realm of RDBMS this sounds scary, but a lot of no-SQL documents encourage duplication(?).
Is this a valid approach?
What happens when a user needs to update his/her email address? How easily you make sure that the email is updated in all places?
The only benefit I see in the second solution is that you can fetch both post and user data in one call.
Is there any other solution for this simple yet very common scenario?
ps: go easy on me, first time no-sql dev.
Thanks in advance.
Use solution 1. Guidance on nesting vs not nesting will depend on the N-to-M relationship of those entities (for example, is it 1 to many, many to many?).
If you believe you will never access an entity without accessing its 'parent', nesting may be appropriate. In firestore (or document-based noSQL databases), you should make the decision whether to nest that entity directly in the document vs in a subcollection based on the expect size of that nested entity. For example, messages in a chat should be a subcollection, as they may in total exceed the maximum document size.
Mongo, a leading noSQL db, provides some guides here
Firestore also provided docs
Hope this helps
#christostsang I would suggest a combination of option 1 and option 2. I like to duplicate data for the view layer and reference the user_id as you suggested.
For example, you will usually show a post and the created_by or author_name with the post. Rather than having to pay additional money and cycles for the user query, you could store both the user_id and the user_name in the document.
A model you could use would be an object/map in firestore here is an example model for you to consider
posts = {
id: xxx,
title: xxx,
body: xxx,
likes: 4,
user: {refId: xxx123, name: "John Doe"}
}
users = {
id: xxx,
name: xxx,
email: xxx,
}
Now when you retrieve the posts document(s) you also have the user/author name included. This would make it easy on a postList page where you might show posts from many different users/authors without needed to query each user to retrieve their name. Now when a user clicks on a post, and you want to show additional user/author information like their email you can perform the query for that one user on the postView page. FYI - you will need to consider changes that user(s) make to their name and if you will update all posts to reflect the name change.
I had to rephrase this question since it was a bit misleading (my fault).
Here is my dilemma, let's say I have a party collection:
parties {
status: "open",
invitees: [56486,68978,897650], # user ids of invited users
scheduled_at: 1948089050 # timestamp
}
I'd like to query only "open" parties, that I'm invited to (my user id in the invitees array), and sorted by scheduled_at
I could solve the first part of querying the array by turning it into a hash (thanks to #renaud and #james poag):
parties {
status: "open",
invitees: {
56486: true,
68978: true,
897650: true
}
scheduled_at: 1948089050
}
Now performing this:
db.collection('parties').where('status', '==', 'open').where('invitees.56486', '==', true').orderBy('scheduled_at')
Results in a firebase error asking me to make a composite index for status + invitees.56486 + scheduled_at. as you can see it's impractical for me to add an index for each user id.
Any ideas?
It looks like you're trying to make a query against a schema that doesn't really support that query. You're going to have to adjust your schema (possibly duplicating data between collections) to support your intended query. This sort of practice is normal for NoSQL type databases.
You're going to need a new collection that relates a single party with a single invitee, one for each combination, that effectively serves as a "join" between them:
party-invitees
- party_id
- party_status ("open")
- party_scheduled_at
- attendee_id
Now you can find out which open parties an attendee is invited to:
db.collection('party-invitees')
.where('party_status', '==', 'open')
.where('attendee_id', '==', 'whatever')
.orderBy('party_scheduled_at')
Bear in mind that you'll have to change this collection along with any other collections with the same data as they change. Fortunately, batch writes and transactions make this easier to do atomically.
Regarding the actual limitations in querying data based on subcollections values, what is the suggested way to manage multitenancy in Firestore?
I would like to be able to retrieve and limit access to data related to entities/companies the user is part of.
Example data structure :
/companies/{companyId}/users/
/companies/{companyId}/users/{user}/roles
/companies/{companyId}/docs/
Can /companies/{companyId}/users/ be a collection?
How can I only retrieve companies where user own a role in /companies/{companyId}/users ?
Firestore paths alternate from collection to document and back again:
/collection/document/subcollection/subdocument
So yes, in this case, you would have collections of companies, users, and docs. Collections are also implicit in that they are created automatically when documents exist in them, and removed when no documents exist in them.
At present, subcollection queries (e.g. "all users in a given company") aren't supported, so you'll have to structure your query the other way around: having a users collection with company as a property, when performing a query to find all users in that company.
ref.collection('users').where('company', '==', 'ACME').get().then((document) => {/* Do stuff here */});