【问题标题】:SQLAlchemy: complex ON clause when performing LEFT JOIN on many-to-many relationSQLAlchemy:在多对多关系上执行 LEFT JOIN 时的复杂 ON 子句
【发布时间】:2026-02-13 10:00:01
【问题描述】:

在对多对多关系执行 JOIN 时,有什么方法可以使用动态过滤器扩展 ON 子句?

我在 * 上找到了很好的相关问题:“Performing a left join across a many-to-many table with conditions”。不幸的是,这个问题是关于原始 SQL 的,只是描述了同样的问题。我还没有找到任何好的解决方案如何在 SQLAlchemy 中实现它。

好的,让我描述一下环境。假设有两个表:

Model = declarative_base()

M2M = Table('m2m', Model.metadata,
            Column('book_id', Integer, ForeignKey(...)),
            Column('author_id', Integer, ForeignKey(...)),

class Book(Model):
    __tablename__ = 'books'
    id = Column(Integer, primary_key=True, autoincrement=True)
    authors = relation('Author',
                       secondary=M2M,
                       back_populates='books')

class Author(Model):
    __tablename__ = 'authors'
    id = Column(Integer, primary_key=True, autoincrement=True)
    books = relation('Book',
                     secondary=M2M,
                     back_populates='authors')

让我们创建一些作者,然后为他们分配一些书籍:

A1 = Author()  # id: 1
A2 = Author()  # id: 2
A3 = Author()  # id: 3

B1 = Book(authors=[A1, A2])  # id: 1
B2 = Book(authors=[A2])      # id: 2
B3 = Book(authors=[A3])      # id: 3

好吧,现在我想获取 ID 在 [1,3] 中的书籍,以及 ID 在 [1,2] 中的外连接作者。我的意思是,如果作者 ID 属于“允许的 ID”列表,我想获得加载作者的书籍 B1 和 B2。这样的允许 id 列表是由外部代码创建的,所以我不能用它来构建 primaryjoinsecondaryjoin 关系过滤器,允许的 id 列表是在外部动态生成的。

根据this question,以下查询会有所帮助:

SELECT books.id, authors.id
FROM books
LEFT JOIN m2m ON (
    books.id = m2m.book_id
    AND m2m.author_id IN (1,2)) -- allowed author ids!
LEFT JOIN authors ON author.id = m2m.author_id
WHERE books.id IN (1,3) -- and any another filter
;

-- result:
--  books.id | authors.id
--  ---------+-----------
--         1 |          1
--         1 |          2
--         3 |       null

但我不知道如何使用 SQLAlchemy 创建这样的查询。该文档仅描述了基于子查询的解决方案。

如果有任何帮助,我将不胜感激。

【问题讨论】:

    标签: postgresql sqlalchemy many-to-many left-join outer-join


    【解决方案1】:
    book_ids = [1, 3]
    author_ids = [1, 2]
    
    q = (session.query(Book.id, Author.id)
         .outerjoin(M2M, and_(Book.id == M2M.c.book_id, M2M.c.author_id.in_(author_ids)))
         .outerjoin(Author, M2M.c.author_id == Author.id)
         .filter(Book.id.in_(book_ids))
         )
    

    但是,我认为下面应该产生相同的结果,但少一个 JOIN

    q = (session.query(Book.id, M2M.c.author_id)
         .outerjoin(M2M, and_(Book.id == M2M.c.book_id, M2M.c.author_id.in_(author_ids)))
         .filter(Book.id.in_(book_ids))
         )
    

    【讨论】: