How can I achieve this Many-to-many relationship in Firebase? - firebase

I know Firebase does not support JOINs between nodes (like SQL Server does between tables), but that is exactly what I need to accomplish. Here's my situation:
I have a transactions node in Firebase like this (where I am including the category name for each transaction):
"transactions":
{
"-Jruazf35b9a_gAVmZBe":
{
payee: "McDonalds", amount: "2.35", category: "Eating Out"
}
"-JruadR11b4a_aTVmZFi":
{
payee: "Walmart", amount: "78.12", category: "Household"
}
"-Jruazf35b9a_AgvNWCq":
{
payee: "CapitalOne", amount: "150.00", category: "Debt"
}
"-JryJF2c33ijbjbBc24p":
{
payee: "FootLocker", amount: "107.54", category: "Personal Blow"
}
"-Jrz0T-aL61Vuw4SOqRb":
{
payee: "Starbucks", amount: "2.88", category: "Eating Out"
}
}
And I have a Categories node like this (where I am including the transactions under each category):
"categories":
{
"-Jruazf35b2a_gAVmZRy":
{
categoryname: "Eating Out",
categorytype: "Expense"
}
"transactions": {
"-Jruazf35b9a_AgvNWCq": {
payee: "McDonalds", amount: "2.35"
}
.
.
.
}
}
}
So far so good. My data is flat. I'm able to show the list of transactions with the category name (screenshot below) and I can show the list of transactions under each category in the expenses per category section (screenshot not shown here).
The problem I have is that if I rename a category the change is only reflected for future transactions. Past transactions show the old category name.
This is obvious due to the way I'm saving the data. So my first logical reaction was to save the category unique ID in the transactions node instead of the category name. However, that presents the challenge where, in my SQL Server little brain, I would need a JOIN so I can get the list of transactions and also include the name of the category for each transaction.
How can I structure my data so that I can:
show a list of transactions including the name of the category (as it does today)
allow a user to rename a category and show the change reflected for ALL transactions (past and future)
show a list of transactions under each category (I think the current approach would still be valid)

Joining data from two lists is inherently a slow operation, especially on NoSQL databases.
I would recommend keeping the categoryName and adding a categoryId. That way you can show your current screen with a single read, but also link to the category. For how to deal with updating the categoryName in each transaction, see How to write denormalized data in Firebase and https://medium.com/#collardeau/es6-promises-with-firebase-76606f36c80c.
Alternatively: the list of categories is likely to be relatively small. So you could also pre-load it and perform a client-side lookup while you're iterating the transactions.

Related

Firebase REST API query with different keys

So this is the structure of my Firebase DB right now, I am using the Firebase REST API:
"company": {
company1_id {
id: company_id,
userId: userid,
name: name
//someotherstuff
}
company2_id {
id: company_id,
userId: userid,
name: name,
//someotherstuff
}
}
Soo, right now I am getting the companies belonging to one user by calling :
"firebasedbname.firebaseio.com/company.json?orderBy="userId"&equalTo=userId"
This works perfectly fine and gets the corresponding data, but now I want it to order the companies alphabetically by name, and then i try this:
"firebasedbname.firebaseio.com/company.json?orderBy="name"&equalTo=userId"
But this time, it returns no data! Even though i have added .indexOn: "name" to the company node.Any help will be aprreciated.
As explained in the doc, if you want to filter data you need to first "specify how you want your data to be filtered using the orderBy parameter", and then you need to "combine orderBy with any of the other five parameters: limitToFirst, limitToLast, startAt, endAt, and equalTo".
So if you added "an .indexOn: "name" to the company node", it means that you intend to query as follows:
https://xxxx.firebaseio.com/company.json?orderBy="name"&equalTo="companyName"
You cannot order by (company) name and filter on userId.
If you want to get all the companies corresponding to a specific user and order them by the company name, you will need to use ?orderBy="userId"&equalTo=userId" and do the sorting in the client/application calling the REST API.

Firestore features and limitations on different data structure model

I have created app whose structure looks like this. Current structure for one company only.
let current = {
products: {
product1: {}//...
},
customers: {
customer1: {},// ...
},
orders: {
order1: {},// ...
},
}
Now I have design data structure to make it multi company app. Suppose companies are ABC, PQR, XYZ but the customers are same. So, a customer can see products from different companies.
Option 1: Add Company property in every lists doc.
let option1 = {
company: {
products: {
product1: {
company: 'ABC'
},
},
customers: { //Also we can put it at root with field company as array. Customers are not primary concern
customer1: {
company: 'PQR'
}
},
orders: {
order1: {
company: 'ABC'
}
},
}
}
My Remarks: I have to put company property in every list which may be more than these. It doesn't look like right solution. Querying products in different companies looks easy.
Option 2: Copy the current root structure for different companies.
let option2 = {
company1: {
products: {
product1: {}
},
customers: {//Also we can put it at root with field company as array. Customers are not primary concern
customer1: {},
},
orders: {
order1: {},
},
},
company2: {
products: {
product1: {}
},
customers: {
customer1: {},
},
orders: {
order1: {},
},
},
// ...
}
My Remarks: I don't know firestore limitations and upcoming features. Querying products in different companies may not be easy.
let option3= {} //your suggestions.
In same firestore project, assume customer handling will not be a problem.
What can be done here? What are things I am missing?
Products
It is unusual for several companies to sell the same product, without wanting to create their own stock item, description, price, etc. Also, if you have an array / map of companies who sell the product, within the product document, customers will see where they can buy the product and there may be no loyalty to any one company. If this is what you're hoping to achieve, then your options could work for you.
Customers
Companies will also want to keep certain data about their customers separate from other companies.
My proposal
I would suggest that you create a Cloud Firestore collection companies and have a document for each company. Within that document, you can create sub-collections for customers, orders and products.
A separate users collection at the root level will allow users to maintain their own data and allow companies to collect whatever they need to keep their records up to date. Adding public and private sub-collections of the user data can manage this easily for you.
Todd Kerpelman from the Firebase team, has made an excellent video which will really help with data modelling in Cloud Firestore.
I hope that this helps

Updating a string in multiple locations in firebase

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.

Filter and sort on multiple values with Firebase

I'm building a social app using Firebase. I store posts in Firebase like this:
posts: {
"postid": {
author: "userid"
text: "",
date: "timestamp"
category: "categoryid"
likes: 23
}
}
Each post belong to a category and it's possible to like posts.
Now, I'm trying to show posts that belong to a specific category, sorted by the number of likes. It's possible I also want to limit the filter by date, to show only the most recent, most liked posts in a category. How can I do this?
Firebase query functionality doesn't seem to support multiple queries like this, which seems strange...
You can use only one ordering function with Firebase database queries, but proper data structure will allow you to query by multiple fields.
In your case you want to order by category. Rather than have category as a property, it can act as an index under posts:
posts: {
"categoryid": {
"postid": {
author: "userid"
text: "",
date: "timestamp",
category: "categoryid",
likes: 23
}
}
}
Now you can write a query to get all the posts underneath a specific category.
let postsRef = Firebase(url: "<my-firebase-app>/posts")
let categoryId = "my-category"
let categoryRef = postsRef.childByAppendingPath(categoryId)
let query = categoryRef.queryOrderedByChild("date")
query.observeEventType(.ChildAdded) { (snap: FDataSnapshot!) {
print(snap.value)
}
The code above creates a reference for posts by a specific category, and orders by the date. The multiple querying is possible by the data structure. The callback closure fires off for each individual item underneath the specified category.
If you want to query further, you'll have to do a client-side filtering of the data.

handling complex queries in firebase

I'm developing a shopping cart mobile application using ionic framework and FIREBASE as back-end . I have a requirement that I need to join JOIN, SORT, FILTER and PAGINATE by multiple data attributes.
Sample data structure is given below.
products : {
prod1:{
name: Samsung-s4,
type: pro,
category: Phone,
created_datetime: 1426472828282,
user: user1,
price: 400
},
prod2:{
name: iPhone 5s,
type: pro,
category: Phone,
created_datetime: 1426472635846,
user: user2,
price: 500
},
prod3:{
name: HP Laptop i3,
type: regular,
category: Computer,
created_datetime: 1426472111111,
user: user1,
price: 600
}
}
user_profiles : {
user1:{
name: abc_user,
display_name: ABC,
email: abc#mail.com
},
user2:{
name: xyz_user,
display_name: XYZ,
email: xyz#mail.com
}
}
I need to query the products using multiple ways. two simple examples given below.
1) Get all products with Pagination, where type is "pro" then join with user_profiles and SORT by created date.
2) Get all products with Pagination, filter by category then join with user_profiles and SORT by created date.
Like above there will be more and more filtering options coming in Eg: price.
My main problem is, I couldn't find straight forward way of doing this using FIREBASE query options. Also I referred firebase util documentation but there also I don't see a way of getting this done.
As far as I see only way to get this done is, do the majority of processing in client end by getting all the data (or majority of the data) in to client side and do the SORT / FILTER / PAGINATE in client end.
But we are expecting thousands of records in these schemas, therefore there will be a huge performance impact if we do the processing in client end !!
Appreciate your expertise/support to solve this problem.
Thank you in advance.
UPDATE:
I changed the data structure (as webduvet explained) and tried with firebase-util but failed to achieve what i want. CRITERIA :Products -> Filter By type/pro -> Sort By products.created_date
type: {
pro:{
product1: user1,
product3: user1,
...
},
regular:{
...
}
}
firebase-util - angularfire code
var list = $firebase(new Firebase.util.NormalizedCollection(
ref.child("products").orderByChild('created_datetime'),
ref.child('type').child('pro')
).select(
{key: "products.name" , alias: 'name'}
).ref()).$asArray();
"products" will have hundred thousand records, so we have to make sure we restrict as much possible in firebase end rather than handling in client end.
Please help !
This has to be done not by queries but by database design. You need to store data in de-normalised way for example:
type: {
pro:{
product1: user1,
product3: user1,
...
},
regular:{
...
}
}
the above structure will give you option to query all pro products and retrieve the user id as well. Firebase offer good sorting mechanism so there should not be a problem. The more complex query you require the more complex the data structure will be needed and the more denormalized data you will have.
But as pointed out by #Swordfish0321 sql type of db might suit you much better after all.

Resources