SQLAlchemy:三元组中的定向关系
SQLAlchemy: directed relationships within triads
给定用户和他们之间的定向关系:
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 来 select 三合会用户,例如:
A follows B
B follows A
A follows C
C follows B
首先我不知道如何使用 exists()
到 select 双向关系。
我认为在所需的关系 (table Following
) 上使用多个内部 JOIN
可以以一种相当直接的方式实现此目的:
# 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
可以跟在 him/herself 之后,这可能不会那么稳健,在这种情况下,可以使用额外的 where
子句排除此类情况。
Option-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)
给定用户和他们之间的定向关系:
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 来 select 三合会用户,例如:
A follows B
B follows A
A follows C
C follows B
首先我不知道如何使用 exists()
到 select 双向关系。
我认为在所需的关系 (table Following
) 上使用多个内部 JOIN
可以以一种相当直接的方式实现此目的:
# 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
可以跟在 him/herself 之后,这可能不会那么稳健,在这种情况下,可以使用额外的 where
子句排除此类情况。
Option-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)