2011-12-18 4 views
4

SQLAlchemy에서 다 대다 매핑과 같은 소셜 네트워크를 만들려고한다. 그것은 Character 클래스와 Character_relations 크로스 테이블을 가지고있어서 Character에서 Character로 연결됩니다. 지금까지는 쉽습니다.SQLAlchemy : 나와 함께한다면, 나도 너와 함께있어. 자기 참조 M2M 관계.

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 인수는 일원 관계에 관한 것입니다. 양국 관계에 대한 속성/열 속성을 어떻게 추가 할 수 있습니까?

"my_character.real_friends는 관계가"양쪽에서 나옵니다 "인 경우 CharacterRelationship의 항목 만 포함합니다.

은 내가

@property 
def real_friends(self): 
    return set(my_character.friends) & set(my_character.knows_me) 

같은 것을 사용할 수 있지만,이 SQL에 잘 번역하지 않고, 나는 데이터베이스 수준에서이 교집합을하고 엄청난 속도 향상을 기대하는 것을 알고있다. 그러나 달성 할 것이 더 있습니다!

  • 1 알고 다른 의미,

    • -1 의미

      character.friends.other_character 
      character.friends.relationship_type = -1 | 0 | 1 
      

      내가 다른 일방적으로 알고

    • 0 양자 : 더 나은

      같은 최종 목적을 가지고하는 것입니다 나 일방적으로

    이 논리를 데이터베이스 수준에 적용 할 이유가 있습니까? 그렇다면 어떻게 할 것입니까? 알다시피, 간단한 real_friend 양측 부분을 데이터베이스 수준에 두는 방법을 알고 있습니까?

  • 답변

    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 (나는 이들이 비 상호 (non-mutual) 친구라고 가정하고 있음)의 경우 비슷한 질의를하지만 outerjoin을 사용합니다. 관계 friends 정의 데

    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 
    
    +0

    잊지 마세요 이 (자기'사용 object_session하기)' – Eric

    2

    real_friends 쉽게 다음과 같이 이루어집니다 : knows_person()를 캐싱하는 동안

    는 relationship_type, 나는 다음과 같은 작업을 수행 할

    @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가 더 나은 코드이지만 Jonathan의 버전보다 열등 할 수도 있습니다. JOIN to th e 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() 
    

    몇 가지 참고 :이 다시 다음과 같은 쿼리를 수행 할 수 있습니다 (참고로 조나단의 대답 @ 촬영)

    • filterjoin 조건으로 이동합니다. 외부 조인에서는 매우 중요합니다. 그렇지 않으면 결과가 잘못됩니다.
    • 결과는 _code_[(User, _code_), ..] 될 것입니다 : 0 - 아니 연결, 한 - 친구 2 - 나를 알고, 3 - 진짜 친구 (두 가지)