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.