【问题标题】:sqlalchemy: how to join several tables by one query?sqlalchemy:如何通过一个查询连接多个表?
【发布时间】:2011-08-28 00:13:04
【问题描述】:

我有以下 SQLAlchemy 映射类:

class User(Base):
    __tablename__ = 'users'
    email = Column(String, primary_key=True)
    name = Column(String)

class Document(Base):
    __tablename__ = "documents"
    name = Column(String, primary_key=True)
    author = Column(String, ForeignKey("users.email"))

class DocumentsPermissions(Base):
    __tablename__ = "documents_permissions"
    readAllowed = Column(Boolean)
    writeAllowed = Column(Boolean)

    document = Column(String, ForeignKey("documents.name"))

我需要为user.email = "user@email.com"弄一张这样的桌子:

email | name | document_name | document_readAllowed | document_writeAllowed

如何使用 SQLAlchemy 的一个查询请求来实现它?下面的代码对我不起作用:

result = session.query(User, Document, DocumentPermission).filter_by(email = "user@email.com").all()

谢谢,

【问题讨论】:

  • 我发现以下方法可以连接两个表:result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='user@email.com').all() 但我还没有管理如何使三个表的工作相似(包括 DocumentPermissions)。有什么想法吗?
  • 当我执行类似的任务时,我得到 SyntaxError: keyword can't be an expression

标签: python sql join sqlalchemy


【解决方案1】:

此函数将生成所需的表作为元组列表。

def get_documents_by_user_email(email):
    query = session.query(
       User.email, 
       User.name, 
       Document.name, 
       DocumentsPermissions.readAllowed, 
       DocumentsPermissions.writeAllowed,
    )
    join_query = query.join(Document).join(DocumentsPermissions)

    return join_query.filter(User.email == email).all()

user_docs = get_documents_by_user_email(email)

【讨论】:

    【解决方案2】:

    试试这个

    q = Session.query(
             User, Document, DocumentPermissions,
        ).filter(
             User.email == Document.author,
        ).filter(
             Document.name == DocumentPermissions.document,
        ).filter(
            User.email == 'someemail',
        ).all()
    

    【讨论】:

    • join 是做什么的?内部、外部、交叉还是什么?
    • 这实际上根本不做连接,它返回一个元组中的行对象。在这种情况下,它会返回 [(<user>, <document>, <documentpermissions>),...]/
    • “根本不进行连接” - 这有点误导。它将有像select x from a, b ,c 这样的sql,这是一个交叉连接。然后过滤器使其成为内部连接。
    • 您可以通过省略.all() 来打印查询。所以print Session.query.... 看看它到底在做什么。
    • 我是 SQLAlchemy 的新手。我注意到.filter() 可以接收多个条件,如果逗号分隔。最好在括号内使用一个带有逗号分隔的.filter(),还是像上面的答案一样使用多个.filter()
    【解决方案3】:

    正如@letitbee 所说,将主键分配给表并正确定义关系以允许正确的 ORM 查询的最佳实践。话说……

    如果您有兴趣按照以下方式编写查询:

    SELECT
        user.email,
        user.name,
        document.name,
        documents_permissions.readAllowed,
        documents_permissions.writeAllowed
    FROM
        user, document, documents_permissions
    WHERE
        user.email = "user@email.com";
    

    那么你应该选择类似的东西:

    session.query(
        User, 
        Document, 
        DocumentsPermissions
    ).filter(
        User.email == Document.author
    ).filter(
        Document.name == DocumentsPermissions.document
    ).filter(
        User.email == "user@email.com"
    ).all()
    

    如果相反,您想执行以下操作:

    SELECT 'all the columns'
    FROM user
    JOIN document ON document.author_id = user.id AND document.author == User.email
    JOIN document_permissions ON document_permissions.document_id = document.id AND document_permissions.document = document.name
    

    那么你应该按照以下方式做一些事情:

    session.query(
        User
    ).join(
        Document
    ).join(
        DocumentsPermissions
    ).filter(
        User.email == "user@email.com"
    ).all()
    

    关于那个的一个注释......

    query.join(Address, User.id==Address.user_id) # explicit condition
    query.join(User.addresses)                    # specify relationship from left to right
    query.join(Address, User.addresses)           # same, with explicit target
    query.join('addresses')                       # same, using a string
    

    欲了解更多信息,请访问docs

    【讨论】:

    • 这样做。请参阅 - 连接中指定的内容不多。这是因为如果表/db-model 已经在这些表之间设置了正确的外键 - SQLAlchemy 将负责为您加入正确的列。
    • 这是sqlalchemy最正确的答案和正确使用方法。但是,我不得不使用过滤器解决方案,因为 SQLAlchemy 不接受带有 join() 的删除语句
    【解决方案4】:

    扩展 Abdul 的答案,您可以通过连接列来获得 KeyedTuple 而不是离散的行集合:

    q = Session.query(*User.__table__.columns + Document.__table__.columns).\
            select_from(User).\
            join(Document, User.email == Document.author).\
            filter(User.email == 'someemail').all()
    

    【讨论】:

    • 这行得通。但是,序列化对象时缺少它们​​的列名。
    【解决方案5】:

    一种好的风格是为权限设置一些关系和一个主键(实际上,通常最好为所有内容设置整数主键,但无论如何):

    class User(Base):
        __tablename__ = 'users'
        email = Column(String, primary_key=True)
        name = Column(String)
    
    class Document(Base):
        __tablename__ = "documents"
        name = Column(String, primary_key=True)
        author_email = Column(String, ForeignKey("users.email"))
        author = relation(User, backref='documents')
    
    class DocumentsPermissions(Base):
        __tablename__ = "documents_permissions"
        id = Column(Integer, primary_key=True)
        readAllowed = Column(Boolean)
        writeAllowed = Column(Boolean)
        document_name = Column(String, ForeignKey("documents.name"))
        document = relation(Document, backref = 'permissions')
    

    然后用连接做一个简单的查询:

    query = session.query(User, Document, DocumentsPermissions).join(Document).join(DocumentsPermissions)
    

    【讨论】:

    • 最后一行query 设置为什么,如何访问其中的连接记录?
    • @pate 我不确定“查询设置为什么”是什么意思,但它会根据关系加入,并产生 3 元组。 query() 调用的参数本质上是 sqlalchemy 中的选择列表。
    • 如何访问查询结果集中的Document(第二个元组值)?
    • @PetrusTheron 就像我说的,查询将产生 3 元组。你可以索引元素,或者直接解包:for (user, doc, perm) in query: print "Document: %s" % doc
    • 哇,过去的爆炸。 'permissions' 是 Document 对象的一个​​属性,它为您提供一组 DocumentPermission 对象。因此是反向引用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-07
    • 1970-01-01
    • 2017-01-27
    • 1970-01-01
    • 1970-01-01
    • 2016-07-04
    • 1970-01-01
    相关资源
    最近更新 更多