将IN 用于非常 大型列表确实是病态的,您可能会更好地为using a temporary table 和IN 提供子查询或连接。但问题是“如何覆盖特定运算符的编译器输出”。对于像IN 和NOT IN 这样的二元运算符,您需要覆盖的是SQLAlchemy 如何处理BinaryExpressions 的编译:
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.elements import BinaryExpression
from sqlalchemy.sql.operators import in_op, notin_op
def visit_in_op_binary(compiler, binary, operator, **kw):
return "%s IN %s" % (
compiler.process(binary.left, **kw),
compiler.process(binary.right, **{**kw, "literal_binds": True}))
def visit_notin_op_binary(compiler, binary, operator, **kw):
return "%s NOT IN %s" % (
compiler.process(binary.left, **kw),
compiler.process(binary.right, **{**kw, "literal_binds": True}))
@compiles(BinaryExpression)
def compile_binary(binary, compiler, override_operator=None, **kw):
operator = override_operator or binary.operator
if operator is in_op:
return visit_in_op_binary(
compiler, binary, operator, override_operator=override_operator,
**kw)
if operator is notin_op:
return visit_notin_op_binary(
compiler, binary, operator, override_operator=override_operator,
**kw)
return compiler.visit_binary(binary, override_operator=override_operator, **kw)
请注意,简单地生成包含绑定参数的分组和子句列表的二进制表达式对于非常大的列表会花费大量时间,更不用说即使使用文字绑定也要编译所有内容,因此您可能不会观察到显着的性能提升.另一方面,许多实现对您可以在语句中使用多少个占位符/参数有限制,因此内联绑定允许这样的查询完全运行。
另一方面,如果您的列表确实符合您的实现设置的限制(Postgresql 似乎只受可用 RAM 的限制),您可能不需要任何具有最新 SQLAlchemy 的编译器解决方法; use expanding bind parameters instead:
In [15]: %%time
...: session.query(Foo).\
...: filter(Foo.data.in_(range(250000))).\
...: all()
...:
CPU times: user 5.09 s, sys: 91.9 ms, total: 5.18 s
Wall time: 5.18 s
Out[15]: []
In [16]: %%time
...: session.query(Foo).\
...: filter(Foo.data.in_(bindparam('xs', range(250000), expanding=True))).\
...: all()
...:
CPU times: user 310 ms, sys: 8.05 ms, total: 318 ms
Wall time: 317 ms
Out[16]: []
正如 cmets 中所述,在 1.4 版中,扩展 bindparam 将支持开箱即用的文字执行:
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]: []