SQLAlchemy:如何扩展混合属性?

时间:2018-10-05 09:45:19

标签: python sql-server sqlalchemy

我正在使用MSSQL数据库,无法控制数据库设置或数据库中的(只读)数据。像这样在SQLAlchemy中表示一个表:

class pdAnlage(pdBase):
    __tablename__ = "Anlage"
    typ = Column(CHAR(4), primary_key=True)
    nummer = Column(CHAR(4), primary_key=True)

在访问数据库时,我需要一个属性“名称”,该属性只是“ typ”和“ nummer”的连接,并且它们之间有一个点。所以我这样做了:

    @hybrid_property
    def name(self):
        return self.typ + '.' + self.nummer

看起来简单,并且可以正常工作。不过,有两个警告,一个是一般性,另一个是特殊性。一般情况:表很大,我想对Anlage.name进行查询,如下所示:

db.query(Anlage).filter(Anlage.name.like('A%.B'))
db.query(Anlage).filter(Anlage.name == 'X.Y')

这有效,但是效率低下,因为SQL Server首先必须在进行测试之前将(大)表的所有“ typ”和“ nummer”列连接在一起。所以我定义了这样的类方法:

    @classmethod
    def name_like(self, pattern):
        p = pattern.split('.', 2)
        if len(p) == 1 or not p[1]:
            return self.typ.like(p[0])
        else:
            return and_(self.typ.like(p[0]), self.nummer.like(p[1]))

这不是很优雅,但是可以很好地完成工作。重载“ ==”和“ like()”会更好,有没有办法做到这一点?

由于特殊情况:名称和类型列都可以在数据库中包含尾随空格。但是name属性不能有空格,尤其是点号之前不能有空格。所以我试图像这样重写名称混合属性:

    @hybrid_property
    def name(self):
        return self.typ.rstrip() + '.' + self.nummer.rstrip()

这不起作用,因为SQLAlchemy不知道如何将rstrip()python方法转换为MSSQL RTRIM()函数。我该怎么办?

1 个答案:

答案 0 :(得分:1)

您可以实现一个custom comparator来以特殊方式(以及其他必要方式)处理字符串操作数:

from sqlalchemy.ext.hybrid import Comparator

_sep = '.'


def _partition(s):
    typ, sep, nummer = s.partition(_sep)
    return typ, nummer


class NameComparator(Comparator):

    def __init__(self, typ, nummer):
        self.typ = typ
        self.nummer = nummer
        super().__init__(func.rtrim(typ) + _sep + func.rtrim(nummer))

    def operate(self, op, other, **kwgs):
        if isinstance(other, str):
            typ, nummer = _partition(other)
            expr = op(self.typ, typ, **kwgs)

            if nummer:
                expr = and_(expr, op(self.nummer, nummer, **kwgs))

            return expr

        else:
            # Default to using the "slow" method of concatenating first that
            # hides the columns from the index created for the primary key.
            return op(self.__clause_element__(), other, **kwgs)

并与您的混合属性一起使用:

class pdAnlage(Base):
    __tablename__ = "Anlage"
    typ = Column(CHAR(4), primary_key=True)
    nummer = Column(CHAR(4), primary_key=True)

    @hybrid_property
    def name(self):
        return self.typ.rstrip() + _sep + self.nummer.rstrip()

    @name.comparator
    def name(cls):
        return NameComparator(cls.typ, cls.nummer)