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). Любая идея?
баранкин
Когда я выполняю аналогичную задачу, я получаю SyntaxError: ключевое слово не может быть выражением
Ишан Томар

Ответы:

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перекрестное соединение. Затем фильтры делают его внутренним соединением.
Эйдан Кейн
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)
Letitbee
источник
Что queryустановлено в последней строке и как получить доступ к объединенным записям в ней?
Петрус Терон
@pate Я не уверен, что вы имеете в виду под «что установлен запрос», но он будет соединяться в соответствии с отношениями и 3-кортежами yield. Аргументы вызова query () по сути являются списком выбора в sqlalchemy.
letitbee
Как мне получить доступ к Document(2-му значению кортежа) в наборе результатов запроса?
Петрус Терон,
1
@PetrusTheron Как я уже сказал, запрос выдаст 3 кортежа. Вы можете индексировать элементы или просто распаковывать:for (user, doc, perm) in query: print "Document: %s" % doc
letitbee
1
Ого, взрыв из прошлого. «разрешения» - это атрибут объекта Document, который предоставляет вам набор объектов DocumentPermission. Отсюда backref.
letitbee 05
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

Для получения дополнительной информации посетите docs .

Уллаури
источник
4
СДЕЛАЙ ЭТО. Видите - в объединениях не так много всего указано. Это потому, что если таблицы / db-model уже были настроены с соответствующими внешними ключами между этими таблицами, 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
Это работает. Однако имена столбцов отсутствуют при сериализации объекта.
Лукас
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)
Валерий Рамусик
источник