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 = "user@email.com":

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 = "user@email.com").all()

Gracias,


Descubrí que lo siguiente funciona para unir dos tablas: result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='user@email.com').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()

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)

¿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 = "user@email.com";

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 == "user@email.com"
).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 == "user@email.com"
).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 .


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

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)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.