couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Apache Wiki <wikidi...@apache.org>
Subject [Couchdb Wiki] Update of "EntityRelationship" by WoutMertens
Date Mon, 13 Apr 2009 18:38:22 GMT
Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Couchdb Wiki" for change notification.

The following page has been changed by WoutMertens:
http://wiki.apache.org/couchdb/EntityRelationship

The comment on the change is:
Done I think :-)

------------------------------------------------------------------------------
   1. Use separate documents
   2. Use an embedded array
  
- == Separate One to Many ==
+ === One to Many: Separate documents ===
  
  When using separate documents, you could have documents like this for the phone numbers:
  {{{
  {
    "_id":"the phone number",
    "type":"phone",
-   "contact":"id of the contact document that has this phone number",
+   "contact_id":"id of the contact document that has this phone number",
    "phone_type":"string describing type of phone, like home,work,fax,mobile,..."
  }
  }}}
@@ -56, +56 @@

  Creating the relationship between a contact and one of its phone numbers is easy to do.
Let's say you have a contact named "Scott" who has a home phone and a mobile phone. You populate
his contact info like this (using Perl and Net::CouchDB):
  {{{
  $db->insert({type => 'contact', _id => 'Scott', name => 'My Friend Scott'});
- $db->insert({type => 'phone', _id => '(650) 555 - 2200', contact => 'Scott',
phone_type => 'home'});
+ $db->insert({type => 'phone', _id => '(650) 555 - 2200', contact_id => 'Scott',
phone_type => 'home'});
- $db->insert({type => 'phone', _id => '(650) 555 - 2201', contact => 'Scott',
phone_type => 'mobile'});
+ $db->insert({type => 'phone', _id => '(650) 555 - 2201', contact_id => 'Scott',
phone_type => 'mobile'});
  }}}
  
  To get the contacts and their phone numbers from CouchDB in one search, you need to use
a little trick: You need to create a view that sorts the contacts and their phone numbers
in order. This is the view:
@@ -67, +67 @@

     if (doc.type == 'contact') {
        emit([doc._id, 0], doc);
     } else if (doc.type == 'phone') {
-       emit([doc._id, 1], doc);
+       emit([doc.contact_id, 1, doc.phone_type], doc);
     }
  }
  }}}
  
- If you then query this view with the ''startkey'' parameter set to "[''''''"Scott"]" and
endkey "[''''''"Scott",{}]"
+ If you then query this view with the ''startkey'' parameter set to "[''''''"Scott"]" and
endkey "[''''''"Scott",{}]", you'll get the contact details in the first row and the phone
numbers in the following rows (sorted by phone_type as well). You can easily extend this system
to have other types of one-to-many attributes in the same view by giving them a different
number in the view above.
  
- = this is how far I got, work in progress =
+ NOTE: This needs a code example showing how to use the output of the view. Feel free to
add one.
  
+ Because CouchDB always sorts on keys, you can use this view to only get Scotts home phone
numbers by querying with ''startkey'' set to "[''''''"Scott",1,"home"]" and ''endkey'' set
to "[''''''"Scott",1,"home",{}]"
+ 
- Because ReferenceProperty creates this special property on Contact, it makes it very easy
to retrieve all the phone numbers for a given person. If you wanted to print all the phone
numbers for a given person, you can do it like this:
- print 'Content-Type: text/html'
- print
- for phone in scott.phone_numbers: 
-   print '%s: %s' % (phone.phone_type, phone.number)
- This will produce results that look like:
- home: (650) 555 - 2200
- mobile: (650) 555 - 2201
- Note: The order of the output might be different as by default there is no ordering in this
kind of relationship.
- The phone_numbers virtual attribute is a Query instance, meaning that you can use it to
further narrow down and sort the collection associated with the Contact. For example, if you
only want to get the home phone numbers, you can do this:
- scott.phone_numbers.filter('phone_type =', 'home')
  When Scott loses his phone, it's easy enough to delete that record. Just delete the PhoneNumber
instance and it can no longer be queried for:
- scott.phone_numbers.filter('phone_type =', 'home').get().delete()
+ {{{
+ $db->doc('(650) 555 - 2200')->delete;
+ }}}
  
- == Embedded One to Many ==
+ === One to Many: Embedded Documents ===
  
- The embedded array is only an option as long as you don't have "too many" items to store,
since each document is always handled as a whole and bigger documents mean slower handling
and network transfers. Phone numbers should be ok unless you plan to store the whole company
phonebook in there.
+ The embedded array is only an option as long as you don't have "too many" items to store,
since each document is always handled as a whole and bigger documents mean slower handling
and slower network transfers. Phone numbers should be ok unless you plan to store the whole
company phonebook in there.
  
+ This is the easiest way to handle one-to-many as everything you need is in one place. Here's
how the document for Scott would look:
+ {{{
+ {
+   "_id":"Scott",
+   "type":"contact",
+   "name":"My Friend Scott",
+   "phones":[{"number":"(650) 555 - 2200","type":"home"},{"number":"(650) 555 - 2201","type":"mobile"}],
+ }
+ }}}
+ Note how only the fields that we know are stored. Also note that the phone numbers are not
simply an array, they are an array of associative hashes. We could extend this with no effort
to add email addresses, IM names etc, even if IM names would need an extra attribute that
has the service type. In essence, you're embedding child documents in the master document.
That is the power of schema-less databases.
+ 
+ == Many to Many ==
+ One thing you would like to do is provide the ability for people to organize their contacts
in to groups. They might make groups like "Friends", "Co-workers" and "Family". This would
allow users to use these groups to perform actions en masse, such as maybe sending an invitation
to all their friends for a hack-a-thon. Let's define a simple Group model like this:
+ {{{
+ {
+   "_id":"unique group id",
+   "type":"group",
+   "name":"Elaborate group name",
+   "description":"description"
+ }
+ }}}
+ 
+ You could make a one-to-many relation with Contact. However, this would allow contacts to
be part of only one group at a time. For example, someone might include some of their co-workers
as friends. You need a way to represent many-to-many relationships.
+ 
+ === Many to Many: List of Keys ===
+ One very simple way is to create a list of keys on one side of the relationship, like we
did in the "Embedded One to Many" section.
+ 
+ Our friend and colleague Scott would then get a new field in his contact document which
holds group ''_id'' values:
+ {{{
+   "groups":["Friends","Colleagues"]
+ }}}
+ 
+ Adding and removing a user to and from a group means working with a list of keys. Suppose
we don't like Scott any more:
+ {{{
+    my $scott = $db->doc('Scott');
+    $scott->{groups} = grep { $_ ne 'Friends' } $scott->{groups};
+    $scott->update;
+ }}}
+ 
+ To get all the members of a group, you'd create a view like this:
+ {{{
+ "map":function(doc) {
+    if (doc.type == 'contact') {
+       for (var i in doc.groups) {
+          emit(i,doc.name);
+       }
+    } else if (doc.type == 'group') {
+       emit(doc._id,doc);
+    }
+ }
+ }}}
+ 
+ If you then query this view with search parameters
+  * ''descending=true''
+  * ''key="Friends"''
+ then you'll get all the names of members of the group Friends and the group information
as the first row. (Hashes sort behind strings).
+ 
+ Here's a space optimization hint: If you make the view be
+ {{{
+ "map":function(doc) {
+    if (doc.type == 'contact') {
+       for (var i in doc.groups) {
+          emit(i,null);
+       }
+    } else if (doc.type == 'group') {
+       emit(doc._id,null);
+    }
+ }
+ }}}
+ and query this view with search parameters
+  * ''key="Friends"''
+  * ''include_docs=true''
+ You'll get all documents that are pertinent to the group, but in no particular order. The
size of your index will be smaller though.
+ 
+ In general though, you want to avoid storing overly large lists of any kind in a single
document. This means you should place the list on side of the relationship which you expect
to have fewer values. In the example above, the Contact side was chosen because a single person
is not likely to belong to too many groups, whereas in a large contacts database, a group
might contain hundreds of members.
+ 
+ === Many to Many: Relationship documents ===
+ 
+ Another way of implementing many-to-many is by creating a separate document for each relationship.
A document explaining that Scott is a Friend would look like
+ {{{
+ {
+   "_id":"some unique id",
+   "type":"relation",
+   "contact_id":"Scott",
+   "group_id":"Friends"
+ }
+ }}}
+ 
+ You would use this method when there is potentially a large number of contacts in a group
'''and''' a large number of groups. In that case embedding a list of keys could result in
huge documents.
+ 
+ If you then want to know who is in a group you'll need to use the view
+ {{{
+ "map":function(doc) {
+    if (doc.type == 'relationship') {
+       emit(doc.group_id,doc.contact_id);
+    }
+ }
+ }}}
+ 
+ To know what groups a contact belongs to you need to use
+ {{{
+ "map":function(doc) {
+    if (doc.type == 'relationship') {
+       emit(doc.contact_id,doc.group_id);
+    }
+ }
+ }}}
+ 
+ As you can see, to retrieve further information about the group or the contact, you'll need
to send another query to CouchDB.
+ 

Mime
View raw message