我正在尝试在SQLAlchemy中创建像多对多映射这样的社交网络。那就是我有一个Character类和一个character_relations交叉表来链接从Character到Character。到现在为止这很简单:
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')
)
有两种可能的关系:单边和双边。那是我可以拥有的交叉表
CharacterRelationship(me_id=1, other_id=2)
CharacterRelationship(me_id=2, other_id=1)
上面给出的Character.friends参数是关于单一的关系。如何为双边关系添加property / column_property?
这是my_character.real_friends只包含来自CharacterRelationship的条目,如果关系“从两边站立”。
我知道我可以使用像
这样的东西@property
def real_friends(self):
return set(my_character.friends) & set(my_character.knows_me)
但是这并不能很好地转换为sql,我希望在数据库级别进行这种集合交叉的巨大加速。但还有更多要实现的目标!
更好的方法是拥有一个像:
这样的最终对象character.friends.other_character
character.friends.relationship_type = -1 | 0 | 1
其中
您认为有任何理由将此逻辑放在数据库级别吗?如果是的话,你会怎么做? 如果知道,您知道如何将简单的real_friend双边部分放在数据库级别吗?
答案 0 :(得分:2)
对于real_friends,您可能希望在数据库级别进行查询。看起来应该是这样的:
@property
def real_friends(self):
char_rels1 = aliased(CharacterRelationship)
char_rels2 = aliased(CharacterRelationship)
return DBSession.query(Character).\
join(char_rels1, char_rels1.other_id == Character.id).\
filter(char_rels1.me_id == self.id).\
join(char_rels2, char_rels2.me_id == Character.id).\
filter(char_rels2.other_id == self.id).all()
您当然可以将此设置为关系。
对于other_character(我假设这些是非共同的朋友),我会做一个类似的查询,但有一个outerjoin。
对于relationship_type,我会在缓存knows_person()时执行以下操作:
def knows_person(self, person):
return bool(DBSession.query(CharacterRelationship).\
filter(CharacterRelationship.me_id == self.id).\
filter(CharacterRelationship.other_id == person.id).\
first())
def rel_type(self, person):
knows = self.knows_person(person)
known = person.knows_person(self)
if knows and known:
return 0
elif knows:
return -1
elif known:
return 1
return None
答案 1 :(得分:2)
定义了您的关系friends
,real_friends
很容易完成,如下所示:
@property
def real_friends(self):
qry = (Session.object_session(self).query(User).
join(User.friends, aliased=True).filter(User.id == self.id).
join(User.knows_me, aliased=True).filter(User.id == self.id)
)
return qry.all()
,但这会使两个JOIN
到User
表更加冗余,所以虽然IMO是一个更好的代码,但它可能不如简单{{1}的Jonathan版本使用了JOIN
表。
至于你要求的更好的选项:这可以在下面的查询中再次完成(以@ Jonathan的答案作为参考):
CharacterRelationship
很少有事情需要注意:
@property
def all_friends(self):
char_rels1 = aliased(Friendship)
char_rels2 = aliased(Friendship)
qry = (Session.object_session(self).query(User, case([(char_rels1.id <> None, 1)], else_=0)+case([(char_rels2.id <> None, 2)], else_=0)).
outerjoin(char_rels1, and_(char_rels1.friends == User.id, char_rels1.knows_me == self.id)).
outerjoin(char_rels2, and_(char_rels2.knows_me == User.id, char_rels2.friends == self.id)).
filter(or_(char_rels1.id <> None, char_rels2.id <> None)) # @note: exclude those that are not connected at all
)
return qry.all()
移至filter
条件 - 这对外连接非常重要,否则结果会出错。join
,其中[(User, _code_), ..]
是:0 - 没有连接,1 - 朋友,2 - 认识我,3 - 真正的朋友(双向)