将过滤器应用于自动连接的表

geo*_*org 5 python postgresql sqlalchemy

这是我的 SQL 设置

    create table a
    (
        id serial primary key,
        ta text
    );
    create table b
    (
        id serial primary key,
        tb text,
        aid integer references a(id) not null
    );
Run Code Online (Sandbox Code Playgroud)

Python:

import sqlalchemy as sa
import sqlalchemy.orm

connection_url = "..."
engine = sa.create_engine(connection_url, echo=True, future=True)
mapper_registry = sa.orm.registry()

class A:
    pass


class B:
    pass


mapper_registry.map_imperatively(
    B,
    sa.Table(
        'b',
        mapper_registry.metadata,
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('tb', sa.String(50)),
        sa.Column('aid', sa.ForeignKey('a.id')),
    ))

mapper_registry.map_imperatively(
    A,
    sa.Table(
        'a',
        mapper_registry.metadata,
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('ta', sa.String(50))
    ),
    properties={
        'blist': sa.orm.relationship(B, lazy='joined'),
    },
)

with sa.orm.Session(engine) as session:
    sel = sa.select(A)
    cur = session.execute(sel)
    for rec in cur.unique().all():
        print(rec.A.ta, [b.tb for b in rec.A.blist])
Run Code Online (Sandbox Code Playgroud)

到目前为止,这工作正常,但现在我需要对子表 ( B) 应用过滤器,以仅包含与条件匹配的行。

 sel = sa.select(A).where(?WHAT?.like('search'))
Run Code Online (Sandbox Code Playgroud)

换句话说,如何在 SqlAlchemy 中编写与以下 SQL 等效的语句?

SELECT *
FROM a
LEFT OUTER JOIN b ON a.id = b.aid
WHERE b.tb like 'search'
Run Code Online (Sandbox Code Playgroud)

这个怎么样(我希望在目标类中生成空列表):

SELECT *
FROM a
LEFT OUTER JOIN b ON a.id = b.aid
    AND b.tb like 'search'
Run Code Online (Sandbox Code Playgroud)

van*_*van 5

下面提出的两个解决方案(针对 2 个提出的问题)依赖于两个 sqlalchemy 函数:


问题1

SELECT      *
FROM        a
OUTER JOIN  b 
        ON  a.id = b.aid
WHERE       b.tb like 'search'
Run Code Online (Sandbox Code Playgroud)

答案1

是通过此查询实现的,并附有解释:

sel = (
    sa.select(A)

    # disable 'joinedload' if it remains configured on the mapper; otherwise, the line below can be removed
    .options(sa.orm.lazyload(A.blist))

    # join A.blist explicitely
    .outerjoin(B, A.blist)  # or: .outerjoin(B, A.id == B.aid)

    # add the filter
    .filter(B.tb.like('search'))

    # trick/hint to SQ that the relationship objects are already returned in the query
    .options(sa.orm.contains_eager(A.blist))
)
Run Code Online (Sandbox Code Playgroud)

问题2

SELECT      *
FROM        a
OUTER JOIN  b 
        ON  a.id = b.aid
        AND b.tb like 'search'
Run Code Online (Sandbox Code Playgroud)

答案2

是通过此查询实现的,并带有行内解释,但基本上.filter条件已移至join条件中:

sel = (
    sa.select(A)

    # disable 'joinedload' if it remains configured on the mapper; otherwise, the line below can be removed
    .options(sa.orm.lazyload(A.blist))

    # join A.blist explicitely including the filter
    .outerjoin(B, sa.and_(B.aid == A.id, B.tb.like('search')))

    # trick/hint to SQ that the relationship objects are already returned in the query
    .options(sa.orm.contains_eager(A.blist))
)
Run Code Online (Sandbox Code Playgroud)

警告contains_eager:在明确定义的范围内使用和使用它时应该小心,因为您基本上是在向 SA 模型“撒谎”,说您已经加载了“所有”相关对象,而实际上您可能没有加载。仅出于查询数据的目的,通常完全没问题,但修改和添加关系可能会导致一些奇怪的结果。