2011-12-18 15 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') 
      ) 

2つの可能な関係があります:片側および両側です。それは私が持つことができるクロステーブルにあります

CharacterRelationship(me_id=1, other_id=2) 
CharacterRelationship(me_id=2, other_id=1) 

上記のCharacter.friends引数は、単方向関係に関するものです。二国間関係のためにどのようにプロパティ/ 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 

  • を-1私が一方的に他のを知っている意味、
  • 0二国間、
  • 1は、他が知っていること私は一方的に

このロジックをデータベースレベルに設定する理由はありますか。はいの場合、どうしますか? もしあなたが知っていれば、単純な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(これは非公式の友人であると仮定しています)の場合、私は同様のクエリを実行しますが、外部結合を使用します。

relationship_typeについては

knows_personをキャッシュながら、私は(次の手順を実行します):

@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() 

が、これは行います:

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

friendsが定義され、あなたの関係を有する、real_friendsは簡単に次のように行われています2つ以上の冗長JOINUserのテーブルなので、IMOでも良いコードですが、単純な場合は、JOINになっているジョナサンのバージョンより劣る可能性があります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 - なし接続され、1 - 友人、2 - 、私を知っている3 - 本当の友人(両方の方法)
関連する問題