sqlalchemy: ¿cómo unir varias tablas con una consulta?

93

Tengo las siguientes clases mapeadas de 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"))

Necesito conseguir una mesa como esta para user.email = "[email protected]":

email | name | document_name | document_readAllowed | document_writeAllowed

¿Cómo se puede hacer usando una solicitud de consulta para SQLAlchemy? El siguiente código no me funciona:

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

Gracias,

barankin
fuente
Descubrí que lo siguiente funciona para unir dos tablas: result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='[email protected]').all()Pero aún no he logrado hacer que funcione de manera similar para tres tablas (para incluir DocumentPermissions). ¿Alguna idea?
barankin
Cuando realizo una tarea similar, obtengo SyntaxError: la palabra clave no puede ser una expresión
Ishan Tomar

Respuestas:

93

Prueba esto

q = Session.query(
         User, Document, DocumentPermissions,
    ).filter(
         User.email == Document.author,
    ).filter(
         Document.name == DocumentPermissions.document,
    ).filter(
        User.email == 'someemail',
    ).all()
Abdul Kader
fuente
6
¿Qué tipo de joinhace? interior, exterior, cruz o qué?
Nawaz
7
En realidad, esto no hace una combinación en absoluto, devuelve objetos de fila en una tupla. En este caso, devolvería [(<user>, <document>, <documentpermissions>),...]/
Nombre falso
32
"no se une en absoluto", eso es un poco engañoso. Tendrá sql como, select x from a, b ,cque es una combinación cruzada. Luego, los filtros lo convierten en una combinación interna.
Aidan Kane
7
Puede imprimir la consulta dejando fuera el .all(). Entonces, print Session.query....para ver exactamente qué está haciendo.
Boatcoder
4
Soy nuevo en SQLAlchemy. Noté que .filter()puedo recibir múltiples criterios si están separados por comas. ¿Es preferible usar uno .filter()con separaciones de comas dentro del paréntesis, o usar múltiples .filter()como la respuesta anterior?
Explorador intraestelar
51

Un buen estilo sería configurar algunas relaciones y una clave principal para los permisos (en realidad, generalmente es un buen estilo configurar claves primarias enteras para todo, pero lo que sea):

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

Luego haz una consulta simple con uniones:

query = session.query(User, Document, DocumentsPermissions).join(Document).join(DocumentsPermissions)
letitbee
fuente
¿Qué se queryestablece en la última línea y cómo se accede a los registros unidos en ella?
Petrus Theron
@pate No estoy seguro de lo que quiere decir con 'qué se establece en la consulta', pero se unirá de acuerdo con las relaciones y el rendimiento de 3 tuplas. Los argumentos de la llamada query () son esencialmente la lista de selección en sqlalchemy.
letitbee
¿Cómo accedo al Document(segundo valor de tupla) en el conjunto de resultados de la consulta?
Petrus Theron
1
@PetrusTheron Como dije, la consulta producirá 3 tuplas. Puede indexar elementos, o simplemente descomprimir:for (user, doc, perm) in query: print "Document: %s" % doc
letitbee
1
Vaya, explosión del pasado. 'permisos' es un atributo del objeto Document, que le proporciona un conjunto de objetos DocumentPermission. Por lo tanto, backref.
letitbee
38

Como dijo @letitbee, su mejor práctica es asignar claves primarias a las tablas y definir correctamente las relaciones para permitir una consulta ORM adecuada. Habiendo dicho eso...

Si está interesado en escribir una consulta como:

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

Entonces deberías optar por algo como:

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

Si, en cambio, quieres hacer 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

Entonces deberías hacer algo como:

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

Una nota sobre eso ...

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 obtener más información, visite los documentos .

Ullauri
fuente
4
HACER ESTO. Mira, no hay muchas cosas especificadas en las combinaciones. Esto se debe a que si las tablas / modelo de base de datos ya se han configurado con las claves externas adecuadas entre estas tablas, SQLAlchemy se encargará de unir las columnas adecuadas por usted.
Brad
8

Ampliando la respuesta de Abdul, puede obtener una KeyedTuplecolección de filas en lugar de una discreta uniendo las columnas:

q = Session.query(*User.__table__.columns + Document.__table__.columns).\
        select_from(User).\
        join(Document, User.email == Document.author).\
        filter(User.email == 'someemail').all()
mih
fuente
1
Esto funciona. Sin embargo, faltan los nombres de las columnas al serializar el objeto.
Lucas
1

Esta función producirá la tabla requerida como 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
fuente