I’m trying to create a social-network like many-to-many mapping in SQLAlchemy. That is I have a Character class and a character_relations cross table to link from Character to Character. Until now this is easy:
character_relationships = Table('character_relationships', Base.metadata,
Column('me_id', Integer, ForeignKey('characters.id', ondelete=True, onupdate=True), nullable=False, primary_key=True),
Column('other_id', Integer, ForeignKey('characters.id', ondelete=True, onupdate=True), nullable=False, primary_key=True),
UniqueConstraint('me_id', 'other_id', name='uix_1')
)
class CharacterRelationship(object):
def __init__(self, me_id, other_id):
self.me_id = me_id
self.other_id = other_id
mapper(CharacterRelationship, character_relationships)
class Character(IdMixin, TimestampMixin, Base):
__tablename__ = "characters"
name = Column(Unicode, nullable=False)
friends = relationship(lambda: Character, secondary=character_relationships,
primaryjoin=lambda: Character.id==character_relationships.c.me_id,
secondaryjoin=lambda: Character.id==character_relationships.c.other_id,
backref=backref('knows_me')
)
There are two possible relationships: unilateral and bilateral. That is in the cross table I can have
CharacterRelationship(me_id=1, other_id=2)
CharacterRelationship(me_id=2, other_id=1)
The Character.friends argument as given above is about unilater relationships. How can one add a property/column_property for bilateral relationships?
That is my_character.real_friends contains only entries from CharacterRelationship if the relationship “stands from both sides”.
I know that I could use something like
@property
def real_friends(self):
return set(my_character.friends) & set(my_character.knows_me)
but this doesn’t translate well to sql, and I would expect a huge speedup doing this set intersection at the database level. But there is even more to achieve!
Even better would be to have a final object like:
character.friends.other_character
character.friends.relationship_type = -1 | 0 | 1
where
- -1 means I know other unilaterally,
- 0 bilateral,
- 1 means other knows me unilaterally
Do you see any reason to put this logic at the database level? If yes, how would you do it?
If know, do you know how to put the simple real_friend bilateral part at the database level?
For real_friends, you would want to query this at the database level. It should look something like this:
You can of course set this as a relationship.
For other_character (I’m assuming these are non-mutual friends), I would do a similar query but with an outerjoin.
For relationship_type, I would do the following while caching knows_person():