Firebase queryOrderedByChild() method not giving sorted data - ios

My database structure is some thing like this:
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"score": 4,
},
"ghopper": { ... },
"eclarke": { ... }
}
}
I am trying to retrieve top 20 scores in descending order.
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToLast(20)
queryRef.observeSingleEventOfType(.Value, withBlock: { (querySnapShot) in
print(querySnapShot.value)
})
i am trying to get output like
score": 4
score": 3
score": 2
or
score": 2
score": 3
score": 4
or
2
3
4
Please let me know how to solve this.

When you request the children in a specific order, the resulting snapshot will contain both the data that matches the query and information about the order in which you requested them.
But when you request the .value of the snapshot, the keys+data are converted to a Dictionary<String,AnyObject>. Since a dictionary does not have an extra place to put the information about the order, that information is lost when converting to a dictionary.
The solution is to not convert to a dictionary prematurely and instead loop over the snapshot:
queryRef.observeSingleEventOfType(.Value, withBlock: { (querySnapShot) in
for childSnapshot in querySnapShot.children {
print(childSnapshot.value)
}
})
You can also listen to the .ChildAdded event, instead of .Value, in which case the children will arrive in the correct value:
queryRef.observeSingleEventOfType(.ChildAdded, withBlock: { (childSnapshot) in
print(childSnapshot.value)
})
Update
I just added this JSON to my database:
{
"users" : {
"alovelace" : {
"name" : "Ada Lovelace",
"score" : 4
},
"eclarke" : {
"name" : "Emily Clarke",
"score" : 5
},
"ghopper" : {
"name" : "Grace Hopper",
"score" : 2
}
}
}
And then ran this code:
let queryRef = ref.child("users").queryOrderedByChild("score").queryLimitedToLast(20);
queryRef.observeEventType(.ChildAdded) { (snapshot) in
print(snapshot.key)
}
The output is:
ghopper
alovelace
eclarke
Which is the users in ascending order of score.
Update to add more on getting the scores in descending order
The above code gets the 20 highest scores in ascending order. There is no API call to return themthem in descending score.
But reversing 20 items client side is no performance concern, you just need to write the code for it. See for example this answer.
If you really are stuck on reversing them client side, you can add an inverted score. See this answer for an example of that.

Use method observeEventType instead of observeSingleEventOfType.
Also, make FIRDataEventType to ChildAdded.
Last, If you want Top 20 items, use queryLimitedToFirst instead of queryLimitedToLast.
{
"users" : {
"alovelace" : {
"name" : "Ada Lovelace",
"score" : 4
},
"eclarke" : {
"name" : "Emily Clarke",
"score" : 5
},
"ghopper" : {
"name" : "Grace Hopper",
"score" : 2
}
}
}
For the dataset above
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToFirst(20)
queryRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
print("key: \(snapshot.key), value: \(snapshot.value)")
})
key: ghopper, value: Optional({
name = Grace Hopper;
score = 2;
})
key: alovelace, value: Optional({
name = Ada Lovelace;
score = 4;
})
key: eclarke, value: Optional({
name = Emily Clarke;
score = 5;
})
Snapshot will returns the contents as native types.
Data types returned:
NSDictionary
NSArray
NSNumber (also includes booleans)
NSString
So, you can get your scores this way.
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToFirst(20)
queryRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
if let scores = snapshot.value as? NSDictionary {
print(scores["score"])
}
})
Optional(2)
Optional(4)
Optional(5)
Moreover, the default of realtime database return everything in ascending order.
If you want descending order, you can make some tricks(4:40) in your database.

Related

IndexOn on firebase Root to query entire database swift 3

I am trying to query by name inside firebase database, I want to return all the names matching the query. But I can't seem to get it to work, I am getting error Consider adding ".indexOn": "name" at / to your security rules.
Security Rules:
{
"rules": {
".read": "true",
".write": "true",
"goals_new": {
".indexOn": ["name"]
}
}
}
I Can retrieve the names for specific child like this:
let query = ref.child("goals_new").queryOrdered(byChild:"name").queryEqual(toValue: name)
query.observe(.childAdded) { (snapshot) in
// if let values =
if let values = snapshot.value as? [String:String] {
print(values)
print(values["name"]?.count ?? "")
}
}
But I want to retrieve all the names in the database that matches the query
Database Structure:
"goals_new" : [ null, {
"name" : "Eric thomas",
"pic" : “…….”,
"title" : "Be Obsessed with your Goals",
"url" : “……”
},
{
"name" : "Bob Proctor",
"pic" : “……….",
"title" : "Goal Achievement System",
"url" : “………”
},
Any help would be appreciated.
Consider adding ".indexOn": "name" at / to your security rules <- Is only a warning so you don't have to worry much about that.
I'm seeing perhaps an error. Looks like you're using an array to store your objects of 'goals_news' because of the keys are numbers 1, 2, etc... Maybe that's is messing with your query, you have to change them for string keys like every time you add one use firebase method childByAutoID().
Better use .value instead of .childAdded method so you can get all the objects that matches the name, something like this:
let query = ref.child("goals_new").queryOrdered(byChild:"name").queryEqual(toValue: name)
query.observe(.value) { (snapshot) in
guard snapshot.exists() && snapshot.hasChildren() else {return}
for snap in snapshot.children {
var currentSnapValue = (snap as! DataSnapshot).value as! [String: String]
print("Name: \(currentSnapValue["name"])")
}
}
That way you're getting all the objects that matches the name

How to apply multiple filter in Firebase query in Swift?

I am trying developing an application like Taxi booking and storing data on Firebase.
But, I am facing problem while querying data for RideDetail(History) from Firebase.
I want to fetch ride_detail for specific "customer_id" in pagination form.
My Firebase DataStructure:
{
"ride_details": {
"NuEoP2WNPwigsbY1FQy9M150131918189233": {
"customer_id": "tstebwLlf4OCRdWhNKO9XCO08xY2",
"destination_address": "New Ranip\nNew Ranip\nAhmedabad\nGujarat 380081\nIndia",
"destination_lang": 72.55924470000001,
"destination_latg": 23.0930152,
"discount": "10%",
"driver_id": "cIyZQIJ7tsdvF1a9KpRrKucF2o62",
"drop_time": "2017-07-29 09:12:21 +0000",
"fare": "13.16 Rs.",
"payment_time": 150149034812771,
"pickup_time": "2017-07-29 09:10:38 +0000",
"priceperkm": "10.00 Rs.",
"ride_confirm_time": "2017-07-29 09:06:21 +0000",
"source_address": "Vastrapur\nVastrapur\nAhmedabad\nGujarat\nIndia",
"source_lang": 72.5293244,
"source_latg": 23.0350073,
"tax": "10%"
},
"RH0oZ0Ypbkur3wJM3HMvM150147833457957": {
"customer_id": "aYQFbwLlf4OCRdWhNKO9XCO08xY2",
"destination_address": "Sarovar Park Plaza Hotels and Resorts Private Limted\nNo 1\nSector 10\nCBD Belapur\nWadala West\nWadala\nMumbai\nMaharashtra 400614\nIndia",
"destination_lang": 72.8561644,
"destination_latg": 19.0176147,
"discount": 0,
"driver_id": "cIyZQIJ7tsdvF1a9KpRrKucF2o62",
"drop_time": "",
"fare": 0,
"payment_time": 150149034812772,
"pickup_time": "",
"priceperkm": 0,
"ride_confirm_time": "2017-07-31 05:18:54 +0000",
"source_address": "Smokin Joe's Fresh Pizza\nShop No. 2\n3\nGround Floor\nAbhiman II\nWadala West\nThane West\nMumbai\nMaharashtra 400602\nIndia",
"source_lang": 72.8561644,
"source_latg": 19.0176147,
"tax": 0
}
}
}
Here "payment_time" is timestamp when payment done.
And the response I want is like:
{
"RH0oZ0Ypbkur3wJM3HMvM150147833457957": {
"customer_id": "aYQFbwLlf4OCRdWhNKO9XCO08xY2",
"destination_address": "Sarovar Park Plaza Hotels and Resorts Private Limted\nNo 1\nSector 10\nCBD Belapur\nWadala West\nWadala\nMumbai\nMaharashtra 400614\nIndia",
"destination_lang": 72.8561644,
"destination_latg": 19.0176147,
"discount": 0,
"driver_id": "cIyZQIJ7tsdvF1a9KpRrKucF2o62",
"drop_time": "",
"fare": 0,
"payment_type": 150149034812772,
"pickup_time": "",
"priceperkm": 0,
"ride_confirm_time": "2017-07-31 05:18:54 +0000",
"source_address": "Smokin Joe's Fresh Pizza\nShop No. 2\n3\nGround Floor\nAbhiman II\nWadala West\nThane West\nMumbai\nMaharashtra 400602\nIndia",
"source_lang": 72.8561644,
"source_latg": 19.0176147,
"tax": 0
},
"1trcf0Ypbkur3wJM3HMvM150147833457957": {
"customer_id": "aYQFbwLlf4OCRdWhNKO9XCO08xY2",
"destination_address": "Sarovar Park Plaza Hotels and Resorts Private Limted\nNo 1\nSector 10\nCBD Belapur\nWadala West\nWadala\nMumbai\nMaharashtra 400614\nIndia",
"destination_lang": 72.8561644,
"destination_latg": 19.0176147,
"discount": 0,
"driver_id": "cIyZQIJ7tsdvF1a9KpRrKucF2o62",
"drop_time": "",
"fare": 0,
"payment_type": 150149034812778,
"pickup_time": "",
"priceperkm": 0,
"ride_confirm_time": "2017-07-31 05:18:54 +0000",
"source_address": "Smokin Joe's Fresh Pizza\nShop No. 2\n3\nGround Floor\nAbhiman II\nWadala West\nThane West\nMumbai\nMaharashtra 400602\nIndia",
"source_lang": 72.8561644,
"source_latg": 19.0176147,
"tax": 0
}
}
I want first 10 records for specific "customer_id" that I pass in query orderedBy "payment_time". Also I want to do pagination for the same. i.e. in second query call, it must return 11-20 records and so on.
The question and comments have some different criteria but let me address it at a high level;
The first answer is: Firebase cannot be queried for the value of one child and then ordered by another.
The simple query function expresses that:
let query = ridesRef.queryOrdered(byChild: "cust_id").queryEqual(toValue: "cust id 4")
To accomplish that task, query for the child data you want, in this case all customer id 4 nodes, and then order in code. Here's an example
class RideClass {
var key = ""
var cust_id = ""
var pay_time = ""
init(key: String, cust_id: String, pay_time: String) {
self.key = key
self.cust_id = cust_id
self.pay_time = pay_time
}
}
var rideArray = [RideClass]()
func populateRideArray() {
let usersRef = self.ref.child("ride_details")
let query = usersRef.queryOrdered(byChild: "cust_id").queryEqual(toValue: "cust id 4")
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let key = snap.key
let custId = dict["cust_id"] as! String
let payTime = dict["pay_time"] as! String
let ride = RideClass(key: key, cust_id: custId, pay_time: payTime)
self.rideArray.append(ride)
}
for ride in self.rideArray { //unsorted example
print(ride.pay_time)
}
self.rideArray.sort { $0.pay_time < $1.pay_time } //sort
for ride in self.rideArray { //sorted example
print(ride.pay_time)
}
})
}
In this example, we create a RideClass which stores some info about the ride, and then an array of rides which could be used as a tableView dataSource.
Then query for all rides that are for cust id 4. We have a loop to show what was retreived unsorted and then this little gem
self.rideArray.sort { $0.pay_time < $1.pay_time }
which sorts the ride array in place by pay_time, which answers the question.
Suppose though, there are 100,000 ride child nodes. Loading in all of that data and sorting in code could be challenging memory wise. What do you do?
We leverage a compound value; along with the child nodes of cust_id and pay_time, we also include id_time. Here's a possible structure:
"ride_details" : {
"ride_0" : {
"cust_id" : "cust id 4",
"id_time" : "cust id 4_172200",
"pay_time" : "172200"
},
"ride_1" : {
"cust_id" : "cust id 2",
"id_time" : "cust id 2_165500",
"pay_time" : "165500"
},
"ride_2" : {
"cust_id" : "cust id 1",
"id_time" : "cust id 1_182300",
"pay_time" : "182300"
},
"ride_3" : {
"cust_id" : "cust id 3",
"id_time" : "cust id 3_131800",
"pay_time" : "131800"
},
"ride_4" : {
"cust_id" : "cust id 4",
"id_time" : "cust id 4_132200",
"pay_time" : "132200"
}
},
and then some code to read in the cust id 4 nodes in the correct order
let ridesRef = self.ref.child("ride_details")
let query = ridesRef.queryOrdered(byChild: "id_time")
.queryStarting(atValue: "cust id 4_")
.queryEnding(atValue: "cust id 4_\\uf8ff")
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let key = snap.key
let custId = dict["cust_id"] as! String
let payTime = dict["pay_time"] as! String
let ride = RideClass(key: key, cust_id: custId, pay_time: payTime)
self.rideArray.append(ride)
}
for ride in self.rideArray { //unsorted example
print(ride.pay_time)
}
})
Two things to note:
The snapshot must be iterated over to maintain the child sequence
"\uf8ff" is a character at a very high code level in Unicode - because of that it encompasses all of the preceeding characters.
There are two basic approaches that may work for your requirements:
construct a query that orders by payment_time, limits to include only the first 10 records, then holds a reference to the 10th record so that you can make subsequent pagination calls with the queryStartingAtValue filter.
Setup a firebase cloud function with a database trigger listening to the payment_time node, such that every time your empty sting is updated with a value in the database you transform the data so that it's organized in such a way that makes it trivial to consume for your needs here. For example - I would organize the new data at a path that looks like this: customer_ride_details/{{customer_id}}/{{ride_id}}. And since you are triggering the function when you replace the payment_time empty string with a timestamp. The keys should be ordered already for you to consume. You will still need to manage your pagination like we did with option 1.

Server side filtering of Firebase data

I have a Firebase db structure as follows:
{
"listings" : {
"-KOt8OUGkUphoMyqEXJ2" : {
"created" : 1470911323208,
"ends" : 1470911323209,
"make" : "LONDON TAXIS INT",
"model" : "TX4 BRONZE",
"status" : "For Sale",
},
"-KOt97_P8sJW7woED4aH" : {
"created" : 1470911515115,
"ends" : 1471775515000,
"make" : "NISSAN",
"model" : "QASHQAI N-TEC",
"status" : "For Sale",
},
"-KOt_BYYUEaXu_LNvnUv" : {
"created" : 1470918609414,
"ends" : 1471782609000,
"make" : "MAZDA",
"model" : "3 TS",
"status" : "For Sale",
}
}
}
I use GeoFire to get keys for listings in a given radius. I then use observeSingleEventOfType to return the listing for the key. This all works fine.
However, I would like to only return a listing if the "ends" timestamp > current time. I have tried the following approach:
geoQueryHandle = geoQuery.observeEventType(.KeyEntered, withBlock: {(key, location) in
let listingRef = self.ref.child("listings").child(key)
let now = Int(NSDate().timeIntervalSince1970 * 1000)
let query = listingRef.queryStartingAtValue(now, childKey: "ends")
query.observeSingleEventOfType(.Value, withBlock: {snapshot in
if let listing = Listing(snapshot: snapshot, location: location) {
//...populate my tableview
}
}
})
Can anyone advise me on why my query isn't working?
Thanks
Firebase queries apply ordering/filtering to the properties of the child nodes of the location where you execute them.
queryLocation
child1
filterProperty: filterValue
child2
filterProperty: filterValue
Since your listingRef already points to a specific listing, the order/filter you add will apply to child nodes one level deeper.
The simplest way to get your use-case working seems to simply filter the node client-side:
let listingRef = self.ref.child("listings").child(key)
query.observeSingleEventOfType(.Value, withBlock: {snapshot in
if let listing = Listing(snapshot: snapshot, location: location) {
let now = Int(NSDate().timeIntervalSince1970 * 1000)
// TODO: exit if snapshot.child("ends").getValue() < now
//...populate my tableview
}
}
A more complex, but more efficient, way to accomplish this would be to remove the expired events from the Geofire location.

Firebase - How can I retrieve the objects in a date range?

I have the next JSON structure in Firebase:
{
"-KGX6kYg1NO6d9hn-8um" : {
"phase" : "A",
"timestamp" : "12-18-2015 19:43:37"
},
"-KGXOGSxa3vompZX9UO_" : {
"phase" : "B",
"timestamp" : "03-28-2016 15:28:21"
},
"-KMUvszD-vm3Nu02sofd" : {
"phase" : "A",
"timestamp" : "04-03-2014 03:57:56"
}
}
Is it possible to filter the objects by the timestamp key through a range of date?.. For example, I want to get the objects with timestamp from January 2015 to today date. If not possible, what's the better way to filter the objects by dates?... I'm developing an iOS app.
Thanks.
You can sort the snapshot data by timestamp and define a timestamp limit from which you want your data.
For example you want all data from a specific timestamp timestamp1, your reference handler should look like:
let refHandle = tableRef.queryOrderedByChild("timestamp").queryEndingAtValue("timestamp1").observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
for item in snapshot.children {
}
})
You can also apply number of records that you want by adding queryLimitedToLast or queryLimitedToFirst like :
let refHandle = tableRef.queryOrderedByChild("timestamp").queryEndingAtValue("some_time_stamp").queryLimitedToLast(kPostLimit + 1).observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
for item in snapshot.children {
}
})
Also, you want to have a look at the following post about common sql queries in Firebase.

Firebase in Swift nested query not working properly

I have a JSON structure like the following:
{
"groups" : {
"-KAv867tzVgIghmr15CM" : {
"author" : "ruben",
"name" : "Item A"
},
"-KAv87nqLEG1Jtc04Ebn" : {
"author" : "ruben",
"name" : "Item B"
},
"-KAv88yZe8KTfkjAE7In" : {
"author" : "ruben",
"name" : "Item C"
}
},
"users" : {
"rsenov : {
"avatar" : "guest",
"email" : "ruben#ruben.com",
"groups" : {
"-KAv867tzVgIghmr15CM" : "true",
"-KAv87nqLEG1Jtc04Ebn" : "true",
"-KAv88yZe8KTfkjAE7In" : "true"
}
}
}
}
Every user has the element "groups" with a childByAutoId() key. Then I have the list of all the groups that exists in the app.
Every time that I run the app, I get the current user logged url reference, and I get the list of the groups of that user (in this case, the logged in user is "rsenov" that has 3 groups).
For every group that this user belongs to, I iterate through the groups url reference, looking for getting the information of that 3 groups.
I do this like this:
func loadTable() {
self.groups = []
var counter = 0
self.meses = []
var tempItems = [String]()
DataService.dataService.CURRENT_USER_GROUPS_REF.observeEventType(.Value, withBlock: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
tempItems = []
for snap in snapshots {
DataService.dataService.GROUPS_REF.childByAppendingPath(snap.key).queryOrderedByChild("name").observeEventType(.Value, withBlock: { snapshot in
if let postDictionary = snapshot.value as? Dictionary<String, AnyObject> {
tempItems.append(snapshot.value.objectForKey("name") as! String)
let key = snapshot.key
let group = Group(key: key, dictionary: postDictionary)
self.groups.insert(group, atIndex: 0)
}
counter++
if (counter == snapshots.count) {
self.meses = tempItems
self.miTabla.reloadData()
}
})
}
}
})
}
I think this is not a good idea of iterating in that way. For example, if there is a change of some child in the GROUPS_REF url, the code only runs in that nested code, and since it doesn't have the "snap.key" value got from the for loop, it doesn't work.
Which is the best way to do a good query in this case?
Phew, that took some time to write. Mostly because I don't iOS/Swift a lot:
let ref = Firebase(url: "https://stackoverflow.firebaseio.com/35514497")
let CURRENT_USER_GROUPS_REF = ref.childByAppendingPath("users/rsenov/groups")
let GROUPS_REF = ref.childByAppendingPath("groups")
var counter: UInt = 0
var groupNames = [String]()
CURRENT_USER_GROUPS_REF.observeEventType(.Value, withBlock: { groupKeys in
for groupKey in groupKeys.children {
print("Loading group \(groupKey.key)")
GROUPS_REF.childByAppendingPath(groupKey.key).observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.value)
if (snapshot.childSnapshotForPath("name").exists()) {
groupNames.append(snapshot.value.objectForKey("name") as! String)
}
counter++
if (counter == groupKeys.childrenCount) {
print(groupNames)
}
})
}
})
By the way, this is how you create a minimal, complete verifiable example. The code has no external dependencies (such as Group and DataService in your code) and only contains what's relevant to the answer.
The important bits:
I used observeSingleEventOfType to get each group, since I don't want to get more callbacks if a group changes
I use snapshot.childSnapshotForPath("name").exists() to check if your group has a name. You probably want to either ensure they all have names or add them to the list with some other property in the real app.
Frank's answer is on-point. I wanted to throw in an alternative that may or may not work for your situation as it requires a slight alteration to the database.
groups
gid_0
author: "ruben"
name: "Item A"
users
uid_0: true
gid_1
author: "ruben"
name: "Item B"
users
uid_1: true
gid_2
author: "ruben"
name: "Item C"
users
uid_0: true
And then some ObjC Code for a Deep Query
Firebase *ref = [self.myRootRef childByAppendingPath:#"groups"];
FQuery *query1 = [ref queryOrderedByChild:#"users/uid_0"];
FQuery *query2 = [query1 queryEqualToValue:#"true"];
[query2 observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"key: %# value: %#", snapshot.key, snapshot.value);
}];
This code does a deep query on the /groups for all groups that have a /users/uid_0 = true. In this case it returns gid_0 and gid_2
It eliminates the need for iterations and multiple calls to the database.
Adding a /users/ node to each group with a list of the uid's may offer some additional flexibility.
Just a thought.

Resources