iOS - Firebase Filter Query

How to filter Firebase data in Swift?

You have a few small mistakes in there. Overall nothing too bad, but combined they'll never work:

  1. calling any of the query... methods returns a new object
  2. you need to orderByChild() before you can filter on its value
  3. you need to loop over the results

Combining these:

let ref = FIRDatabase.database().referenceFromURL(FIREBASE_URL).child("topics")
let query = ref.queryOrderedByChild("published").queryEqualToValue(true)
query.observeEventType(.Value, withBlock: { (snapshot) in
for childSnapshot in snapshot.children {
print(childSnapshot)
}
})

We get this question regularly. For example, this from yesterday looks very similar: Firebase Query not Executing Properly. Since my explanation varies with every answer, I recommend browsing a bit to read my relevant answers until it clicks.

Firebase query on multiple filters in Swift

The code you show shouldn't event compile. Firebase Database queries can only contain a single orderBy... clause, and can never do suffix matching (your _NCAA is at the end of the key).

To allow you use-case you should have a key/property that consists of the week number, and the org (NCAA/NFL) value. E.g.

Games
--- 20190220000_NCAA (SportID)
--- WeekId : 1
--- WeekOrg : "01_NCAA"
--- etc...
--- 20190221000_NCAA
--- WeekId : 2
--- WeekOrg : "02_NCAA"
--- etc...
--- 20190204000_NFL
--- WeekId : 1
--- WeekOrg : "01_NFL"
--- etc...
--- etc...(SportsID)

With this in place you can get the nodes in week 1 for the NCAA with:

gamesRef.queryOrdered(byChild: "WeekOrg").queryEqual(toValue: "01_NCAA").observe(.value, with: { snapshot in

Also see my answer here: http://stackoverflow.com/questions/26700924/query-based-on-multiple-where-clauses-in-firebase

How to apply multiple filter in Firebase query in Swift?

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.

Advanced Firebase Query with filter in Swift

The to-the-point answer is: You can't do this type is search in Firebase.

Let me present a wall-of-text answer that will hopefully lead to a solution.

To Be Quite Frank:
As Frank had mentioned in his on-point comments and links, leveraging other products such as ElasticSearch can be one solution. While they do provide scalability, it adds another product to the equation. I would suggest exploring those options further.

Filtering is Cool:
A second solution is to filter in code. While this is a good solution for a couple thousands records, it's not scalable for tens/hundreds of thousands of records. However, this is the best solution if you have a complex data structure and a limited amount of data.

On that note, you can filter in code even with millions of records if the UI is structured to make it work. Decide on one to or two main searches, gender for example. Then perform a query for all females. That cuts your dataset by half and is much more manageable in code. You can also further reduce your dataset - see next section.

Change is good:
Another option is to structure your data to match the types of queries you will be performing. For a simple example: Suppose you have three items you want to query on; gender_country_age

Your Firebase structure would be

users
-Jyiai09jsi
data: "male_US_40"
-Jqkjisjida
date: "male_US_27"
-JyHYjlkall
data: "male_US_30"

Then to query for all male users in the US between the ages of 30 and 40

usersRef.queryOrderedByChild("data").queryStartingAtValue("male_US_30")
.queryEndingAtValue("male_US_40").observeSingleEventOfType(
.Value, withBlock: { snapshot in
print(snapshot)
})

The upside here is that's it's scalable but the downside is you can't query for just US users. On the flip side, this is a much smaller dataset which you could further filter in code.

Duplicate data is your friend:
The good news is there's a solution for that as well: disk space is cheap so duplicate your data

user_countries
US
-Jyiai09jsi: true
-Jqkjisjida: true
-JyHYjlkall: true
UK
etc etc

user_gender
male
-Jyiai09jsi: true
-Jqkjisjida: true
-JyHYjlkall: true
female
etc etc

user_speaks
da_UK
users
fr_FR
users

This structure give you super quick access to data groups; countries, gender etc. I used true here as a placeholder but technically you could have each users node in that spot as well. But, that would again be reading in a lot of data during the query; a bunch of 'true' nodes is a pretty tiny amount of data, even with thousands of nodes.

SQL ftw!
Something else to consider is how you are using the asynchronous nature of Firebase. Do you really need to have that data stored in Firebase or can you store that data in another cloud based SQL server for queries and store links to that data within Firebase. That way you can SQL query to your hearts content and then use Firebase for messaging, updates etc.

Final thought Your best bet if you want to to these kinds of searches is to structure your data in a way that reduces it's footprint as quickly as possible, then filter the rest in code. Imagine having a million records and then querying for male_US_30_FR. Now you have a couple of thousand records that's easily loaded and further filtered in code

I hope one or a combination of these helps.



Related Topics



Leave a reply



Submit