How to get childs whose arrays contain a certain value - ios

I have a database scheme that is essentially the same that the one is the documentation :
// An index to track Ada's memberships
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
},
...
}
}
I would like to create a DatabaseQuery that gets all users that take part of a specific group (for instance users whose "groups" contain "techpionneers")
How should I proceed ? I tried a Database.database().reference(withPath: "users").queryOrdered(byChild: "groups").queryEqual(toValue: "techpioneers") but that does not work (obviously).

To load all users that are part of a specific group, start in the /groups node to find their key:
ref.child("groups/techpioneers/members").observeSingleEvent(of: .value, with: { (snapshot) in
That gives you the keys of all members. Then loop over those keys and load the corresponding user.
This is essentially a client-side join operations. While you may initially think this is incredibly slow, it's actually not all that bad since Firebase pipelines the requests over a single connection. See http://stackoverflow.com/questions/35931526/speed-up-fetching-posts-for-my-social-network-app-by-using-query-instead-of-obse/35932786#35932786.

Franks solution is spot on but an alternative is to use a Deep Query i.e. ordering by deep paths to query the users node for child->child nodes that match the criteria. Here's the format
let ref = self.ref.child("users")
let query = ref.queryOrdered(byChild: "groups/techpioneers").queryEqual(toValue: true)
query.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
print(snap)
}
})
the result is all of the users that are part of the techpioneers group
"alovelace": {
"name": "Ada Lovelace",
"groups": {
"techpioneers": true,
"womentechmakers": true
}
}

Related

Swift Firebase Database: How to filter values by child key

I don't understand how to filter the data lists
my db has the following form
{
"numbers": [0, 1, 2, 3, 4, 5],
"models": {
"random_string_key": {
"name": "model_name",
"value": "some_value"
},
"random_string_key-2": {
"name": "Name",
"value": "any value"
}
}
}
How i can get array from numbers where all values < 3?
How i can filter models and get array models where value == "some_value"?
I would like to understand what I am doing wrong
let ref = Database.database().reference()
let refNumbers = ref.child("numbers")
refNumbers
.getData(completion: { error, snapshot in
// snapshot.value == [0, 1, 2, 3, 4, 5]
// OK
})
refNumbers
.queryOrderedByValue()
.queryEnding(beforeValue: 2)
.getData(completion: { error, snapshot in
/*
error: Unable to get latest value for query FQuerySpec (path: /numbers, params: {
en = "[MIN_NAME]";
ep = 2;
i = ".value";
}), client offline with no active listeners and no matching disk cache entries
why???
*/
})
let modelsRef = ref.child("models")
modelsRef
.getData(completion: { error, snapshot in
// snapshot.value == NSDictionary
// OK
/*
[
"key": String
"value": NSDictionary
]
*/
})
modelsRef
.queryEqual(toValue: "some_value", childKey: "value")
.getData(completion: { error, snapshot in
/*
error: null
snapshot.value == nil
why???
*/
})
modelsRef
.queryOrdered(byChild: "value")
.queryEqual(toValue: "some_value")
.getData(completion: { error, snapshot in
/*
Unable to get latest value for query FQuerySpec (path: /models, params: {
ep = some_value;
i = value;
sp = some_value;
}), client offline with no active listeners and no matching disk cache entries
why???
*/
})
I tried all the options that I found on the Internet but the result is 0
Either I get all the data from the list and filter it in the app, or I get nothing
Is it possible to filter the data upon receipt?
It is a bit difficult to give you a solution to the issues you described above but I can tell you where you're going wrong.
For #1, when you do the below
...
refNumbers
.queryOrderedByValue()
.queryEnding(beforeValue: 2)
.getData(completion: { error, snapshot in
...
})
refNumbers is not an array of numbers, it is an 'object'. And queryOrderedByValue() will not work on this 'single object', neither will .queryEnding(beforeValue: 2). You either need to do what you're doing, which is to get the entire data, convert to swift native types and filter, or you need to restructure your data on the DB side.
Similarly, in-case of #2, the object modelsRef is composed of a number of objects with random keys. So, when you perform a .queryEqual(toValue: "some_value", childKey: "value") operation, it will not find the child-key named 'value'. This child key is actually a child-key for the objects that modelsRef is composed of.
So, again, either you need to get all this data, type cast to native swift types and then filter, or somehow restructure your data.
So, the answer to your question is essentially either continue what you're doing (get data to the app and filter using native swift API which may present scalability challenges later depending on the amount of data), or, restructure your data.
Example with queryStarting & queryEnding
As requested, here is what works for me.
Database design
I have an event based app, using Firebase Realtime Database, with one parent node, lets call it events for now, because the real example is mainly in German.
Under events there is one child for each event, obviously:
{
"events": {
"-L_nMRK8mzXal47IE54x": {
"endDate": 1568715118,
"lat": 48.4382387,
"lon": 10.0499972044298,
"name": "Exampletown - event",
"city": "Exampletown",
"zip": "12345",
"startDate": 1568325600,
"street": "Street 11"
},
"-L_nMRK8mzXal47IE54y": {
"endDate": 1568715118,
"lat": 49.4382387,
"lon": 10.0499972044298,
"name": "Exampletown - event 2",
"city": "Exampletown",
"zip": "12345",
"startDate": 1568325600,
"street": "Street 12"
},
}
}
Swift 4.2
Inside a method I use the following to query the database for all past events based on the current timestamp:
let ref = Database.database().reference()
let query = ref.child("events").queryOrdered(byChild: "endDate").queryStarting(atValue: diffInterval, childKey: "endDate").queryEnding(atValue: now, childKey: "endDate")
// observe single event is sufficient for my app
query.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.childrenCount > 0 {
for snap in snapshot.children {
// create an object for each of the objects of the snapshot
guard let eventData = self.getEventsFromSnapshot(snap: snap as! DataSnapshot) else {return}
// do something with eventData
}
} else {
// custom logging and return of empty array
}
}

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

Query Not Working Firebase Swift

I have a Firebase that its structured as follows:
"zipcodes"
12345
1
"city": "test"
"state": "XX"
98765
2
"city": "test2"
"state": "AA"
3
"city": "test3"
"state": "BB"
I am trying to query my database by the user's zip code; however, when I try to view the snapshot that it found, it is null. Here is the code I currently have:
func firebaseSearch(zipCode: String) {
let conditionRef = FIRDatabase.database().reference().child("zipcodes")
let query = conditionRef.queryEqualToValue(zipCode)
query.observeSingleEventOfType(.Value, withBlock: {snapshot in
print(snapshot.value)
for child in snapshot.children {
print(child)
}
})
}
When I run the code, I get this:
(/zipcodes {
ep = 12345;
sp = 12345;
})
Optional(<null>)
My database has around 77k entries, so I was worried iterating through all entries and trying to find the child with my zipCode value would take a large amount of time, so I am trying to use the query feature to see if it's any quicker. I appreciate any help, thank you very much!
You don't need query for this, use .child(zipCode) instead of .queryEqualToValue(zipCode). That will return the expected result.
Hope this helps!!

Firebase queryOrderedByChild() method not giving sorted data

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.

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.

Resources