0

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:

  1. user Tom Smith has 1 customers
  2. Statistic: Optional("Open service")
  3. user Ben Thomas has 1 customers
  4. Statistic: Optional("Open service")

but I want to show:

  1. Statistic: 2
  • I edited your question and updated the Firebase Structure so take a look. To answer the question, sure. Just set a reference to your_firebase/customers and perform a [Deep Query](https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html) where *serviceID/status_of_service* is equal to Open service. I would suggest NOT using customer names as node names - better to use their uid or perhaps a customer number. See [this](https://stackoverflow.com/questions/45547102/how-to-get-childs-whose-arrays-contain-a-certain-value/45552776#45552776) as well – Jay May 27 '18 at 22:17
  • Could you show me your answer on example code? – Krzysztof Łowiec May 28 '18 at 06:05
  • @KrzysztofŁowiec Is there any possibility to change the db structure? As of current db structure you will have to fetch all customers as you're doing. – TheTiger May 28 '18 at 12:04
  • @TheTiger There's nothing wrong with that structure as long as the returned matches don't exceed the capacity of the device. If that's going to be an issue a separate *count* node could be used if the OP is just interested in a count and not the data itself. e.g. when a child it added or removed from the customers node, increase/decrease a separate count node by 1. – Jay May 28 '18 at 18:06
  • @KrzysztofŁowiec `tom_smith` is a customer OR `tom_smith` can has multiple customers? Could you please expand your example db with multiple keys and values. Then I will give it a try. – TheTiger May 29 '18 at 04:46

1 Answers1

0

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"
Jay
  • 34,438
  • 18
  • 52
  • 81
  • I guess `serviceID` has some value not the text `serviceID`. – TheTiger May 28 '18 at 12:02
  • But is possible to get count of status_of_service equal to "Open service" (in this case 2)? – Krzysztof Łowiec May 28 '18 at 14:12
  • @TheTiger serviceID is a key and the value of that key is another key: value pair of *status_of_service: "Open service"*. serviceID has to be that string in each node in order for a deep query to function. In other words, it has the string *serviceID* so Firebase knows the path to query for *Open service* - if that text were different, it would be a different path possibly in each node, and you couldn't do the deep query. – Jay May 28 '18 at 18:00
  • @KrzysztofŁowiec Sure, just print(snapshot.childrenCount). That will print how how many nodes were retrieved that match the query of *Open service* – Jay May 28 '18 at 18:02
  • This code not work with my database. What if fully database path is not `Customers/Tom Smith/serviceID/Status of service: Open service` but `usersDatabase/g8voYf1yoChnpmhkoPgtmO4FQT62/Customers/Tom Smith/-LDFZw1tca8KOrnqyyWH/Status of service: Open service`? – Krzysztof Łowiec May 28 '18 at 19:53
  • @KrzysztofŁowiec As you can see it's important to include Firebase structures in your initial question - otherwise we are guessing. Can you remove the structure in your question and replace with with a snippet of your actual structure (as text please, no images). My guess it you are not going to be able to do what you need with your current structure but lets take a a look. – Jay May 28 '18 at 20:27
  • @Jay What will you say now with the updated DB structure? I was right about `serviceID`, it has some value not the text `serviceID`. I was also right about the db structure in my yesrterday's comment and now you came up with changing the db structure. – TheTiger May 29 '18 at 13:23
  • @TheTiger I am not sure why you posted that comment or feel you were 'right' about something. In *my* solution, we used a [Deep Query](https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html) to get to data stored within a node. For that to work, the path names must be consistent - which is why in my initial answer, we used the path *serviceID/status_of_service* where serviceID is in fact a key, and that means it would be the literal string of *serviceID*. You should take a look a the documentation I linked as it's a powerful tool to get to child data. – Jay May 29 '18 at 18:27
  • @TheTiger. Take a look at the Old Answer section I left in the answer. The original intention was the keep a node with the key of *serviceID* consistent across all customers - therefore a deep query could be performed on any of it's child nodes; the service status, maybe a location or a timestamp range. The point being that if you want to do a deep query the parent key name cannot be different. We choose the word *serviceID* for this example but it could just as well be *services* or *service_data* or really any string - as long as it was consistent. If you have an alternate answer, post it! – Jay May 29 '18 at 18:55
  • @Jay but what if I put _Status of Service_ value of each customer into tableview (for example: my 4 cell in tableview show "Open service", "During service", "Open service", "Open service") and then get count of cells content (example: 3 ("Open service) and 1("During service)) - is it possible? – Krzysztof Łowiec May 30 '18 at 07:31
  • @KrzysztofŁowiec I dont think I understand the question - you should not use UI elements to get counts - that should be derived from the datasource backing the tableView. Using the structure I proposed in my answer, it should be pretty easy to get counts of any kind of service, open, in process, closed etc either directly from Firebase or from the dataSource. – Jay May 30 '18 at 15:13