sqlalchemy: วิธีการเข้าร่วมหลายตารางโดยหนึ่งแบบสอบถาม?


98

ฉันมีคลาสที่แมป 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 รหัสด้านล่างใช้ไม่ได้สำหรับฉัน:

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

ขอบคุณ


ฉันพบว่าสิ่งต่อไปนี้ใช้ร่วมกับสองตารางได้: result = session.query(User, Document).select_from(join(User, Document)).filter(User.email=='user@email.com').all()แต่ฉันยังไม่ได้จัดการวิธีทำให้งานเหมือนกันสำหรับสามตาราง (รวม DocumentPermissions) ความคิดใด ๆ ?
barankin

เมื่อฉันทำงานที่คล้ายกันฉันได้รับ SyntaxError: keyword ไม่สามารถเป็นนิพจน์ได้
Ishan Tomar

คำตอบ:


98

ลองทำตามนี้

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

7
ชนิดของการjoinไม่ได้ทำอย่างไร ด้านในด้านนอกข้ามหรืออะไร
Nawaz

7
สิ่งนี้ไม่ได้ทำการรวมเลย แต่จะส่งคืนอ็อบเจ็กต์แถวในทูเพิล ในกรณีนี้มันจะกลับมา[(<user>, <document>, <documentpermissions>),...]/
ชื่อปลอม

37
"ไม่เข้าร่วมเลย" - นั่นเป็นการทำให้เข้าใจผิดเล็กน้อย มันจะมี sql เหมือนselect x from a, b ,cซึ่งเป็น cross join จากนั้นตัวกรองจะทำให้เป็นการรวมภายใน
Aidan Kane

7
คุณสามารถพิมพ์แบบสอบถามได้โดยปิดไฟล์.all(). ดังนั้นprint Session.query....จะเห็นว่าสิ่งที่จะทำ
boatcoder

4
ฉันยังใหม่กับ SQLAlchemy ฉันสังเกตว่า.filter()สามารถรับหลายเกณฑ์ได้หากคั่นด้วยเครื่องหมายจุลภาค ควรใช้อันที่.filter()มีเครื่องหมายจุลภาคคั่นอยู่ในวงเล็บหรือไม่หรือใช้หลายตัว.filter()เหมือนคำตอบข้างบน
Intrastellar Explorer

53

รูปแบบที่ดีคือการตั้งค่าความสัมพันธ์บางอย่างและคีย์หลักสำหรับสิทธิ์ (จริงๆแล้วการตั้งค่าคีย์หลักจำนวนเต็มเป็นรูปแบบที่ดีสำหรับทุกสิ่ง แต่อย่างใดก็ตาม):

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

@pate ฉันไม่แน่ใจว่าคุณหมายถึงอะไร 'คิวรีตั้งค่าเป็นอะไร' แต่จะเข้าร่วมตามความสัมพันธ์และให้ผลตอบแทน 3-tuples อาร์กิวเมนต์ของการเรียก query () โดยพื้นฐานแล้วเป็นรายการที่เลือกใน sqlalchemy
letitbee

ฉันจะเข้าถึงDocument(ค่าทูเปิลที่ 2) ในชุดผลลัพธ์การสืบค้นได้อย่างไร
Petrus Theron

1
@PetrusTheron เหมือนที่ฉันพูดแบบสอบถามจะให้ผล 3-tuples คุณสามารถจัดทำดัชนีองค์ประกอบหรือแกะกล่อง:for (user, doc, perm) in query: print "Document: %s" % doc
letitbee

1
โอ้โหระเบิดจากอดีต 'สิทธิ์' เป็นแอตทริบิวต์ของวัตถุเอกสารที่ให้ชุดของวัตถุ DocumentPermission ดังนั้น backref
letitbee

41

ดังที่ @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
ทำเช่นนี้. ดู - ไม่ใช่สิ่งที่ระบุไว้ในการรวม นั่นเป็นเพราะถ้าตาราง / db-model ได้รับการตั้งค่าด้วยคีย์ต่างประเทศที่เหมาะสมระหว่างตารางเหล่านี้แล้ว SQLAlchemy จะดูแลการเข้าร่วมในคอลัมน์ที่เหมาะสมให้กับคุณ
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

2

ฟังก์ชันนี้จะสร้างตารางที่ต้องการเป็นรายการสิ่งที่เพิ่มขึ้น

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.