Many to many

In the previous examples, we allowed a thing to have one owner but one person could have many things. What if Boat was owned by Alex and Curt? This requires a many-to-many relation, and it is realized via an intermediate table that links a person to a thing via an ownership relation.

Here is how to do it:

  1. >>> db.define_table('person',
  2. ... Field('name'))
  3. <Table person (id, name)>
  4. >>> db.person.bulk_insert([dict(name='Alex'), dict(name='Bob'), dict(name='Carl')])
  5. [1, 2, 3]
  6. >>> db.define_table('thing',
  7. ... Field('name'))
  8. <Table thing (id, name)>
  9. >>> db.thing.bulk_insert([dict(name='Boat'), dict(name='Chair'), dict(name='Shoes')])
  10. [1, 2, 3]
  11. >>> db.define_table('ownership',
  12. ... Field('person', 'reference person'),
  13. ... Field('thing', 'reference thing'))
  14. <Table ownership (id, person, thing)>

the existing ownership relationship can now be rewritten as:

  1. >>> db.ownership.insert(person=1, thing=1) # Alex owns Boat
  2. 1
  3. >>> db.ownership.insert(person=1, thing=2) # Alex owns Chair
  4. 2
  5. >>> db.ownership.insert(person=2, thing=3) # Bob owns Shoes
  6. 3

Now you can add the new relation that Curt co-owns Boat:

  1. >>> db.ownership.insert(person=3, thing=1) # Curt owns Boat too
  2. 4

Because you now have a three-way relation between tables, it may be convenient to define a new set on which to perform operations:

  1. >>> persons_and_things = db((db.person.id == db.ownership.person) &
  2. ... (db.thing.id == db.ownership.thing))

Now it is easy to select all persons and their things from the new Set:

  1. >>> for row in persons_and_things.select():
  2. ... print row.person.name, 'has', row.thing.name
  3. ...
  4. Alex has Boat
  5. Alex has Chair
  6. Bob has Shoes
  7. Curt has Boat

Similarly, you can search for all things owned by Alex:

  1. >>> for row in persons_and_things(db.person.name == 'Alex').select():
  2. ... print row.thing.name
  3. ...
  4. Boat
  5. Chair

and all owners of Boat:

  1. >>> for row in persons_and_things(db.thing.name == 'Boat').select():
  2. ... print row.person.name
  3. ...
  4. Alex
  5. Curt

A lighter alternative to many-to-many relations is tagging, you can found an example of this in the next section. Tagging is also discussed in the context of the IS_IN_DB and IS_IN_SET validators on chapter 7. Tagging works even on database backends that do not support JOINs like the Google App Engine NoSQL.