sqlalchemy: Wie werden mehrere Tabellen mit einer Abfrage verknüpft?

92

Ich habe die folgenden SQLAlchemy-Klassen zugeordnet:

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

Ich brauche einen Tisch wie diesen für user.email = "[email protected]":

email | name | document_name | document_readAllowed | document_writeAllowed

Wie kann es mit einer Abfrageanforderung für SQLAlchemy gemacht werden? Der folgende Code funktioniert bei mir nicht:

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

Vielen Dank,

Barankin
quelle
Ich habe festgestellt, dass das Folgende funktioniert, um zwei Tabellen zu verbinden: result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='[email protected]').all()Aber ich habe es noch nicht geschafft, die Arbeit für drei Tabellen ähnlich zu machen (einschließlich DocumentPermissions). Irgendeine Idee?
Barankin

Antworten:

91

Versuche dies

q = Session.query(
         User, Document, DocumentPermissions,
    ).filter(
         User.email == Document.author,
    ).filter(
         Document.name == DocumentPermissions.document,
    ).filter(
        User.email == 'someemail',
    ).all()
Abdul Kader
quelle
6
Was joinmacht es? inner, äußer, kreuz oder was?
Nawaz
7
Dies führt eigentlich überhaupt keinen Join durch, sondern gibt Zeilenobjekte in einem Tupel zurück. In diesem Fall würde es [(<user>, <document>, <documentpermissions>),...]/
Fake Name
32
"macht überhaupt keinen Join" - das ist ein wenig irreführend. Es wird SQL wie select x from a, b ,ceine Cross-Join haben. Die Filter machen es dann zu einer inneren Verbindung.
Aidan Kane
7
Sie können die Abfrage drucken, indem Sie die Option weglassen .all(). Also print Session.query....genau sehen, was es tut.
Bootscodierer
4
Ich bin neu in SQLAlchemy. Mir ist aufgefallen, .filter()dass mehrere Kriterien erhalten werden können, wenn durch Kommas getrennt. Ist es vorzuziehen, eine .filter()mit Komma-Trennungen in Klammern zu verwenden oder mehrere .filter()wie in der obigen Antwort zu verwenden?
Intrastellar Explorer
50

Ein guter Stil wäre das Einrichten einiger Beziehungen und eines Primärschlüssels für Berechtigungen (normalerweise ist es ein guter Stil, ganzzahlige Primärschlüssel für alles, aber was auch immer, einzurichten):

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

Führen Sie dann eine einfache Abfrage mit Joins durch:

query = session.query(User, Document, DocumentsPermissions).join(Document).join(DocumentsPermissions)
Lass es sein
quelle
Was ist queryin der letzten Zeile eingestellt und wie greifen Sie auf die darin enthaltenen verknüpften Datensätze zu?
Petrus Theron
@pate Ich bin mir nicht sicher, was Sie unter "Was ist die Abfrage?" verstehen, aber es wird entsprechend den Beziehungen und der Ausbeute 3-Tupel verbunden. Die Argumente für den Aufruf von query () sind im Wesentlichen die Auswahlliste in sqlalchemy.
Letitbee
Wie greife ich auf den Document(2. Tupelwert) in der Abfrageergebnismenge zu?
Petrus Theron
1
@PetrusTheron Wie gesagt, die Abfrage ergibt 3-Tupel. Sie können Elemente indizieren oder einfach entpacken:for (user, doc, perm) in query: print "Document: %s" % doc
Letitbee
1
Whoa, Explosion aus der Vergangenheit. 'Berechtigungen' ist ein Attribut des Document-Objekts, mit dem Sie eine Reihe von DocumentPermission-Objekten erhalten. Daher backref.
Letitbee
36

Wie @letitbee sagte, empfiehlt es sich, Tabellen Primärschlüssel zuzuweisen und die Beziehungen richtig zu definieren, um eine ordnungsgemäße ORM-Abfrage zu ermöglichen. Davon abgesehen ...

Wenn Sie daran interessiert sind, eine Abfrage wie folgt zu schreiben:

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

Dann sollten Sie sich für etwas entscheiden wie:

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

Wenn Sie stattdessen etwas tun möchten wie:

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

Dann sollten Sie etwas in der Art tun:

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

Ein Hinweis dazu ...

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

Weitere Informationen finden Sie in den Dokumenten .

Ullauri
quelle
4
MACH DAS. Siehe - nicht viele Dinge, die in den Joins angegeben sind. Dies liegt daran, dass SQLAlchemy sich darum kümmert, ON die richtigen Spalten für Sie zu verbinden, wenn das Tabellen- / Datenbankmodell bereits mit geeigneten Fremdschlüsseln zwischen diesen Tabellen eingerichtet wurde.
Brad
8

Wenn Sie Abdul's Antwort erweitern, können Sie eine KeyedTupleanstelle einer diskreten Sammlung von Zeilen erhalten, indem Sie die Spalten verbinden:

q = Session.query(*User.__table__.columns + Document.__table__.columns).\
        select_from(User).\
        join(Document, User.email == Document.author).\
        filter(User.email == 'someemail').all()
mih
quelle
1
Das funktioniert. Bei der Serialisierung des Objekts fehlen jedoch die Spaltennamen.
Lucas
1

Diese Funktion erzeugt die erforderliche Tabelle als Liste der Tupel.

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
quelle