sqlalchemy: jak połączyć kilka tabel za pomocą jednego zapytania?


96

Mam następujące zamapowane klasy 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"))

Potrzebuję takiego stolika na user.email = "user@email.com":

email | name | document_name | document_readAllowed | document_writeAllowed

Jak można to zrobić za pomocą jednego żądania zapytania dla SQLAlchemy? Poniższy kod nie działa dla mnie:

result = session.query(User, Document, DocumentPermission).filter_by(email = "user@email.com").all()

Dzięki,


Zauważyłem, że poniższe działa, aby połączyć dwie tabele: result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='user@email.com').all()Ale nie udało mi się jeszcze zrobić podobnej pracy dla trzech tabel (aby uwzględnić DocumentPermissions). Dowolny pomysł?
barankin

Kiedy wykonuję podobne zadanie, otrzymuję SyntaxError: keyword nie może być wyrażeniem
Ishan Tomar

Odpowiedzi:


96

Spróbuj tego

q = Session.query(
         User, Document, DocumentPermissions,
    ).filter(
         User.email == Document.author,
    ).filter(
         Document.name == DocumentPermissions.document,
    ).filter(
        User.email == 'someemail',
    ).all()

7
Co jointo robi? wewnętrzna, zewnętrzna, krzyż czy co?
Nawaz

7
W rzeczywistości to w ogóle nie wykonuje łączenia, zwraca obiekty wierszowe w krotce. W tym przypadku zwróci [(<user>, <document>, <documentpermissions>),...]/
Fałszywe imię

35
„w ogóle nie łączy się” - to trochę mylące. Będzie miał podobny do sql, select x from a, b ,cktóry jest złączem krzyżowym. Filtry tworzą następnie sprzężenie wewnętrzne.
Aidan Kane

7
Możesz wydrukować zapytanie, opuszczając .all(). Aby print Session.query....dokładnie zobaczyć, co robi.
boatcoder

4
Jestem nowy w SQLAlchemy. Zauważyłem, że .filter()można otrzymać wiele kryteriów, jeśli są oddzielone przecinkami. Czy lepiej jest użyć .filter()oddzielenia przecinkami w nawiasach, czy użyć wielu, .filter()jak w powyższej odpowiedzi?
Intrastellar Explorer

53

Dobrym stylem byłoby ustawienie pewnych relacji i klucza podstawowego dla uprawnień (właściwie zwykle dobrym stylem jest ustawienie całkowitych kluczy głównych dla wszystkiego, ale cokolwiek):

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

Następnie wykonaj proste zapytanie z łączeniami:

query = session.query(User, Document, DocumentsPermissions).join(Document).join(DocumentsPermissions)

Co jest queryustawione w ostatnim wierszu i jak uzyskujesz dostęp do połączonych rekordów?
Petrus Theron,

@pate Nie jestem pewien, co masz na myśli, mówiąc „co jest ustawione na zapytanie”, ale połączy się zgodnie z relacjami i daje trzy krotki. Argumentami wywołania query () są zasadniczo listy wyboru w sqlalchemy.
letitbee

Jak uzyskać dostęp do Document(drugiej wartości krotki) w zestawie wyników zapytania?
Petrus Theron,

1
@PetrusTheron Tak jak powiedziałem, zapytanie zwróci 3 krotki. Możesz indeksować elementy lub po prostu rozpakować:for (user, doc, perm) in query: print "Document: %s" % doc
letitbee

1
Hej, wybuch z przeszłości. „uprawnienia” to atrybut obiektu Document, który udostępnia zestaw obiektów DocumentPermission. Stąd backref.
letitbee

41

Jak powiedział @letitbee, najlepszą praktyką jest przypisywanie kluczy podstawowych do tabel i prawidłowe definiowanie relacji, aby umożliwić prawidłowe zapytania ORM. Biorąc to pod uwagę ...

Jeśli chcesz napisać zapytanie w stylu:

SELECT
    user.email,
    user.name,
    document.name,
    documents_permissions.readAllowed,
    documents_permissions.writeAllowed
FROM
    user, document, documents_permissions
WHERE
    user.email = "user@email.com";

Następnie powinieneś wybrać coś takiego:

session.query(
    User, 
    Document, 
    DocumentsPermissions
).filter(
    User.email == Document.author
).filter(
    Document.name == DocumentsPermissions.document
).filter(
    User.email == "user@email.com"
).all()

Jeśli zamiast tego chcesz zrobić coś takiego:

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

Następnie powinieneś zrobić coś w stylu:

session.query(
    User
).join(
    Document
).join(
    DocumentsPermissions
).filter(
    User.email == "user@email.com"
).all()

Jedna uwaga na ten temat ...

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

Aby uzyskać więcej informacji, odwiedź dokumentację .


4
ZRÓB TO. Zobacz - niewiele rzeczy określonych w złączeniach. Dzieje się tak, ponieważ jeśli tabele / model db zostały już skonfigurowane z odpowiednimi kluczami obcymi między tymi tabelami - SQLAlchemy zajmie się połączeniem ON odpowiednich kolumn za Ciebie.
Brad,

8

Rozwijając odpowiedź Abdula, możesz uzyskać KeyedTuplezamiast dyskretnego zbioru wierszy, łącząc kolumny:

q = Session.query(*User.__table__.columns + Document.__table__.columns).\
        select_from(User).\
        join(Document, User.email == Document.author).\
        filter(User.email == 'someemail').all()

1
To działa. Jednak podczas serializacji obiektu brakuje nazw kolumn.
Lucas

1

Ta funkcja utworzy wymaganą tabelę jako listę krotek.

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.