my receipe data in firebase
![my receipe data in firebase][1]
My receipe data looks like the following
receipe/1/12: "{"itemno":"1","receipeno":"12","receipedescript..."
How can I filter all receipes where receipeno = 12 or starting with receipeno = 12, irrespective of itemno?
I have tried the following, but got no results or errors?
Also tried startAt
this.query = this.db.database.ref("receipe").orderByChild("receipeno").equalTo("12").limitToFirst(100);
BTW: this.query = this.db.database.ref("receipe") this returns all data
and this.db.database.ref("receipe/1") returns all receipe for itemno == 1.
I have updated the data to not use opaque strings.
[Updated db so as not to use opaque strings][2]
and have tried the following.
this.query = this.db.database.ref("receipe").orderByChild("itemno").equalTo("1").limitToFirst(100);
And
this.query = this.db.database.ref("receipe").orderByChild("receipeno").equalTo("r1").limitToFirst(100);
Firebase is not going to help you with deep queries. It filters and sorts at one level. Therefore, there is no way to say, "find me all the recipes with an id (or recipeno) of 12, under any node under recipes.
If you had a single level, then you would do orderByKey().equalTo("12"). Of course this will limit you to one recipe per ID.
If you are going to have multiple recipes with the number 12 and want to do this kind of querying, you need to change your database structure--essentially getting rid of the intermediate level where you currently have keys of 1, 2 etc. Most likely in this case you would use automatically generated keys of the kind that result from push, such as:
+ recipes
+ autokey1: {itemno, recipeno, recipedescription}
+ autokey2: {itemno, recipeno, recipedescription}
Then you could say
orderByChild('recipeno').equalTo('12')
This assumes, of course, as pointed out in a comment, that you are saving the data as objects, not as stringified JSON, which Firebase will never be able to query against the insides of.
This is a good case study of the notion that you should design your Firebase database structure carefully in advance to allow you to do the kinds of querying you will need to do.
By the way, this question has nothing to do whatsoever with Angular.
Related
I am trying to make a chat app with firebase real time database. I store chats between 2 users as an JSON array in firebase.
Initially I when user used to send message, I used to set whole chat array. But I soon realised that's not a good idea as array will grow.
await firebase.database().ref("chats/" + chatId).set(messages)
When I checked official documentation on how to handle Arrays in firebase, I realised I can use push() instead. I am doing something like this now:
let messageRef = firebase.database().ref("chats/" + chatId)
await messageRef.push().set(message);
It causes 2 problems, one is it generates unique keys and other is when I fetch the chat it returns JSON object instead of Array.
I want something like this:
Instead after using push I am getting:
What's the best way to achieve what I want?
As you can see calling push() will generate a key for you.
Two solutions:
• You let Firebase generate unique key for you and transform the JSON object into an array on client side:
firebase.database().ref("chats/" + chatId).once("value").then(data => {
console.log(Object.values(data.val()));
});
Read more here https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/values
• You use your own keys ("0", "1", "2"...)
firebase.database().ref("chats/" + chatId).child(key).set(msg);
The inconvenience here is you need to know the key. You can use messages.length or store a new field with the size of messages (use transaction to increment it). Don't forget to write rules to prevent users overwriting.
If you can you should use the first solution.
Dataloader is able to batch and cache requests, but it can only be used by either calling load(key) or loadMany(keys).
The problem I am having is that sometimes I do not know they keys of the items I want to load in advance.
I am using an sql database and this works fine when the current object has a foreign key from a belongsTo relation with another model.
For example a user that belongs to a group and so has a groupId. To resolve the group you would just call groupLoader.load(groupId).
On the other hand, if I wanted to resolve the users within a group, of which there could be many I would want a query such as
SELECT * from users where user.groupId = theParticularGroupId
but a query such as this doesn't use the keys of the users and so I am not sure how make use of dataloader.
I could do another request to get the keys like
SELECT id from users where user.groupId = theParticularGroupId
and then call loadMany with those keys... But I could have just requested the data directly instead.
I noticed that dataloader has a prime(key, value) function which can be used to prime the cache, however that can only be done once the data is already fetched. At which point many queries would already have been sent, and duplicate data could have been fetched.
Another example would be the following query
query {
groups(limit: 10) {
id
...
users {
id
name
...
}
}
}
I cannot know the keys if I am searching for say the first or last 10 groups. Then once I have these 10 groups. I cannot know the keys of their users, and if each resolver would resolve the users using a query such as
SELECT * from users where user.groupId = theParticularGroupId
that query will be executed 10 times. Once the data is loaded I could now prime the cache, but the 10 requests have already been made.
Is there any way around this issue? Perhaps a different pattern or database structure or maybe dataloader isn't even the right solution.
You'll want a dataloader instance for the lookup you can do, in this case you have a group ID and you want the users:
import DataLoader from 'dataloader';
const userIdsForGroupLoader = new DataLoader(groupIds => batchGetUsersIdsForGroups(groupIds));
Now your batchGetUsersForGroups function is essentially has to convert an array of group IDs to an array of arrays of users (one array of user IDs for each group).
You'd start off with an IN query:
SELECT id from users where user.groupId in (...groupIds)
This will give you a single result set of users, which you'll have to manipulate, by grouping them by their groupId, the array should be ordered according to the original array of groupIds. Make sure you return an empty array for groupIds that don't have any users.
Note that in this we're only returning the user ids, but you can batch fetch the users in one go once you have them. You could tweak it slightly to return the users themselves, you'll have to decide for yourself if that's the right approach.
Everything I mention in this article can be achieved using clever use of Dataloader. But the key takeaway is that the values you pass to the load/loadMany functions don't have to correspond to the IDs of the objects you're trying to return.
I have the following database structure in Firebase:
firebaseDB
Each object contains category and sectionIndex which correspond to a chapter and its sort order.
I am trying to grab those datas in order to display it in my menu.
I can acces to the DB with this code below:
firebase.database().ref().on("value", snapshot => {console.log(snapshot.val());} ,error => {console.log("Error: " + error.code);} );
There is the result in the console:
consoleScreen
My question is how can I enter in each object (0,1,2,3,4,...,45), grab the category value and sort it in order following the sectionIndex and avoid to duplicate the value.
Firebase has a set of queries outlined in the docs which allow you to do some basic manipulation of data. In your case, it looks like you'll want to use orderByChild to sort your entire database object by sectionIndex. You can then specify how many you want to get at one time using the limitToFirst or limitToLast parameters.
Then, when you get your snapshot, just grab all the categoryValues and do what you want with them. Rather than using .on("value") like you are currently, it's probably easier to use .on('child_added') like in their example:
var ref = firebase.database().ref("dinosaurs");
ref.orderByChild("height").on("child_added", function(snapshot) {
console.log(snapshot.key + " was " + snapshot.val().height + " m tall");
});
It shouldn't be to hard for you to apply this to your case.
Remember also that depending on what else you want to do with this data, and depending on how many items your're going to have, it may be easier to do the sorting on the client side (i.e. rather than letting Firebase do the sorting, make your computer/app do the sorting)
Ok, so I've been reading and reading and searching and searching and strangely it doesn't seem like my scenario has been really covered anywhere.
I have an app that creates a list of products. I want a simple view that can sort the products and page through them.
Fore reference here is a simple representation of the data in Firebase.
app
stock
unique_id
name
url
imageUrl
price
When creating the list I have multiple threads using the push method on my firebase references:
new Firebase(firebaseUrl).child('stock').push({
name: "name",
price: 123
});
This gives me a lovely "hash" collection on the stock property of the app.
So what I'd now like to do is have a table to sort and page through the records that were placed in the stock hash.
I make a GET request to my server to a url like /stock?limit=10&skip=10&sort=name%20asc. This particular url would be the second page where the table contained 10 records per page and was sorted by the name property in ascending order.
Currently in my query handler I have this:
var firebaseRef = new Firebase(firebaseUrl).child('stock');
if (this.sortDesc) {
firebaseRef = firebaseRef
.orderByChild(this.sortProperty)
.endAt()
.limitToFirst(this.limitAmount);
} else {
firebaseRef = firebaseRef
.orderByChild(this.sortProperty)
.limitToFirst(this.limitAmount);
if (this.skipAmount > 0) {
firebaseRef = firebaseRef.startAt(this.skipAmount);
}
}
firebaseRef.once('value', function (snapshot) {
var results = [];
snapshot.forEach(function (childSnapshot) {
results.push(childSnapshot.val());
});
callback(null, results);
});
I'm running into a couple of problems. I'm going to split this into two cases, ascending and descending queries.
Ascending query
The orderByChild and limitToFirst seems to work correctly in the sorting ascending case. This means I can change which property has an ascending sort and how many results to return. What I am not able to get to work is skipping n records for paging to work. In the example query above I'm going to the second page. I do not get results 11-20, but I instead get the same 10 records as the first page.
Descending query
In this case I cannot begin to figure out how to tell Firebase to order by a property of the object identified by the unique key in a descending fashion. The closest I've read is to use endAt() and then limit. Docs say the limit is deprecated plus this still doesn't help me with any paging.
I tired to do doodles picturing how this would work. I came up with: order by the property, start at the 'end' of the collection, and then limit back to the page size. While this still wouldn't solve paging I would expect it to give me the last n records where n was the size of the page. I get no results.
I suppose I could say use firebaseRef = firebaseRef .orderByChild(this.sortProperty).limitToLast(this.limitAmount + this.skipAmount); and in the result callback use the forEach loop to take the first (or would it be the last; I'm not sure how that iteration would work) n records where n=this.limitAmount. This just seems inefficient. Wouldn't it be better to limit the query instead of using CPU cycles to limit data that had come over the wire or is this the relational DB query thought pattern overriding the correct thought process for NoSQL?
Further Confusion
After posting this I've still been working on a solution. I've had some things get close, but I'm also running into this filtering issue. How could I filter a set of items to one property by still sorting on another? Jeez! I want to have the ability for a user to get all the stock that isn't sold out and order it by price.
Finally
Why hasn't this basic example been fleshed out on any of the Firebase "Getting Started" pages? Being able to show tabular data, page through it, sort, and filter seem like something that EVERY web developer would come across. I'm using ng-table in an Angular app to drive the view, but it still seems that regardless of platform that the queries that I'm trying to generate would be practical on any platform that Firebase supports. Perhaps I'm missing something! Please educate me!
Firebase and NoSQL
I've come up with this simple scenario that I often run into with web applications. I want to show tabular data, filter, page, and sort it. Very simple. Very common. Writing a SQL statement for this would be dead easy. Why is the query so complicated for something like Firebase. Is this common with all NoSQL solutions? There is no relational data being stored thus the need for a relational database seems unnecessary. Yet, it seems like I could hack together a little flat file to do this storage since the ability to make Firebase do these simple tasks is not made clear in its API or Docs. FRUSTRATED!!!
I want to query object from Parse DB through javascript, that has only 1 of some specific relation object. How can this criteria be achieved?
So I tried something like this, the equalTo() acts as a "contains" and it's not what I'm looking for, my code so far, which doesn't work:
var query = new Parse.Query("Item");
query.equalTo("relatedItems", someItem);
query.lessThan("relatedItems", 2);
It seems Parse do not provide a easy way to do this.
Without any other fields, if you know all the items then you could do the following:
var innerQuery = new Parse.Query('Item');
innerQuery.containedIn('relatedItems', [all items except someItem]);
var query = new Parse.Query('Item');
query.equalTo('relatedItems', someItem);
query.doesNotMatchKeyInQuery('objectId', 'objectId', innerQuery);
...
Otherwise, you might need to get all records and do filtering.
Update
Because of the data type relation, there are no ways to include the relation content into the results, you need to do another query to get the relation content.
The workaround might add a itemCount column and keep it updated whenever the item relation is modified and do:
query.equalTo('relatedItems', someItem);
query.equalTo('itemCount', 1);
There are a couple of ways you could do this.
I'm working on a project now where I have cells composed of users.
I currently have an afterSave trigger that does this:
const count = await cell.relation("members").query().count();
cell.put("memberCount",count);
This works pretty well.
There are other ways that I've considered in theory, but I've not used
them yet.
The right way would be to hack the ability to use select with dot
notation to grab a virtual field called relatedItems.length in the
query, but that would probably only work for me because I use PostGres
... mongo seems to be extremely limited in its ability to do this sort
of thing, which is why I would never make a database out of blobs of
json in the first place.
You could do a similar thing with an afterFind trigger. I'm experimenting with that now. I'm not sure if it will confuse
parse to get an attribute back which does not exist in its schema, but
I'll find out, by the end of today. I have found that if I jam an artificial attribute into the objects in the trigger, they are returned
along with the other data. What I'm not sure about is whether Parse will decide that the object is dirty, or, worse, decide that I'm creating a new attribute and store it to the database ... which could be filtered out with a beforeSave trigger, but not until after the data had all been sent to the cloud.
There is also a place where i had to do several queries from several
tables, and would have ended up with a lot of redundant data. So I wrote a cloud function which did the queries, and then returned a couple of lists of objects, and a few lists of objectId strings which
served as indexes. This worked pretty well for me. And tracking the
last load time and sending it back when I needed up update my data allowed me to limit myself to objects which had changed since my last query.