【问题标题】:Querying across multiple SQLAlchemy many-to-many relationships跨多个 SQLAlchemy 多对多关系查询
【发布时间】:2018-02-24 00:52:27
【问题描述】:

我正在使用 SQLAlchemy 对以下概念进行建模:

  • 一个用户可以是一个member_of一个组
  • 一个群组可以有多个用户members,并且可以提供access_to多个Nodes
  • 一个节点可以是accessed_by多个Groups

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

代码如下(我使用的是 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

【问题讨论】:

  • 您想如何查询节点?作为单独的查询,或者您想在获取用户时预先加载它们?
  • 假设作为一个单独的查询,假设给定用户可以访问的节点本身可能没有父子关系。因此,数据将是一棵树,用户可以在其中访问不同的节点。

标签: python sqlalchemy


【解决方案1】:

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

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

【讨论】:

  • 哇,谢谢!我喜欢这两个比我想出的要好得多。每个人都在做我一直坚持的事情——遍历关系。
  • 还有一种更短的方式来编写这样的“遍历”,但一开始可能会让人感到困惑。在单个实体查询中,join() 还接受字符串作为要连接的关系属性的名称,因此 2 个单独的连接可以写为 join("accessed_by", "members")
  • Ilja,你是不断给予的礼物! :-)
【解决方案2】:

我想出了一些可行的方法,但我不确定这是否是最好的方法,因为我不是 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))

这是最好的方法吗?

【讨论】:

  • 完全没问题,虽然你可以用不那么冗长的连接来表达相同的意思,或者使用 EXISTS。
  • 谢谢!你能举个例子说明你的想法吗?
猜你喜欢
  • 2019-01-18
  • 2011-09-26
  • 1970-01-01
  • 2011-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-17
  • 1970-01-01
相关资源
最近更新 更多