【问题标题】:NULL safe inequality comparisons in SQL Alchemy?SQL Alchemy 中的 NULL 安全不等式比较?
【发布时间】:2014-03-07 06:38:18
【问题描述】:

目前,我知道如何在 SQL Alchemy 中表达 NULL 安全 != 比较(与 NULL 条目的比较结果为 True,而不是 NULL)的唯一方法是:

or_(field == None, field != value)

有没有办法在 SQL Alchemy 中表达这一点,使其发出 MySQL <=> 操作或 PostgreSQL IS DISTINCT FROM 操作,将 NULL 视为另一个可能的值?

附加上下文

这是从基于 XML 的查询语言转换为 SQL Alchemy 过滤器表达式的实用程序中的一个错误:http://gerrit.beaker-project.org/#/c/2776/

具体的错误出现在如下所示的 sn-p 代码中:

query = getattr(field, op)(value)

其中“field”是相关的 SQL Alchemy 模型列,“op”是用于比较操作的相关 Python 属性名称,“value”是要检查的值。例如,在过滤运行在具有特定名称的虚拟机管理程序上的虚拟机的特定情况下,它相当于这样做:

query = Hypervisor.hypervisor.__eq__("KVM")

错误的出现不是由于 value 可能是 NULL(这不会发生 - 值始终是字符串),而是在与 所在的行进行比较时em> 包含一个NULL 值,比较运算符是__ne__

对于除__ne__ 之外的每个运算符,标准SQL NULL 处理工作正常(比较返回NULL,这被解释为与过滤器不匹配的行,这是我们想要的)。但是,对于__ne__ 的情况,我们确实希望返回该列中包含 NULL 值的行 - 我们只想排除设置了值的行并且 em> 它与我们要比较的值不匹配。

原来的代码是这样的:

query = getattr(field, op)(value)

现在看起来更像:

if op == "__ne__":
    query = or_(field == None, field != value)
else:
    query = getattr(field, op)(value)

这对我来说似乎很笨拙,所以我想问是否有一种方法可以将“!=”映射到 __ne__ 以外的其他位置,这将为我们提供在数据库层处理而不是模拟它的备用 NULL使用相当于field IS NOT NULL OR field != value 的 SQL Alchemy(我们现在已经开始这样做了)。

【问题讨论】:

  • IFNULL(field, '') 或类似将任何空值转换为空字符串?
  • 有不同的方法可以解决这个问题,您可以实现一个输出<=> / IS DISTINCT FROM 的运算符,或者您可以创建一个输出or_() 的运算符。但是,如果不做出一些让步,他们都不会做的是能够将“None”作为绑定值处理,也就是说,如果您输入 None 与 non-None(假设绑定值为非无)。这通常不是问题(例如 x IS NOT NULLx != %(value)s),但如果这是类似 executemany() 类型的事情,那么它可能是。
  • 但我不太确定你想要什么。如果您传入“None”或一个值(根据该值呈现运算符),核心表达式 - > SQL 映射已经生成了一个不可知的表达式,所以看起来您可能正在寻找一些固定在 SQL 上的东西边。我必须对您使用的 DBAPI 进行试验,看看如果您将“无”作为绑定值会发生什么。
  • 添加了更多关于我们正在做的事情的上下文 - 它是一个数据驱动的过滤器生成器,具体的问题是我们目前必须使用特殊情况 __ne__ 才能获得带有 a 的行的正确答案NULL 在相关栏中。如果我们能以某种方式将其映射到不同的数据库操作符,我们或许可以移除当前的特殊情况,而只需更改操作 -> 方法名称映射。

标签: python sqlalchemy


【解决方案1】:

有几种方法可以在其中插入备用运算符,以及创建自定义运算符,但了解调用 __ne__() 时发生的情况的最公开/主流的方法是在类型级别:

from sqlalchemy import TypeDecorator, type_coerce, String, or_

class NullComparisons(TypeDecorator):
    impl = String

    class comparator_factory(TypeDecorator.Comparator):
        def __ne__(self, other):
            expr = type_coerce(self.expr, String)
            return or_(expr == None, expr != other)

这样就可以做 OR 的事情了:

from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
class Thing(Base):
    __tablename__ = 'thing'

    id = Column(Integer, primary_key=True)
    data = Column(NullComparisons(50))
print(Thing.data != 'hi')

给我们:

thing.data IS NULL OR thing.data != :param_1

那么对于 PG/MySQL 操作符,我们真正应该拥有的是将@compiles 直接链接到操作符的能力。但是这个钩子现在不存在,所以比理想情况下需要更多的努力,我们可以制作一个自定义的列元素来处理它:

from sqlalchemy import TypeDecorator, type_coerce, String
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import BinaryExpression
import operator
class IsDistinctFrom(BinaryExpression):
    pass

@compiles(IsDistinctFrom, "postgresql")
def pg_is_distinct_from(element, compiler, **kw):
    return "%s IS DISTINCT FROM %s" % (
                    compiler.process(element.left, **kw),
                    compiler.process(element.right, **kw),
                )

@compiles(IsDistinctFrom, "mysql")
def mysql_is_distinct_from(element, compiler, **kw):
    return "%s <=> %s" % (
                    compiler.process(element.left, **kw),
                    compiler.process(element.right, **kw),
                )

class AdvancedNullComparisons(TypeDecorator):
    impl = String

    class comparator_factory(TypeDecorator.Comparator):
        def __ne__(self, other):
            expr = type_coerce(self.expr, String)
            # this step coerces a literal into a SQL expression,
            # this can be done without the private API here but the private
            # function does the most thorough job, this could also be made
            # public
            other = self._check_literal(expr, operator.ne, other)
            return IsDistinctFrom(self.expr, other, operator.ne)

那么我们可以试试看:

from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Thing(Base):
    __tablename__ = 'thing'

    id = Column(Integer, primary_key=True)
    data = Column(AdvancedNullComparisons(50))

from sqlalchemy.dialects import postgresql, mysql
print(Thing.data != 'hi').compile(dialect=postgresql.dialect())
print(Thing.data != 'hi').compile(dialect=mysql.dialect())

给我们:

thing.data IS DISTINCT FROM %(param_1)s
thing.data <=> %s

【讨论】:

  • 谢谢!我认为 SQL Alchemy 足够灵活,可能有一种方法可以在列定义时执行此操作,但实际操作的细节超出了我的范围:)
  • 只是想指出&lt;=&gt; 实际上与IS NOT DISTINCT FROM 相同。也就是说,它是相等比较而不是不等比较,尽管它与&lt;&gt; 相似。
  • 而且看起来 SQLAlchemy 1.1 为这些添加了 .isnot_distinct_from().is_distinct_from(),这很方便。
  • @DanC 这应该是这个问题的答案。第二个链接的唯一改进是.is_distinct_from
【解决方案2】:

使用自定义运算符可能会有所帮助: sqlalchemy custom operator

例如:

外连接(A, B.outlet_id.op('')(A.outlet_id))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-07-02
    • 1970-01-01
    • 2011-11-14
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 2012-03-03
    • 1970-01-01
    相关资源
    最近更新 更多