【问题标题】:SQLAlchemy and empty IN clauseSQLAlchemy 和空 IN 子句
【发布时间】:2014-05-07 16:20:43
【问题描述】:

我发现 SQLAlchemy 可以翻译

db.query(...).filter(A.id.in_(ids))

进入

SELECT ...
FROM a
WHERE a.id != a.id

如果ids 为空。这会导致对a 表的顺序扫描,这对性能显然是灾难性的。

第一个问题是:为什么?为什么不只是 1 = 0 或任何不需要顺序扫描的东西?

第二个,更重要的是:是否有常用的解决方法(除了每个in_ 附近的if)?

我猜想 in_ 不能轻易地重新实现以涵盖所有情况而不会导致该问题,但我不能第一个面对它,并且可能有一些解决方案涵盖 in_ 的简单、常见用例。

编辑

SQLAlchemy 每次发生时都会记录一个警告:

"The IN-predicate on 'foo.bar' was invoked with an empty sequence. This results in a contradiction, which nonetheless can be expensive to evaluate. Consider alternative strategies for improved performance."

【问题讨论】:

  • 关于该主题的有趣文章:Handling empty WHERE IN clauses in DBALs.
  • 对我有用的简单、常见的用例解决方案是 if 语句。您可能正在(或应该)进行某种验证。只需将空列表添加到您的检查中,不要浪费数据库开销。
  • @LukasGraf 那个链接失效了,文章现在是here

标签: python sql sqlalchemy


【解决方案1】:

要回答 OP 的“为什么”问题,这里是 FAQ entry(我总是很难找到):

为什么.col.in_([]) 会产生col != col?为什么不1=0

对该问题的一点介绍。 SQL 中的IN 运算符,给定一个 要与列进行比较的元素列表,通常不会 接受一个空列表,也就是说,说:

column IN (1, 2, 3)

这样说是无效的:

column IN ()

SQLAlchemy 的 Operators.in_() 运算符,当给定一个空列表时,会产生这个表达式:

column != column

从 0.6 版开始,它还会产生警告说明 将呈现效率较低的比较操作。这 表达式是唯一一个既与数据库无关又产生 正确的结果。

例如,“只评估为假,通过 比较 1=0 或 1!=1",不能正确处理空值。表达式 喜欢:

NOT column != column

column IS NULL时不会返回一行,而是一个不考虑列的表达式,如:

NOT 1=0

将返回一行。

this post 所示,您可以使用 ANY 函数来避免这种情况,因为即使对于空列表,它在语法上也是有效的(但显然不支持 SQLite)。对于大型列表,它可能也更快,因为它在构建查询时对字符串进行的处理更少。

in_ 运算符的性能问题有recently been fixed,修复可能会在 SQLAlchemy 1.2.0 中。

【讨论】:

【解决方案2】:

我正在使用:

if len(ids) > 0:
    db.query(...).where(A.id.in_(ids))
else:
    db.query(...).where(False)

我尝试了.limit(0) 而不是.where(false),但没有成功。空查询集存在一些幕后差异,这些差异会破坏管道中的其他内容。这种解决方法虽然可能更快,但至少可以避免您提到的警告。

【讨论】:

    【解决方案3】:

    注意您的要求:

    • 只有当A.id 的值可比较时,任何比较才能真正成功。不存在的值无法与任何东西进行比较,所有比较将导致不存在的值反过来被评估为 False。也就是说,如果A.IDNULL,那么A.ID == anythingFalse 并且A.ID != anything 也是FalseA.ID == A.ID || A.ID != A.IDFalse > 如果A.IDNULL
    • 带有空序列的IN-子句询问该值是否是空列表的一部分。不存在的值不属于任何列表,即使是空的也不属于。
    • 因此,您要求的是IS NOT NULL 的一些变体和什么都不是的东西。这是必须检查的条件。不存在的价值不是东西;只有不是NULL 的值才能成为空列表的成员...
    • 由于 sqlalchemy 很聪明地知道这可能不是您想要表达此条件的方式,因此它会发出警告。如果序列为空,您可能应该删除 IN-clause。

    举个具体的例子sqlfiddle

    有关更哲学的方法,请参阅What is the nature of void

    【讨论】:

      【解决方案4】:

      当我遇到这种情况时,这是因为我对我的数据库表列之一使用了 Enum 类型。当我将其更改为字符串时,问题就消失了。这不是一个真正的解决方案,因为我更喜欢 Enum,但它确实避免了这个问题。

      【讨论】:

        【解决方案5】:

        使用子查询,如果ids 为空,将(从不执行)。

        示例:

        subquery = db.query(SomeTable.id).filter(...).subquery()
        
        db.query(...).filter(A.id.in_(subquery))
        

        进入:

        SELECT ...
        FROM a
        WHERE a.id IN (SELECT ...)
        

        【讨论】:

          猜你喜欢
          • 2012-01-26
          • 1970-01-01
          • 2010-10-07
          • 2022-12-11
          • 1970-01-01
          • 2016-07-19
          相关资源
          最近更新 更多