SQLAlchemy:查找数组列之间的差异

时间:2017-02-16 09:01:30

标签: python postgresql sqlalchemy

我有一个名为UnitOfWork的表,它有3列cases_identifiedcases_completedcases_double_check,所有这些都是整数的Postgresql数组。

是否可以编写一个查询(或混合属性)来查找已识别但未包含在已完成或双重检查列中的案例?

这是我提出的,但SQL表达式不起作用:

@hybrid_property
def todo(self):
    return [x for x in self.cases_identified
            if x not in self.cases_completed and
            x not in self.cases_double_check]

@todo.expression
def todo(cls):
    return [x for x in cls.cases_identified
            if x not in  cls.cases_completed and
            x not in cls.cases_double_check]

我在测试查询中遇到的错误是:

test = Session.query(UnitOfWork.todo).first()
NotImplementedError: Operator 'contains' is not supported on this expression

1 个答案:

答案 0 :(得分:2)

对于这个答案,我假设cls.cases_identifiedcls.cases_completedcls.cases_double_check在Python方面属于postgresql.ARRAY(Integer)类型。

你的@todo.expression应该只返回:一个SQL表达式。目前它正在尝试返回一个python列表。引发异常是因为postgresql.ARRAY不支持in运算符,但它有一个方法contains,它映射到Postgresql中的@>运算符并测试“if元素是参数数组表达式元素的超集“。另一方面,这不是你想要的。你很幸运,那里有if x not in ...,简单

[x for x in cls.cases_identified]

似乎导致无限循环而不是异常。

广泛地介绍了Postgresql中数组之间的区别here,但是这里是使用SQLAlchemy的方法,首先使用an array constructor

from sqlalchemy import func

...

class UnitOfWork(...):

    @todo.expression
    def todo(cls):
        # Force correlation of `cls` from outer FROM objects. Use
        # the `select()` shorthand method of `FunctionElement`.
        identified = func.unnest(cls.cases_identified).select().correlate(cls)
        completed = func.unnest(cls.cases_completed).select().correlate(cls)
        double_check = func.unnest(cls.cases_double_check).select().correlate(cls)
        # Create the subquery statement
        stmt = identified.except_(completed.union(double_check))
        # Uses the `func` generic for ARRAY constructor from
        # subquery syntax. The subquery statement has to be
        # converted to a "scalar subquery" in the eyes of SQLA
        # (really just a subquery returning 1 col) in order
        # to remove it from the FROM clause hierarchy.
        return func.array(stmt.as_scalar())

这有一个不提供任何FROM对象的缺点(因为它与封闭查询的所有内容相关),所以你必须像这样发出原始查询:

test = Session.query(UnitOfWork.todo).select_from(UnitOfWork).first()

你也可以使用Postgresql intarray module为无空数组的整数提供特殊的函数和运算符:

class UnitOfWork(...):

    @todo.expression
    def todo(cls):
        return (cls.cases_identified - cls.cases_completed - 
                cls.cases_double_check)

请注意,您必须先在Postgresql中安装扩展程序:

CREATE EXTENSION intarray;
相关问题