1

I have a structure of objects in Firebase looking like this:

-KBP27k4iOTT2m873xSE
    categories
        Geography: true
        Oceania: true
    correctanswer: "Yaren (de facto)"
    languages: "English"
    question: "Nauru"
    questiontype: "Text"
    wronganswer1: "Majuro"
    wronganswer2: "Mata-Utu"
    wronganswer3: "Suva"

I'm trying to find objects by categories, so for instance I want all objects which has the category set to "Oceania".

I'm using Swift and I can't really seem to grasp the concept of how to query the data.

My query right now looks like this:

ref.queryEqualToValue("", childKey: "categories").queryOrderedByChild("Oceania")

Where ref is the reference to Firebase in that specific path.

However whatever I've tried I keep getting ALL data returned instead of the objects with category Oceania only.

My data is structured like this: baseurl/questions/

As you can see in the object example one question can have multiple categories added, so from what I've understood it's best to have a reference to the categories inside your objects.

I could change my structure to baseurl/questions/oceania/uniqueids/, but then I would get multiple entries covering the same data, but with different uniqueid, because the question would be present under both the categories oceania and geography.

By using the structure baseurl/questions/oceania/ and baseurl/questions/geography I could also just add unique ids under oceania and geography that points to a specific unique id inside baseurl/questions/uniqueids instead, but that would mean I'd have to keep track of a lot of references. Making a relations table so to speak.

I wonder if that's the way to go or? Should I restructure my data? The app isn't in production yet, so it's possible to restructure the data completely with no bigger consequences, other than I'd have to rewrite my code, that pushes data to Firebase.

Let me know, if all of this doesn't make sense and sorry for the wall of text :-)

JAL
  • 41,701
  • 23
  • 172
  • 300
Darwind
  • 7,284
  • 3
  • 49
  • 48
  • That's pretty simple with a [Firebase Deep Query](https://www.firebase.com/blog/2015-09-24-atomic-writes-and-more.html) – Jay May 09 '16 at 00:05

2 Answers2

4

Adding some additional code to Tim's answer for future reference.

Just use a deep query. The parent object key is not what is queried so it's 'ignored'. It doesn't matter whether it's a key generated by autoId or a dinosaur name - the query is on the child objects and the parent (key) is returned in snapshot.key.

Based on your Firebase structure, this will retrieve each child nodes where Oceania is true, one at a time:

      let questionsRef = Firebase(url:"https://baseurl/questions")
      questionsRef.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
            .observeEventType(.ChildAdded, withBlock: { snapshot in
               print(snapshot)
        })

Edit: A question came up about loading all of the values at once (.value) instead of one at at time (.childAdded)

      let questionsRef = Firebase(url:"https://baseurl/questions")
      questionsRef.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
              .observeSingleEventOfType(.Value, withBlock: { snapshot in
              print(snapshot)
       })

Results in (my Firebase structure is a little different but you get the idea) uid_1 did not have Oceania = true so it was omitted from the query results.

Snap (users) {
    "uid_0" =     {
        categories =         {
            Oceania = 1;
        };
        email = "dude@thing.com";
        "first_name" = Bill;
    };
    "uid_2" =     {
        categories =         {
            Oceania = 1;
        };
        "first_name" = Peter;
    };
}
Jay
  • 34,438
  • 18
  • 52
  • 81
3

I think this should work:

ref.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
Tim Vermeulen
  • 12,352
  • 9
  • 44
  • 63
  • I'm afraid that doesn't help either. I'm guessing the issue is the key of the object, which is a unique id generated when inserting the objects into Firebase. I've also updated my question with the query that I would think would work, but doesn't. – Darwind May 08 '16 at 22:24
  • @Darwind Tim's answer is spot on and works so it should be accepted. I added some additional code in an answer as well. But this one is correct. – Jay May 09 '16 at 17:57
  • @Jay well it was apparently, but I was using `ref.observeSingleEventOfType(.Value...` and that always returned all elements. I guess there's still a long way to go for me - your addition helped to solve the puzzle :-) – Darwind May 09 '16 at 19:30
  • @Darwind So `.ChildAdded` only gives you the values you want, while `.Value` gives you everything? – Tim Vermeulen May 09 '16 at 19:37
  • @TimVermeulen yes - which I don't get yet, but I guess my knowledge of Firebase is still quite limited... – Darwind May 09 '16 at 19:38
  • I don't get that either. Maybe you could contact the guys at Firebase directly to see if it's a bug. – Tim Vermeulen May 09 '16 at 19:40
  • @Darwind Not a bug. .Value returns everything within the node.. Everything. So it's handy when you have a limited amount of data. .Child added returned each individual child node, one at a time so it's more manageable. – Jay May 09 '16 at 19:52
  • @Jay That's not the point. `.Value` seems to ignore the query, while `.ChildAdded` doesn't. – Tim Vermeulen May 09 '16 at 21:01
  • @TimVermeulen Those are two different things: ref.queryOrderedByChild is very different than ref.observeSingleEventOfType. The comment was that ref.observeSingleEventOfType(.Value... returned all elements, which is correct. The observeEventType vs observeSingleEventOfType can get a little tricky in that when you state singleEvent, that tells Firebase to do this one time. If you use .Value, it will read in everything in the node that fits the query. If you use .ChildAdded, it will read in the first and only the first item that fits the query. – Jay May 09 '16 at 23:10
  • @Jay But `singleEventOfType` shouldn't ignore the query, should it? Because that's what seems to be happening. – Tim Vermeulen May 10 '16 at 06:50
  • @TimVermeulen It doesn't ignore the query. I updated my answer with additional code to both use .childAdded as well as a one-shot read with observeSingleEventOfType (.Value... – Jay May 10 '16 at 17:32
  • @TimVermeulen @Jay - I found out why the `observeSingleEventOfType` always returned everything. It was because I didn't chain the calls. So just calling `ref.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)` and `ref.observeSingleEventOfType(.Value...` right after doesn't take the `queryOrderedByChild` into consideration. – Darwind May 10 '16 at 17:42
  • @Darwind Well, that's correct as it's a different statement so it wouldn't be aware of the query! Mystery solved. See my answer for a good pattern to use in either .ChildAdded or .Value case. – Jay May 10 '16 at 17:49
  • @Jay - yup sorry I didn't realise you were discussing this in the comments :-) As I wrote earlier, I still have a lot to learn about Firebase :-) – Darwind May 10 '16 at 17:50
  • @Jay I really already did understand the difference between the two. :) It just seemed that a bug was occurring in Darwind's code. – Tim Vermeulen May 10 '16 at 19:40