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:
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.
I need to do a query where I can show only specific data using an 'AND' statement or equivalent to it. I have taken the example which is displayed in the Firebase Documentation.
// Find all dinosaurs whose height is exactly 25 meters.
var ref = firebase.database().ref("dinosaurs");
ref.orderByChild("height").equalTo(25).on("child_added", function(snapshot) {
console.log(snapshot.key);
});
I understand this line is going to retrieve all the dinosaurs whose height is exactly 25, BUT, I need to show all dinosaurs whose height is '25' AND name is 'Dino'. Is there any way to retrieve this information?
Thanks in advance.
Actually firebase only supports filtering/ordering with one propery, but if you want to filter with more than one property like you said I want to filter with age and name, you have to use composite keys.
There is a third party library called querybase which gives you some capabilities of multy property filtering. See https://github.com/davideast/Querybase
You cannot query by multiple keys.
If you need to sort by two properties your options are:
Create a hybrid key. In reference to your example, if you wanted to get all 'Dino' and height '25' then you would create a hybrid name_age key which could look something like Dino_25. This will allow you to query and search for items with exactly the same value but you lose the ability for ordering (i.e. age less than x).
Perform one query on Firebase and the other client side. You can query by name on Firebase and then iterate through the results and keep the results that match age 25.
Without knowing much about your schema I would advise you to make sure you're flattening your data sufficiently. Often I have found that many multi-level queries can be solved by looking at how I'm storing the data. This is not always the case and sometimes you may just have to take one of the routes I have mentioned above.
Let's say we're building a blog using Parse.com's javscript sdk where users can follow eachother and have a feed.
The 'Follow' class includes a pointer to the followee & follower.
While the 'Post' class includes a pointer the creator (followee^).
if I now want to fetch all rows inside 'Post' where the creator equals followee and the user equals follower inside the 'Follow' class; then sort them using descending("createdAt"), how would one do that (efficiently)?
I have checked the api reference and there doesn't appear to be a straightforward answer to this issue: https://www.parse.com/docs/js/api/classes/Parse.Query.html
Any help is gladly welcome. Thanks.
This can be accomplished using a compound query, specifically matchesKeyInQuery. Compound queries only count as 1 API request.
First, create the inner query to find Follow objects where the requesting user is a follower.
var innerQuery = new Parse.Query("Follow");
innerQuery.equalTo("follower", request.user);
Next, create the outer query to find Post objects where their followee key matches the followee key in the results of our inner query.
var outerQuery = new Parse.Query("Post");
outerQuery.descending("createdAt");
outerQuery.matchesKeyInQuery("followee", "followee", innerQuery);
This will give you all of the Posts, in descending order by creation date, for all of the "followees" being followed the requesting user.
Cheers,
Russell
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 have a firebase model where each object looks like this:
done: boolean
|
tags: array
|
text: string
Each object's tag array can contain any number of strings.
How do I obtain all objects with a matching tag? For example, find all objects where the tag contains "email".
Many of the more common search scenarios, such as searching by attribute (as your tag array would contain) will be baked into Firebase as the API continues to expand.
In the mean time, it's certainly possible to grow your own. One approach, based on your question, would be to simply "index" the list of tags with a list of records that match:
/tags/$tag/record_ids...
Then to search for records containing a given tag, you just do a quick query against the tags list:
new Firebase('URL/tags/'+tagName).once('value', function(snap) {
var listOfRecordIds = snap.val();
});
This is a pretty common NoSQL mantra--put more effort into the initial write to make reads easy later. It's also a common denormalization approach (and one most SQL database use internally, on a much more sophisticated level).
Also see the post Frank mentioned as that will help you expand into more advanced search topics.