query mid-section of firebase database in swift - ios

I'm using firebase for a large-ish database, with each entry using an autoid key. To get, say, the last ten entries, I can use:
ref.queryLimitedToLast(10).observeSingleEventOfType(.Value, withBlock: { snapshot in
for item in snapshot.children {
//do some code to each item
}
})
However, I can't for the life of me work out how to then get just the ten entries before that. Eg. if the database had 100 entries, my code would return 90-100, but how would I then get entries 80-90 (without, for example, querying the last 20 and throwing half away, as it seems inefficient)?
Edit:
I ended up using
ref.queryOrderedByChild("timecode").queryEndingAtValue(final).queryLimitedToLast(10).observeSingleEventOfType(.Value, withBlock: { snapshot in
for item in snapshot.children {
//do some code to each item, including saving a new value of 'final'
}
})
and saving the value 'final' as the timecode of the last update. that is, first i would get results, 90-100, say, and save the timecode of 90 as final (minus one second), then use this for the ending value, etc... to find results 80-89.
Just as Jay describes below, but using a timestamp instead of an index number (as it was already in there)
Edit 2:
Also, to get it working better, I also added ".indexOn": "timecode" to the firebase rules for the database

There's a couple of ways to do this but an easy solution is to keep a total_count in another node and an index within each node.
Then use queryStartingAtValue and queryEndingAtValue to query the range of child nodes you are interested in.
When you add a child to your 'posts' node for example, add one to the total_count node and save it. Over time you'll have 100 posts and the total_count node will have a value of 100. You can then query for any range of posts: .queryStartingAtValue(80) and . queryEndingAtValue(89), or .queryStartingAt(20) and .queryEndingAt(30)
For example, assume there's 45 posts (showing just 4 of them here)
posts
...
post_1024
text: "my post!"
index: 42
post_1025
text: "another post"
index: 43
post_1026
text: "yippee"
index: 44
post_1027
text: "Stuff and Things"
index: 45
and then a node to track them
post_info
total_count: 45
and the code to query for the middle two nodes
let ref = myRootRef.childByAppendingPath("posts"
ref.queryOrderedByChild("index").queryStartingAtValue(43).queryEndingAtValue(44)
.observeEventType(.Value, withBlock: { snapshot in
print(snapshot.key)
})
and the output would be
post_1025
text: "another post"
index: 43
post_1026
text: "yippee"
index: 44
That being said, this may be slightly redundant depending on what happens to your data. If you never delete posts, then you're set. However, if you delete posts then obviously there's a gap in your indexes (42, 43, .. 45) so other factors need to be taken into consideration.
You may not even need a total_count - it just depends on how your app works.
You could also leverage the priority variable on your nodes to store the index instead of having it be a child node.
Transitions and .observeSingleEvent with .Value and .numChildren can be also be used to obtain a live node count.

Have you tried stacking queries?
ref.queryLimitedToLast(20).queryLimitedToFirst(10).observeSingleEventOfType(.Value, withBlock: { snapshot in
for item in snapshot.children {
//do some code to each item
}
})
Just a thought, haha.

Related

In firebase, is it possible to make query at 2nd level node, without knowing the value of 1st node?

Please see the attached image, as I only know the value of the 2nd node(-M5Cc-PcLR9HfRQyJ0SU), is it possible to make query base on this and get the value of the 1st node (-M5ukIV79GHeUc0FnVUI)?
Thank you.
Firebase structure
It's not possible. You need to be able to build the full path of node to query. There are no wildcards.
You migth want to consider duplicating the data in such a way that you can find it without the parent node. This sort of data duplication is common in nosql type databases.
If we take the question literally, you don't need a query and the answer is yes, you can get to that data based on the information provided.
Again though, it will NOT be a query.
You asked:
get the value of the 1st node
To get the first node within the -M5Cc9t node, you would read the top level node -M5Cc-Pc... and then read the first node within that snapshot.
That will give you the -M5Cc9t node which contains Item 01. Again, you're asking for the 1st node and that will do it.
Here's the code based on your stucture (I abbreviated the node names)
func readChildNode() {
let nodeRefWeKnow = self.ref.child("DEVPCD").child("-M5uklV").child("-M5Cc-Pc")
nodeRefWeKnow.observeSingleEvent(of: .value, with: { snapshot in
let allSnapshots = snapshot.children.allObjects as! [DataSnapshot]
if let firstSnapshot = allSnapshots.first {
let name = firstSnapshot.childSnapshot(forPath: "name")
print(name)
}
})
}
and the output
Snap (name) Item 01

How to write to the last child of a Firebase ref in Swift?

I am struggling with writing data to Firebase in the desired way.
The situation: I want to store multiple "trips" for each user. Every "trip" contains locations with lat, long, altitude and a timestamp.
On certain conditions, I want to add a new location to the most recent "trip".
Data Structure is the following:
- root
-- users
--- userID
---- trips
------ trip1
------- location
------ trip2
------- location
What would be the best way to add locations to the most recent trip (trip2 in this case)?
I had hoped something similar to this is possible:
func setupFirebase() {
ref = FIRDatabase.database().reference()
userRef = ref.child("users")
userID = FIRAuth.auth()?.currentUser?.uid
tripsRef = self.userRef.child(userID!).child("trips")
currentTripRef = tripsRef.queryLimited(toLast: 1)
}
and then I could simply go and:
currentTripRef.child(dateString).setValue([
"timestamp": dateString,
"lat": lat,
"lng": lng,
"alt": alt
])
wherever I need to write something. This obviously is not the right way to do it, but I can't seem to figure out what the correct approach is.
I figured I could observe my tripsRef, get an array of keys and inside of that observing function write to the child ref with the last key. But that doesn't sound efficient at all.
Any solution on this?
Thanks in advance!
First, remember it's best practice to disassociate your node names from you data. You may be doing that but wanted to stress that point.
Here's a proposed structure that handles users and the their trips
root
users
uid_0
name: "Jamie"
uid_1
name: "Adam"
trips
-Yijaisioaooisd
user_id: "uid_0"
trip_number: 1
trip_name: "France"
locations
-Yuijiaissso
location_name: "Bordeaux"
stop_number: 1
-Yijs9ojnas9
location_name: "Burgundy"
stop_number: 2
-Yijispoais
location_name: "Châteauneuf-du-Pape"
stop_number: 3
If you wanted to add another stop/location to uid_0's trip to France, the ref is
let locationsRef = rootRef.child("trips").child("-Yijaisioaooisd").child("locations")
and then
let anotherLocationRef = locationsRef.childByAutoId()
let stopDict = ["location_name": "Médoc", "stop_number": 4]
locationsRef.setValue(stopDict)
That being said, there may be situations where you want to denormalize your data and break the locations out (for queries etc)
So that would be
locations
trip_id: "-Yijaisioaooisd"
locations:
-Yuijiaissso
location_name: "Bordeaux"
stop_number: 1
-Yijs9ojnas9
location_name: "Burgundy"
stop_number: 2
-Yijispoais
location_name: "Châteauneuf-du-Pape"
stop_number: 3
based on a comment, adding some additional info:
If you want to add another stop to uid_0's latest trip there are several options and the actual answer would depend on the structure of the data.
Using queryLimitedToLast, the last stop in the locations node could be queried to get the stop number (3 in the above example) to where you can increment it and then write out the next stop. This would work if you know the parent key -Yijaisioaooisd as the ref would be -Yijaisioaooisd/locations.
If you don't know the parent key you can easily obtain it with a query on the trips node for user_id equal to uid_0. That will return a snapshot. Then snapshot.key would provide the -Yijaisioaooisd parent key.

Firebase iOS SDK - How to count number of child with good performance?

I'm having this kind of JSON tree:
On Home page of my app, I want to show just list of groups and number of messages in that group.
In normal case I fetch all the chats and show the count of each group chats messages, but doing this will cause a big performance effect if there are more groups.
I found this for web API and outdated, but not for iOS SDK.
So how can I get count of messages from group1 without retrieving all the messages?
How about adding a noOfMessages node under each group, and updating it every time with the group messages count changes. You will have to make a query to count the noOfChildren some time, right? But this way not every time.
For me i just created a func countNodes() which would be fired every time a child is added or you can use a timeInterval in NSTimer, which as you say is expensive.
But with .childAdded you will only receive appended key-value pair not the entire of the node's value, and then all you gotta do is update your noOfMessages node's value with the no of .childAdded count.
As for in your case, change your structure to:-
chats :{
group1 : {
...
},
},
noOfMessages :{
group1 : 18,
group2 : 76,
....
}
Add an observer to your each group of eventType .childAdded.
FIRDatabase.database().reference().child("chats/group1").observeSingleEvent(of: .childAdded, with: {(snap) in
//Run transaction to update your noOfMessage Count
})
To get the noOfMessages count:-
FIRDatabase.database().reference().child("noOfMessages/group1").observeSingleEvent(of: .value, with: {(snap) in
let count = snap.value as! Int
})

Need to arrange the displaying pattern in table view controller Xcode 7 using Firebase

I have created a table view controller which populates the cell from firebase. The table loads perfectly with a minor problem. Whenever a new item is added the item get displayed at the bottom of the screen instead on the top. How do I arrange my item, so that whenever a new item is added, it gets on the top cell and get displayed on the top of the app not on the bottom so I don't have to scroll down to see what new item has been added..
Firebase doesn't have a natural order - you need to decide how you want your data ordered and set up your structure accordingly. Then retrieve the data by that order. You can order them by their keys, values or priority.
For example:
You have a blog app and you always want the the blogs listed in time order:
post_0
timestamp: 1
post: "posted at 1am"
post_1
timeStamp: 13
post: "posted at 1pm"
post_2
timeStamp: 9
post: "posted at 9am"
When you read the posts node, order by timeStamp and it will be in that order, 1, 9, 13.
ref.queryOrderedByChild("timeStamp").observeEventType(.ChildAdded, withBlock: { snapshot in
println("\(snapshot.value)")
})
If you want just the latest two posts, use a limit query.
let scoresRef = Firebase(url:"https://dinosaur-facts.firebaseio.com/scores")
postsRef.queryOrderedByValue().queryLimitedToLast(2).observeEventType(.ChildAdded, withBlock: { snapshot in
println("post time \(snapshot.value)")
})
Of course there are many other options:
Read the firebase data into an array, then use NSSortDescriptors to sort descending.
Read the firebase data in via order by (so they are 1, 2, 3, 4) with 4 being the most current post, and insert each one into an array at position 0.
4 Most current post stored position 0 in the array
3 <- read after 2
2 <- read after 1
1 <- first read in
The Firebase Reading Data is a really good reference for understanding queries and the ordering of data.

Query to get rows with unique objects parse ios

I have a table called table 1 which contains a field called parent field which contains object(Objectid) of table 2.Now i dont want to get the duplicated objectId's and arrange them on the basis of ascending order.Here is the data of the table.
Table1
parentfield(id)
790112
790000
790001
790112
790000
790001
Now the result would be the first three elements but i dont know the number of id's matched.Is there a way to do that?
Unfortunately, there is not SELECT DISTINCT / GROUP BY operation in Parse.
See this thread: https://parse.com/questions/retrieving-unique-values
Suggested team solution:
There's no built-in query constraint that would return distinct values
based on a column.
As a workaround, you can query for all the rows, then iterate through
them and track the distinct values for the desired column
So, the sad, bad, horrible idea, is to make a Cloud Function that fetch all the possible elements ( keep in mind that Parse allow you to fetch at maximum 1000 elements for each query ) and then remove the duplicates from the resulting list. You can do this all on the Cloud code function, and then returning the cleaned list to the client, or you can do this directly on your client devices. All this means that if you want to retrieve the real select distinct equivalent at this conditions, you should first fetch all the element ( a loop of query, retrieving 1000 items at time ) and then apply your custom algorithm for removing the duplicates. I know, it's really long and frustrating, considering the fact that the Parse cloud function execution has a timeout limit of 7-10 seconds. Maybe moving to the Parse backgroud jobs, you can populate a distinct temporary table, since you should have up to 15 minutes of execution before the timeout.
Another drastic solution is to move your data on another server that support an ER databases ( like on Openshift, that keep a free tier ) and with some Parse background job, you synchronize the elements from parse to the ER db, so you redirect the client request to the ER db instead of Parse.
Hope it helps
i have an app with similar requirements to display each company name only once and the number of items it has
here is how I implemented the solution at client side (swift 3), my database is relatively small:
var companyItemsCount = [Int: Int]() // stores unique companyId and number of items (count)
query.findObjectsInBackground(block: { (objects: [PFObject]?, error: Error?) in
var companyId: Int = 0
if error == nil {
// The find succeeded.
// Do something with the found objects
if let objects = objects {
for object in objects {
companyId = object["companyId"] as! Int
if self.companyItemsCount[companyId] == nil {
self.companyNames.append(object["companyName"] as! String)
self.companyIds.append(object["companyId"] as! Int)
self.companyItemsCount[companyId] = 1
}else{
self.companyItemsCount[companyId]! += 1
}
self.tableView.reloadData()
}
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.localizedDescription)")
}
})
self.tableView.reloadData()

Resources