SQL Alchemy中的NULL安全不等式比较?

时间:2014-02-10 03:25:14

标签: python sqlalchemy

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

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

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

附加背景

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

具体错误出现在一段代码中,如下所示:

query = getattr(field, op)(value)

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

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

该错误不是由于可能是NULL(不会发生 - 值始终是字符串),而是在与列的行进行比较时包含NULL值,比较运算符为__ne__

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

所以最初的代码如下:

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来模拟它(正如我们现在开始做的那样)。

2 个答案:

答案 0 :(得分:7)

有几种方法可以在其中插入备用运算符,以及创建自定义运算符,但是在调用__ne__()时获得的最公开/主流的方法是在类型级别:< / p>

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

答案 1 :(得分:0)

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

例如:

  

outerjoin(A,    B.outlet_id.op('<=>')(A.outlet_id))