查询多个SQLAlchemy多对多关系

时间:2017-09-14 22:44:20

标签: python sqlalchemy

我使用SQLAlchemy建模以下概念:

  • 用户可以是member_of
  • 一个论坛可以将多个用户设为members,并且可以提供access_to多个Nodes
  • 节点可以是accessed_by多个Groups

我使用关联表模式为User创建多对多关系 - >组和节点 - >基。

代码如下(我使用Flask-SQLAlchemy,这就是为什么我有db.Model等)

如何查询用户所属的所有群组都可以访问的所有节点?

这是对象模型

group_access = Table('access', db.metadata,
                     Column('group_id', Integer, ForeignKey('groups.group_id')),
                     Column('nid', BigInteger, ForeignKey('nodes.nid')),
                     )

group_membership = Table('membership', db.metadata,
                         Column('group_id', Integer, ForeignKey('groups.group_id')),
                         Column('uid', Integer, ForeignKey('users.id')),
                         )

edges = Table('edges', db.metadata,
              Column('src_id', BigInteger, ForeignKey('nodes.nid'), primary_key=True),
              Column('dest', BigInteger, ForeignKey('nodes.nid'), primary_key=True),
              )


class User(db.Model):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    email = Column(String, unique=True, nullable=False)
    member_of = relationship("Group", secondary=group_membership, back_populates='members')

    def __init__(self, email):
        self.email = email


class Node(db.Model):
    __tablename__ = 'nodes'

    nid = Column(BigInteger, primary_key=True)
    type = Column(String)
    parents = relationship("Node",
                           secondary=edges,
                           primaryjoin="Node.nid==edges.c.src_id",
                           secondaryjoin="Node.nid==edges.c.dest",
                           backref="children")
    accessed_by = relationship("Group", secondary=group_access, back_populates='access_to')

    def __init__(self, owner=None, type=None):
        self.type = type
        # self.owner = owner

    def __repr__(self):
        return "<{} nid:{} >".format(self.type, self.nid)


class Group(db.Model):
    __tablename__ = 'groups'

    group_id = Column(Integer, primary_key=True)
    type = Column(String)
    members = relationship("User", secondary=group_membership, back_populates="member_of")
    access_to = relationship("Node", secondary=group_access, back_populates="accessed_by")

    def __init__(self, name):
        self.type = name

    def __repr__(self):
        return self.type

2 个答案:

答案 0 :(得分:5)

可以使用连接形成用于获取与给定用户相关的节点的查询:

db.session.query(Node).\
    join(Node.accessed_by).\
    join(Group.members).\
    filter(User.id == u.id).\
    all()

由于SQLAlchemy处理模型实体的方式,重复行不是问题。您也可以使用EXISTS,它不会产生重复:

db.session.query(Node).\
    filter(Node.accessed_by.any(
        Group.members.any(User.id == u.id))).\
    all()

答案 1 :(得分:1)

我想出了一些有用的东西,但我不确定这是否是最佳方式,因为我不是SQLAlchemy或数据库专家。此外,查询不直接使用关系,我认为可能有更好的方法。

def find_nodes_for_user(u):
    users_groups = db.session.query(group_membership.c.group_id) \
                             .filter(group_membership.c.uid == u.id) \
                             .subquery()
    node_id = db.session.query(group_access.c.nid) \
                        .filter(group_access.c.group_id.in_(users_groups)) \
                        .subquery()
    return db.session.query(Node).filter(Node.nid.in_(node_id)).all()

这会生成以下SQL:

SELECT nodes.nid AS nodes_nid, nodes.type AS nodes_type 
FROM nodes 
WHERE nodes.nid IN (SELECT access.nid 
FROM access 
WHERE access.group_id IN (SELECT membership.group_id 
FROM membership 
WHERE membership.uid = %(uid_1)s))

这是最好的方法吗?