sqlalchemy: comment joindre plusieurs tables par une requête?

93

J'ai les classes mappées SQLAlchemy suivantes:

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

J'ai besoin d'une table comme celle-ci pour user.email = "[email protected]":

email | name | document_name | document_readAllowed | document_writeAllowed

Comment peut-il être fait en utilisant une requête de requête pour SQLAlchemy? Le code ci-dessous ne fonctionne pas pour moi:

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

Merci,

barankin
la source
J'ai trouvé que ce qui suit fonctionne pour joindre deux tables: result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='[email protected]').all()Mais je n'ai pas encore réussi à faire fonctionner de la même manière pour trois tables (pour inclure DocumentPermissions). Une idée?
barankin
Lorsque j'effectue une tâche similaire, j'obtiens SyntaxError: le mot clé ne peut pas être une expression
Ishan Tomar

Réponses:

93

Essaye ça

q = Session.query(
         User, Document, DocumentPermissions,
    ).filter(
         User.email == Document.author,
    ).filter(
         Document.name == DocumentPermissions.document,
    ).filter(
        User.email == 'someemail',
    ).all()
Abdul Kader
la source
6
Quel genre de joinchose fait-il? intérieur, extérieur, croix ou quoi?
Nawaz
7
En fait, cela ne fait pas du tout de jointure, cela renvoie des objets de ligne dans un tuple. Dans ce cas, il reviendrait [(<user>, <document>, <documentpermissions>),...]/
Faux nom
32
"ne fait pas du tout une jointure" - c'est un peu trompeur. Il aura sql comme select x from a, b ,cqui est une jointure croisée. Les filtres en font alors une jointure interne.
Aidan Kane le
7
Vous pouvez imprimer la requête en laissant le .all(). Alors print Session.query....pour voir exactement ce qu'il fait.
boatcoder
4
Je suis nouveau sur SQLAlchemy. J'ai remarqué que .filter()peut recevoir plusieurs critères si séparés par des virgules. Est-il préférable d'en utiliser un .filter()avec des séparations par virgule entre parenthèses, ou d'utiliser plusieurs .filter()comme la réponse ci-dessus?
Explorateur intrastellaire
51

Un bon style serait de configurer des relations et une clé primaire pour les autorisations (en fait, il est généralement bon de configurer des clés primaires entières pour tout, mais peu importe):

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

Ensuite, faites une simple requête avec des jointures:

query = session.query(User, Document, DocumentsPermissions).join(Document).join(DocumentsPermissions)
letitbee
la source
Qu'est-ce qui est querydéfini sur la dernière ligne et comment accéder aux enregistrements joints?
Petrus Theron
@pate Je ne suis pas sûr de ce que vous entendez par «à quoi la requête est-elle définie», mais elle se joindra selon les relations et le rendement 3-tuples. Les arguments de l'appel query () sont essentiellement la liste de sélection dans sqlalchemy.
letitbee
Comment accéder à la Document(2ème valeur de tuple) dans le jeu de résultats de la requête?
Petrus Theron
1
@PetrusTheron Comme je l'ai dit, la requête donnera 3 tuples. Vous pouvez indexer des éléments, ou simplement décompresser:for (user, doc, perm) in query: print "Document: %s" % doc
letitbee
1
Whoa, souffle du passé. 'permissions' est un attribut de l'objet Document, qui vous donne un ensemble d'objets DocumentPermission. D'où backref.
letitbee
38

Comme l'a dit @letitbee, sa meilleure pratique consiste à attribuer des clés primaires aux tables et à définir correctement les relations pour permettre une interrogation ORM appropriée. Cela étant dit...

Si vous souhaitez rédiger une requête du type:

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

Ensuite, vous devriez opter pour quelque chose comme:

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

Si à la place, vous voulez faire quelque chose comme:

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

Ensuite, vous devriez faire quelque chose du genre:

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

Une note à ce sujet ...

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

Pour plus d'informations, consultez la documentation .

Ullauri
la source
4
FAITES CECI. Voir - pas beaucoup de choses spécifiées dans les jointures. En effet, si le modèle tables / db a déjà été configuré avec des clés étrangères appropriées entre ces tables, SQLAlchemy se chargera de joindre ON les colonnes appropriées pour vous.
Brad
8

En développant la réponse d'Abdul, vous pouvez obtenir une KeyedTuplecollection de lignes au lieu d'une collection discrète de lignes en joignant les colonnes:

q = Session.query(*User.__table__.columns + Document.__table__.columns).\
        select_from(User).\
        join(Document, User.email == Document.author).\
        filter(User.email == 'someemail').all()
mih
la source
1
Cela marche. Cependant, les noms de colonne sont manquants lors de la sérialisation de l'objet.
Lucas
1

Cette fonction produira la table requise sous forme de liste de tuples.

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
la source