【问题标题】:SQLAlchemy force in_() to use literalsSQLAlchemy 强制 in_() 使用文字
【发布时间】:2019-09-08 01:36:33
【问题描述】:

有没有办法强制过滤器中的 SQLAlchemy in_() 运算符将 literal 值传递给数据库,而不是每个元素的单独绑定变量?

我必须为IN 子句执行一个(MySQL)查询,其中可能包含大量值。 (IN (SELECT ...) 子查询在这里不是一个选项。)类似于:

query\
    .join(something)\
    .join(somethingelse)\
    .with_entities(somecol, someothercol)\
    .filter(somecol.in_(long_list_of_ids_in_a_list_variable))\
    .order_by(somecol)\
    .all()

为此,SQLAlchemy 将以下内容发送到 MySQL:

SELECT ... FROM ... JOIN ... 
WHERE id IN (%(id_1)s, %(id_2)s, %(id_3)s, %(id_4)s, %(id_5)s, %(id_6)s,
...
%(id_49)s, %(id_50)s)
ORDER BY ...
{'id_1': 718, 'id_2': 714, 'id_3': 713, 'id_4': 715, 'id_5': 712, 'id_6': 711,
...
'id_49': 657, 'id_50': 656}

随着IN 项目的数量越来越大,此语句变得冗长且笨拙(例如,至少在调试期间)。 (我真的不认为通过使用变量重用 MySQL 查询计划的任何潜在收益是显着的。)

我希望它像行内文字一样发送这些值:

WHERE id IN (718, 714, 713, 715, 712, 711,
...
657, 656)

有没有办法做到这一点(使用in_() 或其他合适的构造)?请注意,我希望将查询的 IN 段转换为像这样的文字,而不是查询的所有其他部分。

[我确实看过SQLAlchemy filter in_ operator,但没有从那里得到适合我的答案。]

【问题讨论】:

    标签: python mysql sqlalchemy


    【解决方案1】:

    在(撰写本文时)1.4 版中,您可以使用扩展 bindparamliteral_execute=True 来实现您所追求的效果:

    In [2]: class Foo(Model):
       ...:     id = Column(Integer, primary_key=True)
       ...:     data = Column(Integer)
    
    In [3]: metadata.create_all()
    
    In [4]: session.query(Foo).\
       ...:     filter(Foo.data.in_(
       ...:         bindparam('xs', range(10), expanding=True, literal_execute=True))).\
       ...:     all()
    2019-09-07 20:35:04,560 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
    2019-09-07 20:35:04,561 INFO sqlalchemy.engine.base.Engine SELECT foo.id AS foo_id, foo.data AS foo_data 
    FROM foo 
    WHERE foo.data IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    2019-09-07 20:35:04,561 INFO sqlalchemy.engine.base.Engine ()
    Out[4]: []
    
    

    【讨论】:

    • 我想知道'xs'魔术字符串是什么意思,是必需的吗?
    • 绑定参数键,必填参数:docs.sqlalchemy.org/en/14/core/…
    • 感谢您的回复。经过一番尝试,我注意到它的参数名称。没有由bindparam 明确指定的事件,sqlalchemy 也会为参数生成自动名称。
    【解决方案2】:

    您可以为您的IN 子句使用text 构造(注意sql 注入风险):

    from sqlalchemy_app import Base, Session, engine
    import sqlalchemy as sa
    from sqlalchemy.sql import text
    
    
    class Something(Base):
    
        id = sa.Column(sa.Integer, primary_key=True)
    
    
    class SomethingElse(Base):
    
        id = sa.Column(sa.Integer, primary_key=True)
        something_id = sa.Column(sa.Integer, sa.ForeignKey("something.id"))
    
    
    if __name__ == "__main__":
        Base.metadata.drop_all(engine)
        Base.metadata.create_all(engine)
        s = Session()
        in_clause = text("something.id IN (1, 2, 3)")
        s.query(Something).join(SomethingElse).filter(in_clause).all()
    

    发出此查询:

    2019-08-10 14:23:32,329 INFO sqlalchemy.engine.base.Engine SELECT something.id AS something_id
    FROM something INNER JOIN somethingelse ON something.id = somethingelse.something_id
    WHERE something.id IN (1, 2, 3)
    

    【讨论】:

    • 谢谢,我不知道text()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-07
    • 2013-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    相关资源
    最近更新 更多