This is my Firebase database inside "/articles", which has loads of articles inside. A user can (using his/her own article), list other articles that correspond to certain conditions. In order for a article to pass the query test, it has to be of category that the user's article has listed inside "tradableCategories", while also THAT article needs to have the user's article's category within its "tradableCategories".
Here’s the database structure:
"articles": {
"article1": {
"title": "Car",
"category": "vehicles",
"owner": "user1",
"tradableCategories": {
"furnishings": true,
"other": true,
"vehicles": true
},
"category_tradableCategories": {
"vehicles_furnishings": true,
"vehicles_other": true,
"vehicles_vehicles": true
}
},
"article2": {
"title": "Bike",
"category": "vehicles",
"owner": "user2",
"tradableCategories": {
"furnishings": true,
"other": true
"vehicles": true,
},
"category_tradableCategories": {
"vehicles_furnishings": true,
"vehicles_other": true,
"vehicles_vehicles": true
}
},
"article2": {
"title": "Couch",
"category": "furnishings",
"owner": "user2",
"tradableCategories": {
"furnishings": true,
"other": true,
"vehicles": true
},
"category_tradableCategories": {
"furnishings_furnishings": true,
"furnishings_other": true,
"furnishings_vehicles": true
}
},
...
}
user1 owns article1, which wants to find articles that are within furnishings, other and vehicles. Those articles that match the conditions also have to look for article1’s set category. The query can be done easily using SQL:
SELECT *
FROM articles
WHERE category = ’vehicles’ /* This is article1’s category */
AND find_in_set(category, :tradableCategories) /* :tradableCategories is a stringified, comma-separated set of article1’s tradableCategories: “furnishings,other,vehicles” */
AND NOT owner = ‘user1’
As you’ve seen in the database structure. I have included another object called “category_tradableCategories”. I’ve seen various answers here on Stack Overflow that explain how to search for items using two conditions combined into one. This could’ve worked but means that I have to initiate 3 Firebase queries since I cannot combine three (or more) different categories within tradableCategories.
I am afraid this is too complicated for Firebase, but if there is any efficient solution to this I’d like some help. Thank you!
In relational databases you often first define your data model to match with the data you want to store and then write queries for the use-cases of your app. In NoSQL databases you typically use the inverse logic: you make a list of your app's use-cases and then define your data model to match those.
If Firebase's API doesn't directly support the query you want to build, you'll typically have to change/augment your data model to allow that query. This will lead to storing more data and more complex updates, but the advantage is that you have faster and simpler read operations.
So in your scenario: you want a list of articles in one of three categories that is not owned by the current user. The most direct mapping of that requirement would be to literally store that list:
user_articles
$uid
categories_1_2_3
articlekey1: true
articlekey2: true
This would make the query trivial: ref.child("user_articles").child(currentUser.uid).child(categories).on("child_added"....
Now this may be taking the denormalization and duplication a bit too far. We'd need a separate list for each user/category combination. So an article in 3 categories with 10 users would end up in 60 lists.
More likely you'll want to keep these articles-per-categories in a single list across all users. For example:
articles_by_category_with_owner
category_1
articlekey1: uid1
articlekey2: uid2
articlekey3: uid1
category_2
articlekey1: uid1
articlekey2: uid2
category_3
articlekey1: uid1
articlekey3: uid1
Now you can get all article keys with category_1 with ref.child("articles_by_category_with_owner").child(category).on("child_added"... and then do the "not owned by the current user" filtering client-side.
In the above list I've also removed the multiple-categories. That does mean that you'll need to read a node for each category. But this is actually not as slow as you may expect, since Firebase pipelines these requests (see link below).
Further recommended reading/viewing:
NoSQL data modeling
Firebase for SQL developers
Questions/answers from this list
Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly
Query based on multiple where clauses in firebase
Related
I have a firebase database like this structure:
-groups
--{group1id}
---groupname: 'group1'
---grouptype: 'sometype'
---groupmembers
----{uid1}:true
----{uid2}:true
--{group2id}
---groupname: 'group2'
---grouptype: 'someothertype'
---groupmembers
----{uid1}:true
----{uid3}:true
----{uid4}:true
Now, I am trying to pull groups of authenticated user. For example for uid1, it should return me group1id and group2id, and for example uid3 it should just return group2id.
I tried to do that with this code:
database().ref('groups/').orderByChild('groupMembers/' + auth().currentUser.uid).equalTo('true').on('value' , function(snapshot) {
console.log('GROUPS SNAPSHOT >> ' + JSON.stringify(snapshot))
})
but this returns null. if I remove "equalTo" and go it returns all childs under 'groups'.
Do you know any solution or better database structure suggestion for this situation ?
Your current structure makes it easy to retrieve the users for a group. It does not however make it easy to retrieve the groups for a user.
To also allow easy reading of the groups for a user, you'll want to add an additional data structure:
userGroups: {
uid1: {
group1id: true,
group2id: true
},
uid2: {
group1id: true,
group2id: true
},
uid3: {
group2id: true
},
uid3: {
group2id: true
}
}
Now of course you'll need to update both /userGroups and /groups when you add a user to (or remove them from) a group. This is quite common when modeling data in NoSQL databases: you may have to modify your data structure for the use-cases that your app supports.
Also see:
Firebase query if child of child contains a value
NoSQL data modeling
Many to Many relationship in Firebase
The structure of the table is:
chats
--> randomId
-->--> participants
-->-->--> 0: 'name1'
-->-->--> 1: 'name2'
-->--> chatItems
etc
What I am trying to do is query the chats table to find all the chats that hold a participant by a passed in username string.
Here is what I have so far:
subscribeChats(username: string) {
return this.af.database.list('chats', {
query: {
orderByChild: 'participants',
equalTo: username, // How to check if participants contain username
}
});
}
Your current data structure is great to look up the participants of a specific chat. It is however not a very good structure for looking up the inverse: the chats that a user participates in.
A few problems here:
you're storing a set as an array
you can only index on fixed paths
Set vs array
A chat can have multiple participants, so you modelled this as an array. But this actually is not the ideal data structure. Likely each participant can only be in the chat once. But by using an array, I could have:
participants: ["puf", "puf"]
That is clearly not what you have in mind, but the data structure allows it. You can try to secure this in code and security rules, but it would be easier if you start with a data structure that implicitly matches your model better.
My rule of thumb: if you find yourself writing array.contains(), you should be using a set.
A set is a structure where each child can be present at most once, so it naturally protects against duplicates. In Firebase you'd model a set as:
participants: {
"puf": true
}
The true here is really just a dummy value: the important thing is that we've moved the name to the key. Now if I'd try to join this chat again, it would be a noop:
participants: {
"puf": true
}
And when you'd join:
participants: {
"john": true,
"puf": true
}
This is the most direct representation of your requirement: a collection that can only contain each participant once.
You can only index known properties
With the above structure, you could query for chats that you are in with:
ref.child("chats").orderByChild("participants/john").equalTo(true)
The problem is that this requires you to define an index on `participants/john":
{
"rules": {
"chats": {
"$chatid": {
"participants": {
".indexOn": ["john", "puf"]
}
}
}
}
}
This will work and perform great. But now each time someone new joins the chat app, you'll need to add another index. That's clearly not a scaleable model. We'll need to change our data structure to allow the query you want.
Invert the index - pull categories up, flattening the tree
Second rule of thumb: model your data to reflect what you show in your app.
Since you are looking to show a list of chat rooms for a user, store the chat rooms for each user:
userChatrooms: {
john: {
chatRoom1: true,
chatRoom2: true
},
puf: {
chatRoom1: true,
chatRoom3: true
}
}
Now you can simply determine your list of chat rooms with:
ref.child("userChatrooms").child("john")
And then loop over the keys to get each room.
You'll like have two relevant lists in your app:
the list of chat rooms for a specific user
the list of participants in a specific chat room
In that case you'll also have both lists in the database.
chatroomUsers
chatroom1
user1: true
user2: true
chatroom2
user1: true
user3: true
userChatrooms
user1:
chatroom1: true
chatroom2: true
user2:
chatroom1: true
user2:
chatroom2: true
I've pulled both lists to the top-level of the tree, since Firebase recommends against nesting data.
Having both lists is completely normal in NoSQL solutions. In the example above we'd refer to userChatrooms as the inverted index of chatroomsUsers.
Cloud Firestore
This is one of the cases where Cloud Firestore has better support for this type of query. Its array-contains operator allows filter documents that have a certain value in an array, while arrayRemove allows you to treat an array as a set. For more on this, see Better Arrays in Cloud Firestore.
I'm making an app with database structure like this:
{
"Locations": {
"location1": {
"name": "Nice location"
}
},
"User_posts": {
"user1": {
"post1": {
"location_name": "Nice location",
"location_id": "location1",
"description": "Wow!"
},
"post2": {
"location_name": "Nice location",
"location_id": "location1",
"description": "Nice"
}
}
}
If I have to change location1 name, how to change all location_name's that all users posts have? I have to download all the data before and update it or there is other method?
I think that using location id only to get location name for every location when user enters his posts is not a good idea.
By duplicating data you improve your read performance/scalability at the cost of decreased write performance. This is a normal trade-off in NoSQL databases and in highly scaleable systems in general.
If you want to update the location_name of all posts, you will indeed have to query the posts and update each. If you need to do this regularly, consider keeping a separate lookup list for each location to find the posts where it used. Such an inverted index is another common occurrence in NoSQL databases.
I covered strategies for updating the duplicated data in my answer here: How to write denormalized data in Firebase
Coming from a relational/SQL background, this may initially feel uncomfortable, since it goes against the normalization rules we've been taught. To counter that feeling, I recommend reading NoSQL data modeling, watching Firebase for SQL developers and in general just read some more NoSQL data modeling questions.
You can add one more attribute to location1 , say isLocationOf , which will store all the user id or perhaps post id/post names. Like
"Locations": {
"location1": {
"name": "Nice location",
"isLocationOf": {
'post1': true,
'post2': true
}
}
}
Here isLocationOf is an attribute of Locations whose value is an object.Now if locations1's name gets changed then you can retrieve its isLocationOf object , iterate through it , get all posts id/name containing that location.Then use the post ids to update all entries having this address .
Also whenever you add new post , you have to add its post id/name to isLocation object.
im trying to use firebase to store and retrieve data for my application.. i know that it is recommended to denormalize data and that it may require data replication..
my scenario is as follows:
there are a number of users in the system..
there are a number of posts in the system..
any user should be able to get a list of posts for a particular user..
each posts has a number of users as participants..
i am tempted to use the following structure for this:
users: {
abc: {
name: 'UserA',
profilePicture: 'imageA.png'
},
pqr: {
name: 'UserB',
profilePicture: 'imageB.png'
},
xyz: {
name: 'UserC',
profilePicture: 'imageC.png'
},
...,
...,
...
},
posts: {
def: {
title: 'PostA',
users: {
abc: true,
def: true,
ghi: true,
...,
...,
...
}
},
stu: {
title: 'PostB',
users: {
abc: true,
xyz: true,
...,
...,
...
}
},
...,
...,
...
}
the issue with this is that if i need to show a list of users with each post, i will need to make a query to POST, and then make sequential calls to USER for each user inside that post to get the name/profilePicture data..
if i replicate the user info inside posts as well, the issue becomes that if a user later changes her profilePicture or name, then existing posts will still show the old data..
how can i structure this data better so these cases are efficient?
thanks..
Don't replicate data inside posts. Read Firebase Docs about structuring data
Best practices:
Avoid nesting data
Flatten data structures
if you include data in post you are breaking those 2 rules (and you don't want it).
Multiple calls are not bad.
On the firebase structure data section, it shows how to structure data with a many-many user-group situation. But, why they have used "referece":true on both the side instead of using a simple array od ids.
Like, it can be used like both the ways:
A user having array of groups
"groups" : [ "groupId1", "groupId2", ... ]
A user having
"groups": {
"groupId1" : true,
"groupId2" : true,
..
}
They have done it a second way. What is the reason for that?
Something was told at the Google I/O 2016 for that in some video. But, I'm unable to recall.
Example from structure your data:
// An index to track Ada's memberships
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
},
...
}
}
Firebase recommends against using arrays in its database for most cases. Instead of repeating the reasons here, I'll refer you to this classic blog post on arrays in Firebase.
Let's look at one simple reason you can easily see from your example. Since Firebase arrays in JavaScript are just associative objects with sequential, integer keys, your first sample is stored as:
"groups" : {
0: "groupId1",
1: "groupId2"
]
To detect whether this user is in groupId2, you have to scan all the values in the array. When there's only two values, that may not be too bad. But it quickly gets slower as you have more values. You also won't be able to query or secure this data, since neither Firebase Queries nor its security rules support a contains() operator.
Now look at the alternative data structure:
"groups": {
"groupId1" : true,
"groupId2" : true
}
In this structure you can see whether the user is in groupId2 by checking precisely one location: /groups/groupId2. It that key exists, the user is a member of groupId2. The actual value doesn't really matter in this case, we just use true as a marker value (since Firebase will delete a path if there's no value).
This will also work better with queries and security rules, because you now "just" needs an exists() operator.
For some great insights into this type of modeling, I highly recommend that article on NoSQL data modeling.