AngularFire - How do I query denormalised data?

2019-02-05 06:45发布

问题:

Ok Im starting out fresh with Firebase. I've read this: https://www.firebase.com/docs/data-structure.html and I've read this: https://www.firebase.com/blog/2013-04-12-denormalizing-is-normal.html

So I'm suitably confused as one seems to contradict the other. You can structure your data hierarchically, but if you want it to be scalable then don't. However that's not the actual problem.

I have the following structure (please correct me if this is wrong) for a blog engine:

"authors" : {
  "-JHvwkE8jHuhevZYrj3O" : {
    "userUid" : "simplelogin:7",
    "email"   : "myemail@domain.com"
  }
},
"posts" : {
  "-JHvwkJ3ZOZAnTenIQFy" : {
    "state" : "draft",
    "body" : "This is my first post",
    "title" : "My first blog",
    "authorId" : "-JHvwkE8jHuhevZYrj3O"
  }
}

A list of authors and a list of posts. First of all I want to get the Author where the userUid equals my current user's uid. Then I want to get the posts where the authorId is the one provided to the query.

But I have no idea how to do this. Any help would be appreciated! I'm using AngularFire if that makes a difference.

回答1:

Firebase is a NoSQL data store. It's a JSON hierarchy and does not have SQL queries in the traditional sense (these aren't really compatible with lightning-fast real-time ops; they tend to be slow and expensive). There are plans for some map reduce style functionality (merged views and tools to assist with this) but your primary weapon at present is proper data structure.

First of all, let's tackle the tree hierarchy vs denormalized data. Here's a few things you should denormalize:

  • lists you want to be able to iterate quickly (a list of user names without having to download every message that user ever wrote or all the other meta info about a user)
  • large data sets that you view portions of, such as a list of rooms/groups a user belongs to (you should be able to fetch the list of rooms for a given user without downloading all groups/rooms in the system, so put the index one place, the master room data somewhere else)
  • anything with more than 1,000 records (keep it lean for speed)
  • children under a path that contain 1..n (i.e. possibly infinite) records (example chat messages from the chat room meta data, that way you can fetch info about the chat room without grabbing all messages)

Here's a few things it may not make sense to denormalize:

  • data you always fetch en toto and never iterate (if you always use .child(...).on('value', ...) to fetch some record and you display everything in that record, never referring to the parent list, there's no reason to optimize for iterability)
  • lists shorter than a hundred or so records that you always as a whole (e.g. the list of groups a user belongs to might always be fetched with that user and would average 5-10 items; probably no reason to keep it split apart)

Fetching the author is as simple as just adding the id to the URL:

var userId = 123;
new Firebase('https://INSTANCE.firebaseio.com/users/'+userId);

To fetch a list of posts belonging to a certain user, either maintain an index of that users' posts:

/posts/$post_id/...
/my_posts/$user_id/$post_id/true

var fb = new Firebase('https://INSTANCE.firebaseio.com');
fb.child('/my_posts/'+userId).on('child_added', function(indexSnap) {
   fb.child('posts/'+indexSnap.name()).once('value', function(dataSnap) {
       console.log('fetched post', indexSnap.name(), dataSnap.val());
   });
});

A tool like Firebase.util can assist with normalizing data that has been split for storage until Firebase's views and advanced querying utils are released:

/posts/$post_id/...
/my_posts/$user_id/$post_id/true

var fb = new Firebase('https://INSTANCE.firebaseio.com');
var ref = Firebase.util.intersection( fb.child('my_posts/'+userId), fb.child('posts') );
ref.on('child_added', function(snap) {
   console.log('fetched post', snap.name(), snap.val();
});

Or simply store the posts by user id (depending on your use case for how that data is fetched later):

/posts/$user_id/$post_id/...

new Firebase('https://INSTANCE.firebaseio.com/posts/'+userId).on('child_added', function(snap) {
   console.log('fetched post', snap.name(), snap.val());
});