Swift - Firebase Search in Database

Swift - Firebase search in database

You are using queryStartingAtValue. From the documentation:

The FIRDatabaseQuery instance returned by
queryStartingAtValue:childKey will respond to events at nodes with a
value greater than startValue, or equal to startValue and with a key
greater than or equal to childKey.

So keep in mind that if you search for the name of an user it will give all the users that have name greater in alphabetic order than the given one. You need to set a queryEndingAtValue to limit the query range.

ref.child("Users").queryOrderedByChild("name").queryStartingAtValue(text).queryEndingAtValue(text+"\u{f8ff}")

Firebase legacy documentation is such a good resource to understand on how firebase handle queries. There you will find:

The f8ff character used in the query above is a very high code point
in the Unicode range. Because it is after most regular characters in
Unicode, the query matches all values that start with a b.

You might also get interested in Elasticsearch. Flashlight is a node service that integrates elasticsearch and firebase.


Update:

As pointed out in the comments this won't work if the string you are searching for has upper case characters. The solution in this case is to have an additional field in the object with the info you are searching all in lower case. So if you have Satoshi Nakamoto as name you would save an additional nameSearch property with satoshi nakamoto.

And finally make sure your searching string is in lower case too.


There is a few other problems with your code.

  • You are setting an observer. It will trigger the callback anytime your data changes so you should be resetting the users array inside the callback so you don't have duplicated and dirty data.

  • Your function won't return the users with the data since firebase call is async. When you call return users you won't have the users array populated. I recommend you using self.users = users instead of returning it in the function. Otherwise, if you want to proceed with the current approach and return it, you should set a completion handler.


class func findUsers(text: String)->Void{
ref.child("Users").queryOrderedByChild("name").queryStartingAtValue(text).queryEndingAtValue(text+"\u{f8ff}").observeEventType(.Value, withBlock: { snapshot in
var user = User()
var users = [User]()
for u in snapshot.children{
user.name = u.value!["name"] as? String
...
users.append(user)
}
self.users = users
})
}

Swift: Search for a specific value in Firebase Database & find all associated data

1. Realtime Database

Since you haven't included the structure of your database, I assume you have a database structure for drinks like below:

Screenshot of my Realtime database for this answer

{
"Drinks" : {
"-LYiUHm4vtrB3LqCBxEc" : {
"location" : "toronto",
"name" : "pepsi max",
"price" : 13.5,
"rating" : 3.6
},
"-LYiUHm5Lgt3-LENTdBZ" : {
"location" : "new york",
"name" : "diet coke",
"price" : 15.45,
"rating" : 5
},
"-LYiUHm5Lgt3-LENTdB_" : {
"location" : "chicago",
"name" : "mountain dew",
"price" : 2,
"rating" : 2
},
"-LYiUHm5Lgt3-LENTdBa" : {
"location" : "vancouver",
"name" : "sprite",
"price" : 6.98,
"rating" : 4.5
}
}
}

2. Swift 4.0

Now, to search any drink by name use below code:

func search(drinkName: String) {
let databaseRef = Database.database().reference().child("Drinks")
let query = databaseRef.queryOrdered(byChild: "name").queryStarting(atValue: drinkName).queryEnding(atValue: "\(drinkName)\\uf8ff")

query.observeSingleEvent(of: .value) { (snapshot) in
guard snapshot.exists() != false else { return }
//print(snapshot.value)
DispatchQueue.main.async {
// Update TextFields here
}
}
}

The \uf8ff character used in the query above is a very high code point in the Unicode range. Because it is after most regular characters in Unicode, the query matches all values that start with a b.

Source: https://firebase.google.com/docs/database/rest/retrieve-data

Note: queryOrderedByChild() is case-sensitive. It is nice practice to save all fields lowercased in database as this makes it easier to query data. You can always format strings in front end.

3. Add ".indexOn" to Realtime Database's Rules

In order to above query to work and achieve better performance, you need to set the index on the field that you are going to search by.
You can do this by going to Rules tab and adding index like below:

{
"rules": {
".read": true,
".write": true,
"Drinks": {
".indexOn": "name"
}
}
}

Source: More information on indexing data

Updated Answer for your updated question:

func searchT() {
// You must cast pub variable as String.
guard let pub: String = pubName.text else { return }

print(pub)


let databaseRef = Database.database().reference().child("Drinks")

let query = databaseRef.queryOrdered(byChild: "pub").queryStarting(atValue: pub).queryEnding(atValue: "\(String(describing: pub))\\uf8ff")

query.observeSingleEvent(of: .value) { (snapshot) in
guard snapshot.exists() != false else {
print("failing here")
return }
print(snapshot.value as Any)
DispatchQueue.main.async {

guard let dict = snapshot.value as? [String:Any] else {
print(snapshot)
return
}

let pubName = dict["pub"] as? String
let pubLocation = dict["location"] as? String
let price = dict["price"] as? String
let rating = dict["rating"] as? String
let comment = dict["comment"] as? String
}
}
}

Swift: Searching Firebase Database for Users

Suppose the Firebase structure is like this

Example 1 :

users
-> uid
->otherdata
->userdata
->username : "jen"
-> other keys

-> uid2
->otherdata
->userdata
->username : "abc"
-> other keys

And to query based on username, we need to query on queryOrdered(byChild: "userData/username"). queryStarting & queryEndingAt , fetches all the names starting from "je" and this "\uf8ff" signifies * (any) .

let strSearch = "je"
baseRef.child("users").queryOrdered(byChild: "userData/username").queryStarting(atValue: strSearch , childKey: "username").queryEnding(atValue: strSearch + "\u{f8ff}", childKey: "username").observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
}) { (err) in
print(err)
}

If the username is directly below the users node, then try this

Example 2 :

users
-> uid
->username : "jen"
-> other keys

-> uid2
->username : "abc"
-> other keys

The query will be restructured as below,

 let strSearch = "je"
baseRef.child("users").queryOrdered(byChild: "username").queryStarting(atValue: strSearch).queryEnding(atValue: strSearch + "\u{f8ff}").observeSingleEvent(of: .value, with: { (snapshot) in

print(snapshot)

})

Please note this method is case-sensitive . The search results for search String = "je" and "Je" will be different and the query will match the case.

How do I search for a particular item in my database using Swift and Firebase?

You need to take into account there may be a significant number of words so loading all of the words in will not only be slow as they have to loaded and then iterated over to find the word you are looking for, but it may also overwhelm the devices memory.

A simple solution is a Firebase Query - let Firebase do the heavy lifting and just return the node you want. It will be a LOT faster and won't overwhelm the device.

Here's a function that will tell you if a word exists within your firebase structure

func findWord(aWord: String) {
let ref = self.ref.child("wordList")
let query = ref.queryOrdered(byChild: "word").queryEqual(toValue: aWord)
query.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
print("found the word")
//you could expand on this to indicate it was a Noun etc
} else {
print("\(aWord) is not in the wordList")
}
})
}

Also review the Firebase Sorting and Filtering Data Guide

*this assumes a structure of

root
wordList
word_id_0 //created with .childByAutoId
word: "Water"

swift firebase search user results

The only filtering mechanism is queries, but that will give you a subset of the nodes directly under users.

There is no way to get a subset of each node. If that is required for your use-case, consider splitting your data in two top-level nodes (/userinfos and /usersettings), both using the UID as the key, and then loading it from the one node that is (or both nodes that are) relevant to the use-case.

Also see:

  • Firebase: How to structure public/private user data
  • Firebase security rules to read partial objects
  • Firebase realtime database: How to listen to a specific subset of items of a node?
  • Firebase Rules to read Specific Leaf / Child Node from Parent Nodes

Swift - Firebase : How to search with multi data?

func filterContent(searchText:String, entries:[String]){
self.filteredUsers = self.userArray.filter{ user in
guard let user = user as? User else { return false }
var array:[Bool] = [Bool]()
for key in entries {
let value = user[key] as? String
let entry = value?.lowercased().contains(searchText.lowercased()) ?? false
array.append(entry)
}
return(array.contains(true))
}
tableView.reloadData()
}

How To Search in Database Firebase

If you are looking for finding the node which has a specific name try this:-

  let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Items").queryOrdered(byChild: "Name").queryEqual(toValue: "Fadi").observeSingleEvent(of: .value, with: { (snapShot) in

if let snapDict = snapShot.value as? [String:AnyObject]{

for each in snapDict{
let key = each.key as! String
let name = each.value["Name"] as! String
print(key)
print(name)
}
}
}, withCancel: {(Err) in

print(Err.localizedDescription)

})

How can I create a search function for firebase firestore?

After some more creative thinking, I decided to create keywords for each document. It is not super elegant but it works. Also each "name" field is limited to X characters so I do not need to worry about the keywords field exceeding more than 20-30 individual keyword strings.

I added

var keywords: [String]?

to the StoryModel and the fields looks like this once filled in on doc creation:
keywords

And I am getting the live results with:

private lazy var storiesDataCollection = Firestore.firestore().collection("Stories")

self.storiesDataCollection
.whereField("keywords", arrayContains: self.storiesSearchText)
.limit(to: 8)
.getDocuments {}

I am not including the function that creates the keywords because I have simply not done it yet, I will edit and add it once I complete it, but for testing I just manually created the keywords in firestore. Lastly, this solution does not ignore case, the search text has to match perfectly.

EDIT: Here is the function that creates the keywords, I also added the ability to make a case matched and lowercased for better searching.

    func createKeywords(name: String) {
var keywords: [String] = []
var lowercaseKeywords: [String] = []
var alreadyKeyed: String = ""
for letter in name {
if keywords.isEmpty {
keywords.append("\(letter)")
let lower = letter.lowercased()
lowercaseKeywords.append("\(lower)")
} else {
let key = alreadyKeyed + "\(letter)"
keywords.append(key)
let lower = key.lowercased()
lowercaseKeywords.append("\(lower)")
}
alreadyKeyed.append("\(letter)")
}
let allKeywords = keywords + lowercaseKeywords
self.storiesDataCollection.document("doc id goes here").updateData([
"keywords" : allKeywords
]) { error in
if let error = error {
print("Error: \(error)")
} else {
print("SUCCESS")
}
}
}

If you were to feed it a string of "Trevor's NYE Party" here is what it would give back:

["T", "Tr", "Tre", "Trev", "Trevo", "Trevor", "Trevor\'", "Trevor\'s", "Trevor\'s ", "Trevor\'s N", "Trevor\'s NY", "Trevor\'s NYE", "Trevor\'s NYE ", "Trevor\'s NYE P", "Trevor\'s NYE Pa", "Trevor\'s NYE Par", "Trevor\'s NYE Part", "Trevor\'s NYE Party", "t", "tr", "tre", "trev", "trevo", "trevor", "trevor\'", "trevor\'s", "trevor\'s ", "trevor\'s n", "trevor\'s ny", "trevor\'s nye", "trevor\'s nye ", "trevor\'s nye p", "trevor\'s nye pa", "trevor\'s nye par", "trevor\'s nye part", "trevor\'s nye party"]

note the backslashes are not actually uploaded to firestore.

Query Searches Swift

You'll want to use a Firestore query, instead of performing the search in your application code. With a Firestore query, the filtering is performed by the database, instead of in your application code.

The simplest way to search is:

let dbQuery = COLLECTION_USERS
.whereField("fullname", isGreaterThanOrEqualTo: query)
.whereField("fullname", isLessThanOrEqualTo: query+"\u{F7FF}")

This query searches for documents where the fullname starts with the given value.

A few things to note:

  • Firestore queries are case-sensitive, so this matches only when the case also matches exactly. You can emulate lower-case queries, by storing the lower-case version of the fullname in a separate file (e.g. fullname_lowercase) and searching on that. For more on this, see Cloud Firestore Case Insensitive Sorting Using Query and Are Cloud Firestore queries still case sensitive?
  • This query only searches for documents where the fullname starts with the requested value, not where it contains that value in another place. If you want that, consider using a dedicated search engine.


Related Topics



Leave a reply



Submit