Let's say I have an array of keys in my Firebase data which represents a relationship to another section of data. Here's an example:
{
"messages": {
"m1": {
"text": "123"
},
"m2": {
"text": "123"
},
"m3": {
"text": "123"
}
},
"rooms": {
"r1": {
"messages": {
"1": "m1",
"2": "m2",
"3": "m3"
}
}
}
}
We have 3 messages each with an ID, and a room that has a Firebase array of message IDs that belong in that room. This is a pretty common pattern on Firebase.
So, in code, I get an array of message IDs, which I iterate over to create Firebase Refs from.
let messageIDs = snapshot.value["messages"] as! [String]
var refs: [Firebase] = []
for mID in messageIDs {
refs.append(Firebase("https://firebase.com/messages/" + mID))
}
All good. This works as expected.
Now I want to display all of these messages in a list. Is there a way to load all of these items at once? I could loop through these Refs and perform an observe once on each one, but then I would need to manage the results as they come back. Or perhaps that is the only way of doing it? Has anyone dealt with this before?
There may be more to the question and existing structure but perhaps this structure would simplify things?
messages:
message_id_0:
msg_num: 1
text: "123"
room: r1
message_id_1:
msg_num: 1
text: "123"
room: r3
message_id_2:
msg_num: 2
text: "123"
room: r1
and then to get all of the messages for room r1
ref.queryOrderedByChild("room").queryEqualToValue("r1")
.observeEventType(.Value, withBlock: { snapshot in
println(snapshot.value)
})
would return all the messages in a single snapshot for room r1 with the msg_num (for ordering) and the text of each message.
The structure breaks the link between the node name and the data it contains which will add more flexibility. Oh, and it eliminates the need for an array; it's probably a good idea to avoid arrays in Firebase where possible.
In case the message happen to all be in a sequence, you can perform a range query to get all of them. From the docs:
let ref = Firebase(url:"https://dinosaur-facts.firebaseio.com/dinosaurs")
ref.queryOrderedByKey().queryStartingAtValue("b").queryEndingAtValue("b\u{f8ff}")
.observeEventType(.ChildAdded, withBlock: { snapshot in
println(snapshot.key)
})
But most likely the messages won't (always) be sequential. In that case you will indeed have to loop through them and load them each in turn with observeSingleEventOfType. This is indeed a bit more work in code, since you need to merge the results into a single list/array.
Many developers worry about the performance of this operation, but this is not as bad as you may initially think. The requests will all be pipelined over the socket that Firebase keeps open, so it's pretty likely the sequence will be:
send request 1
send request 2
send request 3
... wait for the server a bit
get response 1
get response 2
get response 3
The sending of the requests and the receiving of the responses are going to be close to each other, in between you're waiting for the latency once and for the database to find the items.
Related
I have a value which needs to be compared with an array of values. Basically a user needs to check if it has the same item as another user. But I am struggling to solve this as how do I get the item of an array of users? Normally when you observe a value you do something like this:
Database.database().reference(withPath: "users/\(userID)/itemList").observeSingleEvent...
However, when it comes to an array of users itemList how can this be achieved if there are multiple ID's? I'd like compare a user item or items with other users item and check if they match in order to sort an array of users that have a match.
If there is already an example for this please help direct me there.
Update
This is how my data structure looks:
{
"users": {
"EWJGFYmVTzOiHgnq42ebhrg2fj": {
"firstName": "Friederike",
"itemList": [
2: true,
3: true,
0: true
]
},
"C076OYmVTzOiHgnq4wPQtY2XpED2": {
"firstName": "Ian",
"itemList": [
0: true,
1: true,
3: true
]
},
"Juoiuf0N6qNmkm32jrtu6X6UK62": {
"itemList": [
0: true
],
"firstName": "Jack"
}
}
}
Update 2.0
With the answer below I am able to query through the items table but still unable to query the keys that match as there can be multiple arrays and therefore I cannot use it to filter or anything.
Ok so with your current data structure you'd have to query all nodes under users and then compare the arrays, which is very inefficient. There isn't a straight forward or easy way to do that without modifying your structure. So I suggest you modify your data structure so that each item has a list of all users that have it. Something like this:
{
"items": {
"0": {
"EWJGFYmVTzOiHgnq42ebhrg2fj": "Friederike",
"C076OYmVTzOiHgnq4wPQtY2XpED2": "Ian",
"Juoiuf0N6qNmkm32jrtu6X6UK62": "Jack"
},
"1": {
"C076OYmVTzOiHgnq4wPQtY2XpED2": "Ian"
},
"2": {
"EWJGFYmVTzOiHgnq42ebhrg2fj": "Friederike"
}
//....
}
}
Depending on what you want to display you might want to store more information than just the users UID and username. You can query all the users that have the same items as you using a query like this:
let ref = Database.database().reference()
// assuming you already have the current users items stored in an array
for item in items {
ref.child("items").child(String(item)).observeSingleEvent(of: .value, with: { snap in
for child in snap.children {
let child = child as? DataSnapshot
if let key = child?.key, let name = child?.value as? String {
// do something with this data
}
}
})
}
Firebase database is noSQL, so data is meant to be denormalized or duplicated so that queries can be optimized. Firebase actually recommends that you avoid nesting data. Take a look at this question for more information.
Hope that helps
Code related to question asked in comments
Assuming you are storing the UID's or names of users with the same items in a string array you can prevent duplicates using .contains()
var namesWithMatchingItems = [String]()
if !namesWithMatchingItems.contains(nameYouJustFetched) {
namesWithMatchingItems.append(nameYouJustFetched)
}
I was curious to know if there is a way to simplify my Firebase DB queries.
Also, what are some best practices to follow when making pull requests.
Ive noticed the more complex my hierarchical structure gets from adding children results in extremely strenuous and long queries within my code.
Here is an example of a query within my application:
If you find yourself needing complex queries, it most likely means that your data isn't properly structured.
In your case, your structure needs to be normalized (flattened).
The following modification should make your queries simpler:
{
"listings": {
"listing_key_1": {
"timestamp": "2017 10 19 07:49:38"
},
"listing_key_2": {
"timestamp": "2017 10 19 05:30:02"
},
...
},
"AircraftHouseRules": {
"listing_key_1": {
"Availability": "",
...
},
"listing_key_2": {
"Availability": "",
...
}
},
"BasicInfo": {
"listing_key_1": {
"ModelPlane": "",
...
},
"listing_key_2": {
"ModelPlane": "",
...
}
},
...
}
I don't get what you mean by "pull requests". If you mean observing or simply reading the data, it's pretty straightforward. All you need in your case is a listing id/key to gain direct access to its data (AircraftHouseRules, BasicInfo, DetailedInformation, etc...).
If you want the reverse (to get the key of a listing through, let's say, its TypeOfPlane), you can do the following query
// NOTE: Untested code - just for illustrative purposes
let queryRef = Database().database.reference.child("BasicInfo").queryOrdered(byChild: "TypeOfPlane").queryEqual(to: "some type")
queryRef.observeSingleEvent(of: .value, with { snapshot in
if let listings = snapshot.value as? [String : Any] {
// all retrieved listings with TypeOfPlane == "some type"
}
})
Tips
You should always avoid nesting structures to avoid downloading unnecessary data and hindering performance. In the screenshot you posted, you have up to 8 levels of nested data. With the modification above you can halve those levels easily.
You should also consider replacing all array structures (like the ones under ImageRef) with key-value pairs as arrays can be evil.
I have users structure lke this:
{
"users": {
"uniqueID1": {
"name": "Anon",
"friends": {
"uniqueID2": true,
"uniqueID3": true
}
}
"uniqueID2": { },
"uniqueID3": { },
}
}
I want to show a user's friends' names. I have to access $user/friends/ to get list of unique IDs, and iterate the list to get friend's information. But iterating the unique ID is making multiple queries, and I have to always check if all of my queries are finished. According to the doc, it seems multiple queries will not impact the performance too much, but if I want to update my view only when all of the queries are finished, I have to check how many queries are finished.
Is there no way of 'execute a completion block when all queries are finished'?
Pseudocode
var totalNumOfFriends = 0
var tempArray = NewArray()
ref(/users/uniqueID1/friends).observeEventType{ snapshot
var uIDList = snapshot.children's keys
totalNumOfFriends = uIDList .count
for uID in uIDList {
var nameRef = ref(/users/uID/name) i.e. /users/uniqueID3/name
nameRef.observeSingleEventOfType { snapshot
var username = snapshot.value
tempArray.append(username)
if tempArray.count == totalNumOfFriends {
// If counts are the same, tempArray has all of my friends' names
// Now update view using tempArray
}
}
}
}
Pseudocode explanation:
Get list of unique IDs from /users/uniqueID1/friends
'Save' number of unique IDs. (Explained in step 4)
For each unique IDs from the list, get user's name by using ref like this /users/uniquedID2/name
For each name retrieved, add it to temporary array. Once the count of the temporary array equals to the count from step 2, update my view as I have retrieved all the names.
Firebase has no built-in way to signal when a number of queries has finished. But you can easily implement this in your own code. Your approach with a counter that checks how many items have already been loaded is the most common approach for that.
"John" : {
"David" : {
"-KIMA0aPsujdAOpkzP0w" : {
"message" : "hallu",
"sender" : "10154053432889835",
"time" : 1463898873196
}
}
},
"Harry" : {
"Christina" : {
"-KIMA0aPsujdAOpkzP0v" : {
"message" : "hallu",
"seen" : true,
"sender" : "self",
"time" : 1463898873195
}
},
"Pierce" : {
"-KILZ_GH7Ji9hQYNK-6p" : {
"message" : "Eli there.",
"seen" : true,
"sender" : "179914035712208",
"time" : 1463888795301
},
"-KIM8yPz2UDOZwHEg_nn" : {
"message" : "hahjajak",
"seen" : true,
"sender" : "self",
"time" : 1463898597847
},
}
I wanted to query the count of nodes where "seen" key has value "true" of top node which is John OR Harry There are multiple child node inside it and each child node has multiple child node which has automatic id set.
I just want to know the count of objects which has "seen" key set to true and also count of objects which has "seen" key set to false
I can extract all the values in node as follows:
ref.observeSingleEventOfType(.Value, withBlock: { snap in
for (key,value) in snap.value as! NSDictionary {
}
})
I can loop through the dictionary and count the number of objects manually. But that is not too computationally or data efficient as firebase is volume based.
What I want is to know if there is any query to count the number of objects whose "seen" key in root node is "true" or whose "seen" key in root node is "false".
UPDATE:
Data Structure
John and Harry are user unique ID. and the node David which is immediate child of John node is unique ID of person who the user has chatted with earlier.
And the KIMA0aPsujdAOpkzP0w node represent a unique message from user John to/from David.
So what i wanted was to count the number of messages from David to John not seen by John
If you want to get only the "seen" value, you should reconsider your database structure and denormalize where possible to make reading as fast as you can.
Keeping your structure to get "seen" value, each time you read you have to get the entire node and loop through it to get the value, downloading unnecessary data, wasting processing time and making overall operation slower.
You should create a new node and store just the "seen" value the way you are going to get it later. Lets say, the best way to structure your database is based on which read operations are going to do and what data do you need in those operations.
You shouldn't worry about duplicating data. Remember that Firebase has the "updateChildren" method that allows you to update data in different nodes in a single operation.
For more info you can read the docs about structuring data here: https://firebase.google.com/docs/database/android/structure-data
Cheers!
Not sure if this is the most efficient way to handle it, but for a rapid prototype I've been searching for the answer to this problem as well. I settled on using this strategy to isolate child nodes of the data structure:
ref.observeSingleEventOfType(FIRDataEventType.Value, withBlock: { snapshot in
for (key,value) in snapshot.value as! NSDictionary {
if (key as! String == "John/David/etc")
{
let newRef = FIRDatabase.database().reference().child("John").child(key as! String)
newRef.observeSingleEventOfType(FIRDataEventType.Value, withBlock: { snapshot in
for (childKey, childValue) in snapshot.value as! NSDictionary{
//do whatever you want with child values/etc
}
})
}
}
})
Keep in mind I am VERY new to Firebase on iOS and they just changed their documentation so I'm working through it as I can. I'm sure there are way better ways to accomplish accessing child data but this worked for my MVP so I went with it.
EDIT: This assumes that your original reference is aimed at the part of the data tree that you want to target. For example:
let ref = FIRDatabase.database().reference().child("John")
That way when you did ref.observeSingle..etc, it would look for the values underneath that child object, instead of the entire tree.
I'm using childByAutoId() to generate my children. Each child looks like:
{
user_id: 1
}
I'd like to get the last 10 most recently added, sorted by time DESC. What's the easiest way to do this?
The answer is that you need to use a bit of reverse logic, and also store a timestamp key:value pair within each node as a negative value. I omitted the user_id: 1 to keep the answer cleaner.
Here's the Firebase structure
"test" : {
"-KFUR91fso4dEKnm3RIF" : {
"timestamp" : -1.46081635550362E12
},
"-KFUR9YH5QSCTRWEzZLr" : {
"timestamp" : -1.460816357590991E12
},
"-KFURA4H60DbQ1MbrFC1" : {
"timestamp" : -1.460816359767055E12
},
"-KFURAh15i-sWD47RFka" : {
"timestamp" : -1.460816362311195E12
},
"-KFURBHuE7Z5ZvkY9mlS" : {
"timestamp" : -1.460816364735218E12
}
}
and here's how that's written out to Firebase; I just used a IBAction for a button to write out a few nodes:
let testRef = self.myRootRef.childByAppendingPath("test")
let keyRef = testRef.childByAutoId()
let nodeRef = keyRef.childByAppendingPath("timestamp")
let t1 = Timestamp
nodeRef.setValue( 0 - t1) //note the negative value
and the code to read it in
let ref = self.myRootRef.childByAppendingPath("test")
ref.queryOrderedByChild("timestamp").queryLimitedToFirst(3).observeEventType(.ChildAdded, withBlock: { snapshot in
print("The key: \(snapshot.key)") //the key
})
and I declared a little function to return the current Timestamp
var Timestamp: NSTimeInterval {
return NSDate().timeIntervalSince1970 * 1000
}
and the output
The key: -KFURBHuE7Z5ZvkY9mlS
The key: -KFURAh15i-sWD47RFka
The key: -KFURA4H60DbQ1MbrFC1
As you can see, they are in reverse order.
Things to note:
Writing out your timestamp as negative values
When reading in use .queryLimitedToFirst instead of last.
On that note, you can also just read the data as usual and add it to an Array then then sort the array descending. That puts more effort on the client and if you have 10,000 nodes may not be a good solution.
I'm assuming your data actually looks like this:
someDataSet: {
longUID-1: {
timeCreated: 9999999999, // (seconds since the javascript epoch)
user_id: 1
},
longUID-2: {
timeCreated: 1111111111,
user_id: 2
},
longUID-3: {
timeCreated: 3141592653,
user_id: 3
}
}
You could automate that by calling Firebase.push({user_id: ###, timeCreated: ###}) multiple times in a for loop or any other method. Maybe you're adding news stories to a webpage, but you only want your user to see the most current stories--- IDK. But the answer to your question is to use Firebase's ref.orderByChild() and ref.limitToLast().
var ref = new Firebase("<YOUR-FIREBASE-URL>.firebaseio.com/someDataSet");
//the "/someDataSet" comes from the arbitrary name that I used up above
var sortedRef = ref.orderByChild('timeCreated');
//sort them by timeCreated, ascending
sortedRef.limitToLast(2).on("child_added", function(snapshot){
var data = snapshot.val();
console.log(data);
/* do something else with the data */
});
//The console would look like this
// Object {timeCreated: 9999999999, user_id: 1}
// Object {timeCreated: 3141592653, user_id: 3}
This happened because the program took the child with the greatest timeCreated value first and then the second greatest (value) second...
Also note, the longUID means nothing when you sort them by child and neither do the other values (user_id in this case)
Here is the documentation for:
Firebase .push() method (Sorry, I'm not allowed to post this link- I dont have enough reputation)
Firebase .orderByChild method
And also, Firebase .limitToLast method
The code: ref.queryOrderedByKey().queryLimitedToLast(10) can be used for getting the most recent 10 data. However, this is an ascending order by default.
Alternatively, you can order your data via
ref.orderByChild("id").on("child_added", function(snapshot) {
console.log(snapshot.key());
});
This also presents an ascending order by default. To change it into descending order is little bit tricky. What I would suggest it to multiply ids by -1 as shown below and then sort them.
var ref= new Firebase("your data");
ref.once("value", function(allDataSnapshot) {
allDataSnapshot.forEach(function(dataSnapshot) {
var updatedkey = -1 * dataSnapshot.key();
ref.update({ element: { id: updatedkey}});
});
});
This two SO page might be useful for you also, please check:
How to delete all but most recent X children in a Firebase node?
firebaseArray descending order?