40

Given the data structure below in firebase, i want to run a query to retrieve the blog 'efg'. I don't know the user id at this point.

{Users :
     "1234567": {
          name: 'Bob',
          blogs: {
               'abc':{..},
               'zyx':{..}
          }
     },
     "7654321": {
          name: 'Frank',
          blogs: {
               'efg':{..},
               'hij':{..}
          }
     }
}
koceeng
  • 2,169
  • 3
  • 16
  • 37
Chris Raheb
  • 422
  • 1
  • 4
  • 8
  • See also http://stackoverflow.com/questions/24869180/query-hierarchical-data-structure-in-firebase – Kato Feb 09 '15 at 19:21
  • 3
    There's really no reason to have blogs as a part of the users' records. They aren't a logical "attribute" of a user, they are a separate entity with their own data structures, purpose, and read/write scenarios. I'd start by splitting those (as Frank mentioned) and making things simple and direct. – Kato Feb 09 '15 at 19:23

3 Answers3

38

The Firebase API only allows you to filter children one level deep (or with a known path) with its orderByChild and equalTo methods.

So without modifying/expanding your current data structure that just leaves the option to retrieve all data and filter it client-side:

var ref = firebase.database().ref('Users');
ref.once('value', function(snapshot) {
    snapshot.forEach(function(userSnapshot) {
        var blogs = userSnapshot.val().blogs;
        var daBlog = blogs['efg'];
    });
});

This is of course highly inefficient and won't scale when you have a non-trivial number of users/blogs.

So the common solution to that is to a so-called index to your tree that maps the key that you are looking for to the path where it resides:

{Blogs:
     "abc": "1234567",
     "zyx": "1234567",
     "efg": "7654321",
     "hij": "7654321"
}

Then you can quickly access the blog using:

var ref = firebase.database().ref();
ref.child('Blogs/efg').once('value', function(snapshot) {
    var user = snapshot.val();
    ref.child('Blogs/'+user+'/blogs').once('value', function(blogSnapshot) {
        var daBlog = blogSnapshot.val();
    });
});

You might also want to reconsider if you can restructure your data to better fit your use-case and Firebase's limitations. They have some good documentation on structuring your data, but the most important one for people new to NoSQL/hierarchical databases seems to be "avoid building nests".

Also see my answer on Firebase query if child of child contains a value for a good example. I'd also recommend reading about many-to-many relationships in Firebase, and this article on general NoSQL data modeling.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 18
    Note that [deep queries](https://www.firebase.com/blog/2015-09-24-atomic-writes-and-more.html) are now a feature. – Kato Oct 09 '15 at 21:25
  • 5
    I've thought about it for a few minutes, but give up: how would a deep query solve this problem? – Frank van Puffelen Oct 10 '15 at 05:31
  • 4
    I don't think a deep query solves the problem; but the post states that you can only query one-level deep, which is no longer true. – Kato Oct 29 '15 at 19:57
  • 3
    @FrankvanPuffelen, I've been reading a lot of your answers lately (you seem to be the only one who knows anything about firebase [seeing as how you're a firebase engineer]) and I'm wondering if you final solution in your answer is a best practice, or should we always try to make the information structured so that we may make only a single call? – ntgCleaner Jul 24 '17 at 03:55
2

Given your current data structure you can retrieve the User that contains the blog post you are looking for.

const db = firebase.database()
const usersRef = db.ref('users')
const query = usersRef.orderByChild('blogs/efg').limitToLast(1)
query.once('value').then((ss) => {
  console.log(ss.val()) //=> { '7654321': { blogs: {...}}}
})

You need to use limitToLast since Objects are sorted last when using orderByChild docs.

Bryan Massoth
  • 1,109
  • 6
  • 7
1

It's actually super easy - just use foreslash:

db.ref('Users').child("userid/name")
db.ref('Users').child("userid/blogs")
db.ref('Users').child("userid/blogs/abc")

No need of loops or anything more.

nyagolova
  • 1,731
  • 2
  • 26
  • 42