I have the following Firebase structure with:
A list of bakeries:
firebaseio.com/bakery/:id/stuffAboutTheBakery
And a list of bakers:
firebaseio.com/baker/:id/bakery/bakeryId
Bakers can provide bread to several bakeries so in the baker details I have an object that lists all the bakeries they supply using the bakery ID as a key and a value of true.
In my app I have a $firebaseObject with a baker. I want to get an array with the details of the bakeries they service. I can obviously just go to each end point but surely there is a better way. So my questions are:
Is this data structure what the documentation means by flat structure? Or is there a better way to structure this?
How do I query several specific bakeries into an array?
This is my current code:
self.baker = $firebaseObject(firebase.baker.child($stateParams.id));
self.baker.$loaded()
.then(function() {
self.bakeries = [];
for (var bakery in self.baker.bakeries) {
self.bakeries.push($firebaseObject(firebase.bakery.child(bakery)));
}
})
.catch(function(err) {
console.error(err);
});
Firebase team member here.
To answer your questions:
1 - This is exactly what the docs means by flat structure! What wouldn't be flat is if you put the bakery data underneath the baker. Separating the two is what we recommend.
2 - Since you have an object that stores the bakery ids for the baker you can get all the bakeries for that baker. You can synchronize that object and then for each of those bakeries you can create an array given it's id. Be careful though that you're not downloading too much data, as it will make your app slow.
I answered a similar question recently about clients and invoices. That should show you enough code to get your started.
Related
I've been reading the blog post https://firebase.googleblog.com/2019/06/understanding-collection-group-queries.html to better understand the collectionGroup queries.
Although, I still have one question: how can I limit the results to a specific ancestor. Let me explain myself.
Imagine I have companies that manufacture cars that have tyres. We have different brands of tyres, used in different cars. In the end, we have a many-to-many relationship. I know I should not use this term in the NoSQL world, but I call a dog a dog :-)
Anyway, my question is the following: If we have a shortage in a company A of a specific tyre brand (let's say Michelin), you would need to flag this tyre as out of stock. I would think to run a collectionGroup query such as:
db.collectionQuery("tyre")
.where("brand", "==", "Michelin")
.get()
.then(function (querySnapshot) {
// update flag accordingly
})
But that would update the stock of other companies.
My question is: how would you narrow the collectionGroup query results so you only update the tyres info from company A?
I could include the company A docRef in the tyres collection and use where() to narrow the results. It seems like a valid approach. Although, it would be a mix between a top-level collection and a subcollection. Is it best practice?
UPDATE
Actually, I'm following the example of the restaurants to put my hands on firebase/firestore. A restaurant can have multiple menus. A menu can have multiple items. Items can be reused and therefore present in multiple menus.
collection('restaurants').doc(..).collection('menus').doc(..).collection('items')
I like to think that's the best way to structure the data (vs. a top-level collection for the items). But items like Coffee can easily be found in multiple menus of multiple restaurants. If one restaurant is short on coffee, how can I update the coffee items for that specific restaurant using something like:
db.collectionQuery("items")
.where("name", "==", "Coffee")
.get()
.then(function (querySnapshot) {
// set available = false
})
If one restaurant is short on coffee, how can I update the coffee
items for that specific restaurant?
By using a collectionGroup query you could do like that:
db.collectionQuery('items')
.where('name', '==', 'Coffee')
.get()
.then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
const itemQuantity = doc.data().itemQuantity;
if (itemQuantity === 0) {
const restaurantRef = doc.ref.parent.parent.parent.parent;
return restaurantRef.update( {....})
}
});
});
by alternatively using the parent properties of DocumentReference and CollectionReference.
However, this may not be the most efficient and affordable way if you have a lot of restaurants, because your collectionGroup query will return a lot of records.
A more efficient way would be to keep a set of counters and watch them, through either Firestore listeners or Cloud Functions.
Finally, note an important point: you write "A menu can have multiple items. Items can be reused and therefore present in multiple menus". Note that items documents in
collection('restaurants').doc('r1').collection('menus').doc('m1').collection('items')
and in
collection('restaurants').doc('r1').collection('menus').doc('m2').collection('items')
are totally different documents. This is different from the SQL world where different records from one table can point to the same record of another table.
Conclusion: You should most probably have one itemsStock collection per restaurant, and each time one of the items is "consumed/ordered" you decrease its count by using FieldValue.increment(-1).
In other words, I advise to separate the collections of items that compose a menu from the one which holds the items counters (i.e. the itemsStock collection). The first ones are dedicated to menus items selection and the second one dedicated to managing the stock of the restaurant. When a guest/customer chooses/orders an item you only decrease the collection holding the items counters.
Update following your comment:
If you want to update all the "lasagna" items in all the menus of a restaurant (for example to add an ingredient, as you mentioned in your comment), a very common approach is indeed to modify all the corresponding docs (this is called data duplication in the NoSQL world).
You would use the exact code at the top of my answer: you query all the "lasagna" items documents in all the menus of the restaurant and update them. You could trigger this process by a Cloud Function that would "watch" a master collection in which you have reference items: each time you change a doc of this collection (i.e. an item) you update all the similar/corresponding items doc in the menus subcollections.
I could include the company A docRef in the tyres collection and use where() to narrow the results. It seems like a valid approach. Although, it would be a mix between a top-level collection and a subcollection. Is it best practice?
This is a common approach, since the only way to filter documents in a collection group query is using the fields of the documents. You can't use anything in the path of the document as a filter. It's common to duplicate data in NoSQL type databases in order to facilitate queries.
However, you probably don't want to have a top-level collection with the same name as child collections, if you want to limit the queries to just the child collections.
How would I go about filtering a set of records based on their child records.
Let's say I have a collection Item that has a field to another collection Bag called bagId. I'd like to find all Items where a field on Bags matches some clause.
I.e. db.Items.find( { "where bag.type:'Paper' " }) . How would I go about doing this in MongoDB. I understand I'd have to join on Bags and then link where Item.bagId == Bag._id
I used Studio3T to convert a SQL GROUP BY to a Mongo aggregate. I'm just wondering if there's any defacto way to do this.
Should I perform a data migration to simply include Bag.type on every Item document (don't want to get into the habit of continuously making schema changes everytime I want to sort/filter Items by Bag fields).
Use something like https://github.com/meteorhacks/meteor-aggregate (No luck with that syntax yet)
Grapher https://github.com/cult-of-coders/grapher I played around with this briefly and while it's cool I'm not sure if it'll actually solve my problem. I can use it to add Bag.type to every Item returned, but I don't see how that could help me filter every item by Bag.type.
Is this just one of the tradeoffs of using a NoSQL dbms? What option above is recommended or are there any other ideas?
Thanks
You could use the $in functionality of MongoDB. It would look something like this:
const bagsIds = Bags.find({type: 'paper'}, {fields: {"_id": 1}}).map(function(bag) { return bag._id; });
const items = Items.find( { bagId: { $in: bagsIds } } ).fetch();
It would take some testing if the reactivity of this solution is still how you expect it to work and if this would still be suitable for larger collections instead of going for your first solution and performing the migration.
I have two classes - _User and Car. A _User will have a low/limited number of Cars that they own. Each Car has only ONE owner and thus an "owner" column that is a to the _User. When I got to the user's page, I want to see their _User info and all of their Cars. I would like to make one call, in Cloud Code if necessary.
Here is where I get confused. There are 3 ways I could do this -
In _User have a relationship column called "cars" that points to each individual Car. If so, how come I can't use the "include(cars)" function on a relation to include the Cars' data in my query?!!
_User.cars = relationship, Car.owner = _User(pointer)
Query the _User, and then query all Cars with (owner == _User.objectId) separately. This is two queries though.
_User.cars = null, Car.owner = _User(pointer)
In _User have a array of pointers column called "cars". Manually inject pointers to cars upon car creation. When querying the user I would use "include(cars)".
_User.cars = [Car(pointer)], Car.owner = _User(pointer)
What is your recommended way to do this and why? Which one is the fastest? The documentation just leaves me further confused.
I recommend you the 3rd option, and yes, you can ask to include an array. You even don't need to "manually inject" the pointers, you just need to add the objects into the array and they'll automatically be converted into pointers.
You've got the right ideas. Just to clarify them a bit:
A relation. User can have a relation column called cars. To get from user to car, there's a user query and then second query like user.relation("cars").query, on which you would .find().
What you might call a belongs_to pointer in Car. To get from user to car you'd have a query to get your user and you create a carQuery like carQuery.equalTo("user", user)
An array of pointers. For small-sized collections, this is superior to the relation, because you can aggressively load cars when querying user by saying include("cars") on a user query. Not sure if there's a second query under the covers - probably not if parse (mongo) is storing these as embedded.
But I wouldn't get too tied up over one or two queries. Using the promise forms of find() will keep your code nice and tidy. There probably is a small speed advantage to the array technique, which is good while the collection size is small (<100 is my rule of thumb).
It's easy to google (or I'll add here if you have a specific question) code examples for maintaining the relations and for getting from user->car or from car->user for each approach.
I'm trying to send push notifications to users if one of their favorite foods appear on a dining menu on a given day. For example, if a user lists pizza, spaghetti, and potato as his favorite foods, and on a particular day, the menu contains pizza, the user would receive a push notification.
This is how I'm currently storing the favorite foods
PFInstallation *installation = [PFInstallation currentInstallation];
[installation addUniqueObject:foodName forKey:#"favorites"];
[installation saveInBackground];
In the available functions, I found these, but after closer inspection, they don't seem to be what I need
- (void)whereKey:(NSString *)key containedIn:(NSArray *)array;
- (void)whereKeyExists:(NSString *)key;
(I'll be using cloud code to retrieve the data, but I'm assuming there is an analogous javascript method.)
Is there a way to get a query of all the installation objects containing a given item in their favorites array?
Also, although I map each user to an array of foods, it seems more intuitive to map each food to an array of corresponding users. However, I don't want users to enter login info, so I'm trying to avoid the PFUser class, and I don't know if there is another way of adding a user to an array. I'm wondering if this is possible, and if it would make it easier to retrieve the information.
As per the documentation on Queries on Array Values, if you want to find rows where a value exists in an array:
[query whereKey:#"favorites" equalTo:#"pizza"];
This will return all rows where "pizza" is in the array column "favorites".
you can go for PFRelation Class on Parse.com for relational queries.
and for query you can go for blocks (sorry it's in swift coding)
let query : PFQuery = PFQuery(className: "GameScore")
query.whereKey("favorite", containedIn: ["item1", "item2"])
query.findObjectsInBackgroundWithBlock({(objects: AnyObject[]!, error: NSError!) in
// your code
})
I'm using Parse.com to manage my models, and I came to a problem that I couldn't find a good solution.
Let's say that I have to models:
Team: name, number, country
Member: name, Team (Pointer to Team)
I want to fetch ALL Teams, and include all it's Members in one single query. If this is not possible, I will have to run a query for every Team that I fetches.
Is it possible with Parse? I read their docs. but couldn't find a way to doit...
If the point is to get all of both members and teams, why not get all members and use
includeKey("Team")
to include all team objects in the members query?
On another note, when designing for parse (or any other NoSQL database), you should start with defining what queries you will make and then design your "schema".
Since you have a pointer to Team from Member, it seems that this is a one-to-many relationship. A team can have many members, but a member can only belong to one team.
So, what queries will you mostly perform?
Never "list all Teams a Member belongs to", because it can only be one.
You will query for members, and it would probably be nice to see the Team as well.
You will (apparently) query for Team(s) and need to get all members for that team.
Other queries related to Team or Member?
If you need a list of members in a Team, you could make "Members" a PFRelation from Team to Member. I know this seems odd if you're used to SQL databases, but that is not unusual in NoSQL databases.
Looking through the link in your post, my best guess is this:
var Member = Parse.Object.extend("Member");
var query = new Parse.Query(Member);
// Include the Team data with each Member
query.include("post");
query.find({
success: function(members) {
for (var i = 0; i < members.length; i++) {
// This does not require a network access.
var team = comments[i].get("team");
}
}
});
The above (untested) sample is modified from the section on include.
You may not be able to do what you want here, depending on the size of your members list and team list... I ran across this in the docs:
If you want to retrieve objects where a field contains a Parse.Object
that matches a different query, you can use matchesQuery. Note that
the default limit of 100 and maximum limit of 1000 apply to the inner
query as well, so with large data sets you may need to construct
queries carefully to get the desired behavior. In order to find
comments for posts containing images, you can do: