SQLAlchemy:三合会内的定向关系

时间:2015-01-11 12:44:42

标签: python sql sqlalchemy

鉴于用户和他们之间的直接关系:

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    username = Column(String(30), nullable=False)
    follows = relationship(
        'User', secondary='following',
        primaryjoin=(Following.follower_id == id),
        secondaryjoin=(Following.followee_id == id)
    )
    followed_by = relationship(
        'User', secondary='following',
        primaryjoin=(Following.followee_id == id),
        secondaryjoin=(Following.follower_id == id)
    )

class Following(Base):
    __tablename__ = 'following'
    follower_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
    followee_id = Column(Integer, ForeignKey('user.id'), primary_key=True)

我需要使用SQLAlchemy来选择三位一体的用户,例如:

A follows B
B follows A
A follows C
C follows B  

我无法弄清楚如何使用exists()来选择双向关系。

1 个答案:

答案 0 :(得分:0)

我认为在所需的关系(表JOIN)上使用多个内部Following,您可以以一种相当直接的方式实现这一点:

# create aliases for each pair of relations that must exist
AB = aliased(Following)
BA = aliased(Following)
AC = aliased(Following)
CB = aliased(Following)
CA = aliased(Following)

triads = (
    session.query(
        AB.follower_id.label("a_id"),
        AB.followee_id.label("b_id"),
        AC.followee_id.label("c_id"),
    )
    # .select_from(AB)  # @note: this is implied, so not really required
    .join(BA, and_(AB.followee_id == BA.follower_id,
                   AB.follower_id == BA.followee_id))
    .join(AC, AB.follower_id == AC.follower_id)
    .join(CB, and_(AC.followee_id == CB.follower_id,
                   AB.followee_id == CB.followee_id))
    # exclude C following A using LEFT JOIN + WHERE IS NULL)
    .outerjoin(CA, and_(AC.followee_id == CA.follower_id,
                        AC.follower_id == CA.followee_id))
    .filter(CA.follower_id == None)
)

for a, b, c in triads:
    print(a, b, c)

如果User可以跟随他/她自己,这可能不会那么强大,在这种情况下,可以用额外的where条款排除这种情况。

选项-2:另一种干净(非常易读)的方法是确保存在实际关系,但下面代码生成的SQL语句可能是一种过度杀伤力达到预期的效果:

# create aliases for each assumed user (A, B, C)
A = aliased(User)
B = aliased(User)
C = aliased(User)
# also aliases to navigate relationship and check same user
Aa = aliased(User)
Bb = aliased(User)

triads = (
    session.query(A, B, C)
    .join(B, A.follows)  # A follows B
    .join(Aa, B.follows).filter(Aa.id == A.id)  # B follows Aa (same as A)
    .join(C, A.follows)  # A follows C
    .join(Bb, C.follows).filter(Bb.id == B.id)  # C follows Bb (same as B)
    .filter(~C.follows.any(User.id == A.id))  # exclude C following A
)

for a, b, c in triads:
    print(a, b, c)