sqlalchemy: come unire più tabelle con una query?


93

Ho le seguenti classi mappate 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"))

Ho bisogno di un tavolo come questo per user.email = "user@email.com":

email | name | document_name | document_readAllowed | document_writeAllowed

Come può essere effettuato utilizzando una richiesta di query per SQLAlchemy? Il codice seguente non funziona per me:

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

Grazie,


Ho scoperto che quanto segue funziona per unire due tabelle: result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='user@email.com').all()Ma non sono ancora riuscito a rendere il lavoro simile per tre tabelle (per includere DocumentPermissions). Qualche idea?
barankin

Quando eseguo un'attività simile, ottengo SyntaxError: la parola chiave non può essere un'espressione
Ishan Tomar

Risposte:


93

Prova questo

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

6
Che tipo di joincosa fa? interno, esterno, croce o cosa?
Nawaz

7
Questo in realtà non fa affatto un join, restituisce oggetti riga in una tupla. In questo caso, restituirà [(<user>, <document>, <documentpermissions>),...]/
Fake Name

32
"non fa affatto un join" - questo è un po 'fuorviante. Avrà sql like select x from a, b ,cche è un cross join. I filtri quindi lo rendono un join interno.
Aidan Kane

7
È possibile stampare la query tralasciando il file .all(). Quindi print Session.query....per vedere esattamente cosa sta facendo.
boatcoder

4
Sono nuovo in SQLAlchemy. Ho notato che .filter()possono ricevere più criteri se separati da virgole. È preferibile usarne uno .filter()con separazioni virgole all'interno delle parentesi o utilizzare più .filter()come la risposta sopra?
Intrastellar Explorer

51

Un buon stile sarebbe quello di impostare alcune relazioni e una chiave primaria per i permessi (in realtà, di solito è un buon stile impostare chiavi primarie intere per tutto, ma qualunque cosa):

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

Quindi fai una semplice query con join:

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

Cosa è queryimpostato nell'ultima riga e come si accede ai record uniti in essa?
Petrus Theron

@pate Non sono sicuro di cosa intendi con "su cosa è impostata la query", ma si unirà in base alle relazioni e restituirà 3-tuple. Gli argomenti della chiamata query () sono essenzialmente l'elenco di selezione in sqlalchemy.
letitbee

Come si accede al Document(2 ° valore della tupla) nel set di risultati della query?
Petrus Theron

1
@PetrusTheron Come ho detto, la query produrrà 3 tuple. Puoi indicizzare gli elementi o semplicemente decomprimere:for (user, doc, perm) in query: print "Document: %s" % doc
letitbee

1
Whoa, un tuffo nel passato. "autorizzazioni" è un attributo dell'oggetto Document, che fornisce un insieme di oggetti DocumentPermission. Quindi backref.
letitbee

38

Come ha detto @letitbee, è buona norma assegnare chiavi primarie alle tabelle e definire correttamente le relazioni per consentire una corretta interrogazione ORM. Detto ciò...

Se sei interessato a scrivere una query sulla falsariga di:

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

Quindi dovresti scegliere qualcosa come:

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

Se invece vuoi fare qualcosa come:

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

Quindi dovresti fare qualcosa sulla falsariga di:

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

Una nota a riguardo ...

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

Per ulteriori informazioni, visita la documentazione .


4
FAI QUESTO. Vedi - non molte cose specificate nei join. Questo perché se le tabelle / db-model sono già state configurate con chiavi esterne appropriate tra queste tabelle, SQLAlchemy si occuperà di unire ON le colonne appropriate per te.
Brad

8

Espandendo la risposta di Abdul, puoi ottenere una KeyedTupleraccolta di righe invece di una discreta unendo le colonne:

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

1
Funziona. Tuttavia, i nomi delle colonne mancano durante la serializzazione dell'oggetto.
Lucas

1

Questa funzione produrrà la tabella richiesta come elenco di tuple.

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.