Database count of values - swift

I my database structure userDatabase/userID/Customers I have two customers. For example path:
usersDatabase
g8voYf1yoChnpmhkoPgtmO4FQT62 - (uid)
Customers
Tom Smith (customer with custom ID)
-LDFZw1tca8KOrnqyyWH - (auto id of customer child)
Status of Service: "Open service"
Ben Thomas (customer with custom ID)
-LDFZw1tca8KOjgoenBN - (auto id of customer child)
Status of Service: "Open service"
Is possible to fetch count of value "Open service" form all customers in my database? Now I only know, how to print this value for each customers...
My code to get value from Database:
let userID = Auth.auth().currentUser?.uid
let usersDatabaseRef = Database.database().reference().child("usersDatabase").child(userID!).child("Customers")
usersDatabaseRef.observe(.value, with: { snapshot in
var totalCustomerCount = 0
for child in snapshot.children {
let childSnap = child as! DataSnapshot
let childrenRef = childSnap
totalCustomerCount += Int(childrenRef.childrenCount)
print("user \(childSnap.key) has \(childrenRef.childrenCount) customers")
let userCustomerSnap = childSnap
for customer in userCustomerSnap.children.allObjects as! [DataSnapshot] {
let customerSnap = customer
let dict = customerSnap.value as! [String: Any]
let stat = dict["Status of Service"] as! String
let myStatistic = PrintModel(status: stat)
self.statistic.append(myStatistic)
print("Statistic: \(String(describing: myStatistic.status))")
}
}
print("... and there are \(totalCustomerCount) total customers")
})
For example my log now show:
user Tom Smith has 1 customers
Statistic: Optional("Open service")
user Ben Thomas has 1 customers
Statistic: Optional("Open service")
but I want to show:
Statistic: 2

When structuring Firebase data, the structure is generally based on what you want to get out of it. In this case the data you want is too deep within the posted structure to be useful. So, changing the structure will make this a snap.
Separate your data into their own nodes, denormalizing the data. Like this
users
uid_0
//some info about this user
open_service_calls
auto_id_0: true
auto_id_1: true
uid_1
//info about this user
customers
customer_id_0
customer_name: "Tom Smith"
open_service_calls
auto_id_0: true
customer_id_1
customer_name: "Ben Thomas"
open_service_calls
auto_id_1: true
service_calls
auto_id_0
customer_id: customer_id_0
user_id: "uid_0"
status_of_service: "Open service"
auto_id_1
customer_id: customer_id_1
user_id: "uid_0"
status_of_service: "Open service"
That allows for queries by customer, user, and to address this question, an easy count of all Open Service in the database for all users and customers; query the service_calls/status_of_service for "Open service"
It also allows you to quickly access all service calls, open or closed for any user, any customer and even just open service calls for a users's customer.
Additional nodes would offer further query flexibility - storing the user id within the customer node would allow a super easy query to retrieve all of the customers for a specific user even if they have no service calls; it all depends on what you want to get out of Firebase.
--- Old answer is below ---
Per my comment to the original question, this answer involves using a Deep Query to query a child of child node of customers. The idea here is that within the user node, there's a Customers node where each child key is a customer name and the value of that node is a key: value pair of the string "serviceID" and then a value which may contain other child nodes about the service.
The important part of a Deep Query is to keep the child keys you are query'ing named consistently - in this case we use the key 'serviceID' so the query can properly resolve the path and then any of the child nodes of that can be queried: status_of_service, perhaps a time stamp or even the service location
The initial structure before it was changed was
Customers
Tom Cruise
serviceID
status_of_service: "Open service"
//timestamp of service?
//location of service?
//other data about service we may need to query?
Ben Smith
serviceID:
status_of_service: "Open service"
Note that for this answer self.ref = my firebase so Customers is a direct child of that path.
let ref = self.ref.child("Customers")
let query = ref.queryOrdered(byChild: "serviceID/status_of_service")
.queryEqual(toValue: "Open service")
query.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
print(snap)
}
})
the output is two customer nodes
tom_smith
serviceID
status_of_service: "Open service"
another_customer
serviceID
status_of_service: "Open service"

Related

Firebase BarcodeScanner get parent/other nodes of a specific child

In my app I have certain items in my firebase database like this:
Categories
Category1
Item1
id: item1
name: Item 1 name
barcode: 473088034839
item2
id: item2
name: Item 2 name
barcode: 564084724885
These items are in a collectionview. I have another view where I'm using a barcodeScanner to scan the barcode of the products in my database. The barcode scanner works great and I'm able to print the barcode of scanned products to the console, and it matches the codes in "barcode" in my database.
The problem is I'm trying to get the name if the item I'm scanning. With the following code I'm able to find/match the barcode I'm scanning with the code in my database:
let someBarcode = Database.database().reference().child("Categories").queryOrdered(byChild: "barcode").queryEqual(toValue: code)
print(someBarcode)
code is the string of numbers I get from scanning a barcode. After finding the correct barcode in my database, how can I then retrieve the current Items id and/or name?
Firebase Realtime Database queries work on a flat list. They can order/filter on a property at a fixed path under each direct child under the location you query on.
So in your scenario you could query across all categories on a fixed property of each category, or you can query a specific category for items with a specific property value. But you can't search across all categories for the ones that contain an item with a certain property value.
The solution is to create an data structure that allows the specific query, typically either by flattening the list you already have, or by adding an additional structure to allow the lookup.
For more on this, see:
Firebase Query Double Nested
Firebase query if child of child contains a value
With Franks help I managed to create a new node in my firebase DB with the barcode codes:
Barcodes
item1 : code1
item2 : code2
item3 : code3
Then I used the following function to
func scanner(_ controller: BarcodeScannerViewController, didCaptureCode code: String, type: String) {
// get product id from Barcodes
Database.database().reference().child("barcodes")
.observeSingleEvent(of: .value) { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else {
self.showNotFoundMessage()
return
}
for (key, value) in dict {
let valueString = "\(value)"
if valueString == code {
self.getProduct(id: key)
return
}
}
self.showNotFoundMessage()
}
}

Retriving data from database

First time asking a question here, so sorry if I do it wrong.
Anyways. I'm using Firebase Database to store "Results" in my Quiz app. When data is store it looks like this
Results
-LjQ34gs7QoL1GMufiMsaddclose
Score: xx
UserName: xx
-LjQ3NeCoDGob8wnhstH
Score: xx
UserName: xx
I would like to access score and username from it and display it in a HighScore tableview. Problem is - I can get the "Results" node, but because of the id of the results (ie LjQ34gs7QoL1GMufiMsaddclose) I don't know how to access the score and username.
I got the data snapshot​, but not sure how to "bypass" the id to get to score and username.
Hope I made it at least a bit clear, what the problem is.
let ref = Database.database().reference().child("Results")
ref.observe(.value) { (DataSnapshot) in
print(DataSnapshot.value as Any)
}
You current code gets you a single snapshot with the results of all users. You'll need to loop over the child snapshots to get the result of each user, and then look up their specific properties with childSnapshot(byName:):
let ref = Database.database().reference().child("Results")
ref.observe(.value) { (snapshot) in
for case let userSnapshot as DataSnapshot in snapshot.children {
print(userSnapshot.childSnapshot(forPath: "Score").value)
}
}
Also see:
How to get all child data from firebase without knowing the path
Iterate through nested snapshot children in Firebase using Swift
How do I loop through and get all the keys of the nested nodes in firebase?
Retrieving Data using Firebase Swift
And probably some more from this list.

How to implement a firebase transaction

I have this schema in my firebase :
I have a node articles under it have the ids of users, under one id I have a wishlist name then the articles and the wishlist info, So when I want to change the wishlist name I take the children of the node I want to change then I create w new one with the new name and the same children then I remove the old wishlist. So I want to create a transaction to do all the work in same time or not to do it at all if there is a bad connection because I can loose the data if the operation is interrupted. This is my current code and how I can put the operation in a firebase transaction. Thank you for your help !
func updateWishlist(wishlist:WishList,newName:String){
if((reachability.connection == .wifi) || (reachability.connection == .cellular)){
self.showProgressView()
let child = self.ref.child("Articles").child((user!.uid)!)
self.ref.child("Articles").child((user!.uid)!).child(wishlist.name) .updateChildValues(["name":newName]) { error, _ in
if(error == nil){
//create new node with new name
let oldName=wishlist.name
child.child(wishlist.name).observeSingleEvent(of: .value, with: { (snapshot) in self.ref.child("Articles").child((GlobalVar.user!.uid)!).child(newName).setValue(snapshot.value as! [String : Any]) { error, _ in
if(error == nil){
self.dismissHUD(isAnimated: true)
self.title=newName
self.showMessage("Wishlist modifier !", type: .success, options: [.position(.bottom)])
self.ref.child("Articles").child((GlobalVar.user!.uid)!).child(oldName).removeValue()
}
else{
self.dismissHUD(isAnimated: true)
print("update wishlist name transaction failed")
}
}
user?.wishLists[self.passedWishlistIndex!].name=newName
})
}
self.dismissHUD(isAnimated: true)
}
}
}
This issue really comes down to this statement
I want to change the wishlist name I take the children (out) of the node I
want to change then I create a new one with the new name and the same
children then I remove the old wishlist
The fix is to not use dynamic data as node keys. Restructuring your data will eliminate the need for a transaction.
The wishlist name should be stored as a child of the node with a key created with .childByAutoId.
To clarify, your structure is currently this
Articles
article_0
dadoune //wish list name
solo //with list name
articles
xxx
yyy
zzz
artical_1
artical_2
and here's what will work; move the wish list name to a child node.
Articles
article_0
-Jk0ksk0kj9sdfsdf //wish list key created with .childByAutoId
wish_list_name: "dadoune" //store the name as a child
-Jyl909m9mm3o99jt //wish list key created with .childByAutoId
wish_list_name: "solo" //store the name as a child
articles
xxx
yyy
zzz
article_1
article_2
By storing the dynamic wish list name as a child, you can simply change it whenever you want without having to read the node, delete the node, change the name, and re-write the node.

Query and filter by child when child is a list

I'm having some trouble figuring out how to filter and query when my data is a list. In my current database model, I have conversations and then under each UID are the participants of that conversation. I want to be able to look up all conversations a specific user is a part of.
Im not sure how I would write my code when there is no real "child"
let query = Constants.refs.databaseConvo.queryOrdered(byChild: "").queryEqual(toValue: username)
query.observe(.value, with: { (snapshot) in
for childSnapshot in snapshot.children {
print(childSnapshot)
}
})
Due to the limitations of Firebase queries, you will need to create a new collection in your database that keeps track of all of the conversations that a user is a part of. It should be structured something like this:
usersConversations {
userID1 {
conversationID1: timestamp (or what ever value you would like, I would recommend a timestamp they joined so you can query the latest conversations, etc.)
conversationID2: timestamp
conversationID3: timestamp
userID2 {
conversationID1: timestamp
conversationID2: timestamp
} ... and so on
}
You will need to add and delete to this collection whenever a user joins or leaves a conversation as well as your existing conversations collection.
You can then get all of the conversations a user with the uid userID is a part of by doing something like this:
databaseRef.child("usersConversations").child(userID).observeSingleEvent(of: .value) (snapshot) in {
if snapshot.exists() {
// each snapshot child's key will be a conversationID that they are a part of
} else {
// the user is part of no conversations
}
}

Indexing relational data correctly in Firebase Swift

I would like to check that I am indexing my relational data correctly, as I am trying to wrap my head around Firebase. I have a two way relationship between users and addresses, both are one to one. The idea is that the user node is initially created with just their email, then when they add their address, a key for the addressID is added to the user node and simultaneously, a key for the userID is added to the address node. This is meant to be similar to how it is recommended in the "Structure Data" section of the Firebsae docs (at the bottom). However, to identify the address within the user node and to identify the user within the address node, I have used their auto generated key (i.e. address: userAutoID, user: addressAutoID; rather than userAutoID: true and addressAutoID: true; as the former is more descriptive and easier to understand. The use of an autogenerated ID as a key doesnt indicate what the key represents). The JSON structure is as follows (both nodes are children of the top-most node):
As you can see the address possesses the user's ID, and the user possesses the address ID. I feel as though the code I have used to achieve this is protracted and I could be going about this the wrong way. The code is triggered when the user has filled out a form for their address and they press submit. Hence, the User node exists before the address node. I use a for loop to sequentially obtain the key of each address that exists, which i then feed into another query, which then looks to find if the current user's ID exists whithin the current address node as the value of the userID key. If this is true, then I set the ID of the current address to the value of the addressID within the current users associated node. Here is the code:
self.rootRef.child("Addresses").observeSingleEvent(of: .value, with: { snapshot in
for address in snapshot.children {
guard let addressSnapshot = address as? FIRDataSnapshot else {
print("failed to get addressSnapshot")
return
}
let addressSnapshotKey = addressSnapshot.key
self.rootRef.child("Addresses").child(addressSnapshotKey).observeSingleEvent(of: .value, with: { snapshot in
guard let snapshotValue = snapshot.value as? [String: AnyObject] else {
print("error getting snapshotValue")
return
}
let userID = FIRAuth.auth()?.currentUser?.uid
if (snapshotValue["userID"] as! String) == userID {
let correctKey = addressSnapshotKey
let userRef = self.rootRef.child("Users").child(userID!)
userRef.child("addressID").setValue(correctKey)
}
})
}
})
I can see that running two queries from one piece of code, may become expensive when the User and Addresses node become very large. Any feedback would be greatly appreciated!
I would reuse the auto generated User key as the key under Addresses as well.
- Users
- xyz (auto generated)
- email: jo#jo.com
- Addresses
- xyz (use the previously created id here too)
- street
- author
- lat
- lon