Self-Reference and aliases

It is possible to define tables with fields that refer to themselves, here is an example:

  1. db.define_table('person',
  2. Field('name'),
  3. Field('father_id', 'reference person'),
  4. Field('mother_id', 'reference person'))

Notice that the alternative notation of using a table object as field type will fail in this case, because it uses a table before it is defined:

  1. db.define_table('person',
  2. Field('name'),
  3. Field('father_id', db.person), # wrong!
  4. Field('mother_id', db['person'])) # wrong!

In general db.tablename and 'reference tablename' are equivalent field types, but the latter is the only one allowed for self-references.

When a table has a self-reference and you have to do join, for example to select a person and its father, you need an alias for the table. In SQL an alias is a temporary alternate name you can use to reference a table/column into a query (or other SQL statement).

With web2py you can make an alias for a table using the with_alias method. This works also for expressions, which means also for fields since Field is derived from Expression.

Here is an example:

  1. >>> fid, mid = db.person.bulk_insert([dict(name='Massimo'), dict(name='Claudia')])
  2. >>> db.person.insert(name='Marco', father_id=fid, mother_id=mid)
  3. 3
  4. >>> Father = db.person.with_alias('father')
  5. >>> Mother = db.person.with_alias('mother')
  6. >>> type(Father)
  7. <class 'pydal.objects.Table'>
  8. >>> str(Father)
  9. 'person AS father'
  10. >>> rows = db().select(db.person.name, Father.name, Mother.name,
  11. ... left=(Father.on(Father.id == db.person.father_id),
  12. ... Mother.on(Mother.id == db.person.mother_id)))
  13. >>> for row in rows:
  14. ... print row.person.name, row.father.name, row.mother.name
  15. ...
  16. Massimo None None
  17. Claudia None None
  18. Marco Massimo Claudia

Notice that we have chosen to make a distinction between:

  • “father_id”: the field name used in the table “person”;
  • “father”: the alias we want to use for the table referenced by the above field; this is communicated to the database;
  • “Father”: the variable used by web2py to refer to that alias.

The difference is subtle, and there is nothing wrong in using the same name for the three of them:

  1. >>> db.define_table('person',
  2. ... Field('name'),
  3. ... Field('father', 'reference person'),
  4. ... Field('mother', 'reference person'))
  5. <Table person (id, name, father, mother)>
  6. >>> fid, mid = db.person.bulk_insert([dict(name='Massimo'), dict(name='Claudia')])
  7. >>> db.person.insert(name='Marco', father=fid, mother=mid)
  8. 3
  9. >>> father = db.person.with_alias('father')
  10. >>> mother = db.person.with_alias('mother')
  11. >>> rows = db().select(db.person.name, father.name, mother.name,
  12. ... left=(father.on(father.id==db.person.father),
  13. ... mother.on(mother.id==db.person.mother)))
  14. >>> for row in rows:
  15. ... print row.person.name, row.father.name, row.mother.name
  16. ...
  17. Massimo None None
  18. Claudia None None
  19. Marco Massimo Claudia

But it is important to have the distinction clear in order to build correct queries.