sqlalchemy:1つのクエリで複数のテーブルを結合する方法は?


92

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

次のようなテーブルを取得する必要がありますuser.email = "user@email.com"

email | name | document_name | document_readAllowed | document_writeAllowed

SQLAlchemyの1つのクエリ要求を使用してそれを行うにはどうすればよいですか?以下のコードは私には機能しません:

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

おかげで、


2つのテーブルを結合するために次のように機能することがわかりましたresult = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='user@email.com').all()。何か案が?
バランキン'18年

回答:


91

これを試して

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

6
joinそれはどのようなことをしますか?インナー、アウター、クロスまたは何?
Nawaz

7
これは実際には結合をまったく行わず、タプルで行オブジェクトを返します。この場合、返されます[(<user>, <document>, <documentpermissions>),...]/
偽名

32
「結合をまったく行わない」-これは少し誤解を招きます。これselect x from a, b ,cは、クロス結合であるようなSQLになります。フィルターはそれを内部結合にします。
Aidan Kane

7
を省略してクエリを印刷できます.all()。だからprint Session.query....、それが何をしているかを正確に見るために。
ボートコーダー、2017年

4
SQLAlchemyは初めてです。.filter()カンマで区切ると、複数の基準を受け取ることができることに気づきました。.filter()かっこ内にカンマ区切りの1つを使用するのが望ましい.filter()ですか、それとも上記の回答のように複数を使用するのですか?
恒星内探検家

50

適切なスタイルは、いくつかの関係と権限の主キーを設定することです(実際には、通常、すべての整数の主キーを設定するのが適切ですが、何でも)。

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

次に、結合を使用して簡単なクエリを実行します。

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

query最後の行には何が設定され、その中で結合されたレコードにどのようにアクセスしますか?
Petrus Theron 2014年

@pate「クエリの設定」とはどういう意味かわかりませんが、関係と歩留り3タプルに従って結合されます。query()呼び出しの引数は、本質的にはsqlalchemyの選択リストです。
letitbee 2014年

Documentクエリ結果セットの(2番目のタプル値)にアクセスするにはどうすればよいですか?
Petrus Theron

1
@PetrusTheron私が言ったように、クエリは3タプルを生成します。:あなたは、インデックスの要素、または単にアンパックすることができますfor (user, doc, perm) in query: print "Document: %s" % doc
letitbee

1
おっと、過去からの爆風。'permissions'はDocumentオブジェクトの属性で、DocumentPermissionオブジェクトのセットを提供します。したがって、後方参照。
letitbee

37

@letitbeeが言ったように、主キーをテーブルに割り当て、適切なORMクエリを実行できるように関係を適切に定義することがベストプラクティスです。言われていること...

次の行に沿ってクエリを作成することに興味がある場合:

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

次に、あなたは次のようなものに行くべきです:

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

代わりに、次のようなことをしたい場合:

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

次に、次の行に沿って何かを行う必要があります。

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

それについて一つの注意...

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

詳細については、ドキュメントにアクセスしてください。


4
これを行う。見てください-結合で指定されたものは多くありません。これは、tables / db-modelがこれらのテーブル間に適切な外部キーを使用してすでにセットアップされている場合-SQLAlchemyが適切な列のONへの結合を処理するためです。
Brad

8

Abdulの答えを拡張するとKeyedTuple、列を結合することにより、行の個別のコレクションではなくを取得できます。

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

1
これは機能します。ただし、オブジェクトをシリアル化すると、列名が欠落します。
Lucas

1

この関数は、タプルのリストとして必要なテーブルを生成します。

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.