sqlalchemy: como juntar várias tabelas por uma consulta?

92

Eu tenho as seguintes classes mapeadas 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"))

Preciso conseguir uma mesa como esta para user.email = "[email protected]":

email | name | document_name | document_readAllowed | document_writeAllowed

Como isso pode ser feito usando uma solicitação de consulta para SQLAlchemy? O código abaixo não funciona para mim:

result = session.query(User, Document, DocumentPermission).filter_by(email = "[email protected]").all()

Obrigado,

Barankin
fonte
Descobri que o seguinte funciona para unir duas tabelas: result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='[email protected]').all()Mas ainda não consegui fazer funcionar de forma semelhante para três tabelas (para incluir DocumentPermissions). Qualquer ideia?
Barankin
Quando eu executo uma tarefa semelhante, obtenho SyntaxError: a palavra-chave não pode ser uma expressão
Ishan Tomar

Respostas:

91

Tente isto

q = Session.query(
         User, Document, DocumentPermissions,
    ).filter(
         User.email == Document.author,
    ).filter(
         Document.name == DocumentPermissions.document,
    ).filter(
        User.email == 'someemail',
    ).all()
Abdul Kader
fonte
6
Que tipo de coisa joinisso faz? interno, externo, cruzado ou o quê?
Nawaz
7
Na verdade, isso não faz uma junção, mas retorna objetos de linha em uma tupla. Nesse caso, ele retornaria [(<user>, <document>, <documentpermissions>),...]/
Nome falso de
32
"não faz nenhuma junção" - isso é um pouco enganador. Ele terá sql como, select x from a, b ,cque é uma junção cruzada. Os filtros então a tornam uma junção interna.
Aidan Kane
7
Você pode imprimir a consulta deixando de fora o .all(). Então, print Session.query....para ver exatamente o que ele está fazendo.
boatcoder de
4
Eu sou novo no SQLAlchemy. Percebi que .filter()pode receber vários critérios se separados por vírgulas. É preferível usar um .filter()com separações por vírgula entre parênteses ou usar vários .filter()como a resposta acima?
Intrastellar Explorer
50

Um bom estilo seria configurar algumas relações e uma chave primária para permissões (na verdade, geralmente é um bom estilo configurar chaves primárias inteiras para tudo, mas tanto faz):

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

Em seguida, faça uma consulta simples com junções:

query = session.query(User, Document, DocumentsPermissions).join(Document).join(DocumentsPermissions)
Letitbee
fonte
O que está querydefinido na última linha e como você acessa os registros combinados nela?
Petrus Theron
@pate Não tenho certeza do que você quer dizer com 'o que é a consulta definida como', mas ele se unirá de acordo com as relações e o resultado será de 3 tuplas. Os argumentos para a chamada query () são essencialmente a lista de seleção em sqlalchemy.
letitbee
Como faço para acessar o Document(valor da 2ª tupla) no conjunto de resultados da consulta?
Petrus Theron
1
@PetrusTheron Como eu disse, a consulta renderá 3 tuplas. Você pode indexar elementos ou apenas descompactar:for (user, doc, perm) in query: print "Document: %s" % doc
letitbee de
1
Uau, explosão do passado. 'permissões' é um atributo do objeto Document, que fornece um conjunto de objetos DocumentPermission. Portanto, backref.
letitbee
37

Como @letitbee disse, sua melhor prática é atribuir chaves primárias às tabelas e definir adequadamente os relacionamentos para permitir a consulta ORM adequada. Dito isto ...

Se você estiver interessado em escrever uma consulta ao longo das linhas de:

SELECT
    user.email,
    user.name,
    document.name,
    documents_permissions.readAllowed,
    documents_permissions.writeAllowed
FROM
    user, document, documents_permissions
WHERE
    user.email = "[email protected]";

Então você deve ir para algo como:

session.query(
    User, 
    Document, 
    DocumentsPermissions
).filter(
    User.email == Document.author
).filter(
    Document.name == DocumentsPermissions.document
).filter(
    User.email == "[email protected]"
).all()

Em vez disso, você deseja fazer algo como:

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

Então você deve fazer algo como:

session.query(
    User
).join(
    Document
).join(
    DocumentsPermissions
).filter(
    User.email == "[email protected]"
).all()

Uma nota sobre isso ...

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

Para obter mais informações, visite os documentos .

Ullauri
fonte
4
FAÇA ISSO. Veja - não há muitas coisas especificadas nas junções. Isso porque se o modelo tables / db já tiver sido configurado com as chaves estrangeiras apropriadas entre essas tabelas - o SQLAlchemy cuidará de unir ON as colunas apropriadas para você.
Brad
8

Expandindo a resposta de Abdul, você pode obter um em KeyedTuplevez de uma coleção discreta de linhas unindo as colunas:

q = Session.query(*User.__table__.columns + Document.__table__.columns).\
        select_from(User).\
        join(Document, User.email == Document.author).\
        filter(User.email == 'someemail').all()
mih
fonte
1
Isso funciona. No entanto, os nomes das colunas estão faltando ao serializar o objeto.
Lucas
1

Esta função produzirá a tabela necessária como uma lista de tuplas.

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)
Valery Ramusik
fonte