sqlalchemy: як об’єднати кілька таблиць за одним запитом?


93

У мене є такі зіставлені класи 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). Будь-яка ідея?
баранкін

Коли я виконую подібне завдання, я отримую SyntaxError: ключове слово не може бути виразом
Ішан Томар,

Відповіді:


93

Спробуйте це

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
"взагалі не робить приєднання" - це трохи вводить в оману. Він матиме sql, подібний select x from a, b ,cдо перехресного з'єднання. Потім фільтри роблять це внутрішнє з'єднання.
Айдан Кейн,

7
Ви можете надрукувати запит, залишивши .all(). Тож print Session.query....побачити, що саме він робить.
boatcoder

4
Я новачок у SQLAlchemy. Я помітив, що я .filter()можу отримати кілька критеріїв, якщо розділити їх комами. Чи переважно використовувати один .filter()із розділеннями комами всередині дужок або використовувати декілька, .filter()як наведену вище відповідь?
Intrastellar Explorer

51

Хорошим стилем було б встановити деякі відносини та первинний ключ для дозволів (насправді, як правило, це гарний стиль - встановити цілочисельні первинні ключі для всього, але що завгодно):

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встановлено в останньому рядку та як ви отримуєте доступ до об’єднаних записів у ньому?
Петрус Терон,

@pate Я не впевнений, що ви маєте на увазі під словом "для чого встановлений запит", але він приєднується відповідно до відносин і дає 3 кортежі. Аргументами виклику query () є, по суті, список вибору в sqlalchemy.
letitbee

Як отримати доступ до Document(другого значення кортежу) у наборі результатів запиту?
Петрус Терон

1
@PetrusTheron Як я вже сказав, запит дасть 3 кортежі. Ви можете індексувати елементи, або просто розпаковувати:for (user, doc, perm) in query: print "Document: %s" % doc
letitbee

1
Ой, вибух з минулого. 'дозволи' - це атрибут об'єкта Document, який надає вам набір об'єктів DocumentPermission. Звідси зворотне посилання.
letitbee

38

Як сказав @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-модель вже налаштовані з відповідними зовнішніми ключами між цими таблицями - SQLAlchemy подбає про приєднання до вас відповідних стовпців.
Бред

8

Розширюючи відповідь Абдула, ви можете отримати 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
Це працює. Однак їх імена стовпців відсутні при серіалізації об'єкта.
Лукас,

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.