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.
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 am currently trying to use Firebase, Flashlight and Swift to create an search function to retrieve a random object from my realtime database.
I am trying to perform the following query to Firebase at /search/request
var searchSettings : [Any] = []
if Settings.searchPackage != 99 {
searchSettings.append(["match" : Settings.searchPackage])
}
if Settings.searchCountry != .world {
if let region = Locale.current.regionCode {
searchSettings.append(["match" : region])
}
}
if Settings.searchGender != .All {
searchSettings.append(["match" : Settings.searchGender.rawValue])
}
let postData = [
"index" : "firebase",
"type" : "test",
"body" : [
"query" : searchSettings
]
] as [String : Any]
ref.setValue(postData, withCompletionBlock: { (error, reference) in
if error == nil {
FIRDatabase.database().reference().child("search/response").child(ref.key).observeSingleEvent(of: .childAdded, with: { (snapshot) in
if snapshot.exists() {
print("found random snapshot based on settings \(snapshot)")
}
})
}
})
The problem is, as Firebase described in the documentation it does currently support Arrays, therefor the content of "query" will be:
Flashlight will throw an error because it expects the "query" to contain "match" fields and not the indexes of an array of them.
How would I fix this? I want to be able to search based on multiple fields.
Technically, Flashlight doesn't care at all what you put inside the body tag; it simply passes it on to ElasticSearch. So if there is an error generated about the format, it's ElasticSearch that's doing the complaining.
What you're probably running into here is either a) ElasticSearch doesn't like that syntax, or Firebase's array-like behaviors are converting the array to an object.
Note that Flashlight will allow you to pass a JSON string in place of body. So if this is a result of the array-like behaviors, you can JSON.stringify() the query before passing it into ES, and it will come out the other end as intended.
If the problem is the ES syntax (as it appears to me) then you can simply run the queries directly against ES until they work, and then modify your client to submit correct syntax accordingly.
Take a look at this gist:
BodyQuery in Swift
I wrote this in my Android application project and I'm using this to build queries for ES. Here is equivalent in Java for Android ElasticSearch needs a json query, and you can create it easily using Maps (Android)/Dictionaries (iOS). Enjoy :)
"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.
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.
JSON:
{
"projects":[
{
"id":113,
"name":"Mobile app Android",
"description":"",
"created_on":"2014-10-03T16:53:56+02:00",
"updated_on":"2014-12-03T16:59:45+01:00"
},
{
"id":142,
"name":"Mobile app iOS",
"created_on":"2014-12-11T18:30:55+01:00",
"updated_on":"2014-12-11T18:30:55+01:00"
},
{
"id":52,
"name":"Test project",
"identifier":"grafikr",
"description":"",
"created_on":"2013-10-14T17:21:33+02:00",
"updated_on":"2014-10-10T17:40:47+02:00"
},
{
"id":37,
"name":"Sample project",
"identifier":"grafikf",
"description":"",
"created_on":"2013-09-18T16:31:25+02:00",
"updated_on":"2013-09-26T13:11:58+02:00"
}
],
"total_count":4,
"offset":0,
"limit":25
}
It is easy to access for example name of the first project (with name Mobile app Android) by var name = json["projects"][0]["name"].stringValue
But how do I access all names in SwiftyJSON? If I make a variable var projects = json["projects"], it gives me:
[
{
"id" : 113,
"created_on" : "2014-10-03T16:53:56+02:00",
"name" : "Mobile app Android",
"description" : "",
"updated_on" : "2014-12-03T16:59:45+01:00"
},
...
Now I don't have a problem with making a NSDictionary from data anymore, but this drives me crazy.
There's a lot going on in your code sample—perhaps too much to be addressed by a single Stack Overflow question / answer.
I would strongly recommend going back to Apple's resources for Swift and iOS application patterns. Topics to revisit would include synchronous versus asynchronous programming, authentication, and using data sources with table views.
This should probably work. Your json also contains "total_count", which looking at it, I assume that's the count of the number of projects. Pull that count out, loop over till the count and fetch the name.
var names = [String]()
let count = json["total_count"].int
for index in 0..<count {
let name = json["projects"][index]["name"].string
names.append(name)
}
Why not use SwiftyJSON the Swifty way???
Try this:
let names = json["projects"].arrayValue.map {
$0["name"].stringValue
}