【问题标题】:How should I handle decimal in SQLalchemy & SQLite我应该如何处理 SQLalchemy 和 SQLite 中的小数
【发布时间】:2012-05-08 12:07:00
【问题描述】:

当我使用带有 SQLite 数据库引擎的 Numeric 列时,SQLalchemy 给了我以下警告。

SAWarning:方言 sqlite+pysqlite 不原生支持 Decimal 对象

我正在尝试找出在 SQLalchemy 中使用 pkgPrice = Column(Numeric(12,2)) 的最佳方式,同时仍在使用 SQLite。

这个问题 [1] How to convert Python decimal to SQLite numeric? 展示了一种使用 sqlite3.register_adapter(D, adapt_decimal) 让 SQLite 接收并返回小数,但存储字符串的方法,但我不知道如何深入 SQLAlchemy 核心来做到这一点。类型装饰器看起来是正确的方法,但我还没有理解它们。

有没有人有一个 SQLAlchemy 类型装饰器配方,它在 SQLAlchemy 模型中包含数字或十进制数字,但将它们作为字符串存储在 SQLite 中?

【问题讨论】:

    标签: sqlite sqlalchemy decimal type-conversion


    【解决方案1】:
    from decimal import Decimal as D
    import sqlalchemy.types as types
    
    class SqliteNumeric(types.TypeDecorator):
        impl = types.String
        def load_dialect_impl(self, dialect):
            return dialect.type_descriptor(types.VARCHAR(100))
        def process_bind_param(self, value, dialect):
            return str(value)
        def process_result_value(self, value, dialect):
            return D(value)
    
    # can overwrite the imported type name
    # @note: the TypeDecorator does not guarantie the scale and precision.
    # you can do this with separate checks
    Numeric = SqliteNumeric
    class T(Base):
        __tablename__ = 't'
        id = Column(Integer, primary_key=True, nullable=False, unique=True)
        value = Column(Numeric(12, 2), nullable=False)
        #value = Column(SqliteNumeric(12, 2), nullable=False)
    
        def __init__(self, value):
            self.value = value
    

    【讨论】:

    • 我希望你不会介意我建议对你的代码做一点小改动。 :) 我建议不要只在 process_result_value 中返回一个值: def process_result_value(self, value, dialect): if value != "None": return D(value) else: return None
    • @van,您是否尝试过查询新的 SqliteNumeric 列?结果不正确,因为字符串比较与数字比较完全不同。如果你做 session.query(T).filter(T.value > 100) 你会看到。
    • 另外,我认为 load_dialect_impl 部分是不必要的,因为您已经指定了 impl = types.String
    【解决方案2】:

    由于您似乎使用小数来表示货币值,因此我建议您采取安全措施并将货币的价值存储在其最低面额中,例如1610 美分而不是 16.10 美元。然后你可以只使用 Integer 列类型。

    这可能不是您所期望的答案,但它解决了您的问题并且通常被认为是合理的设计。

    【讨论】:

    • 我的应用程序还处理股票和共同基金,例如123.123456.
    • 您的计算货币单位不必是真实的,它必须等于实体的最小细分,无论是股票、共同基金还是鸡装。例如1231223456 gadagongs,其中 1 gadagong 被转换为例如具有 6 位精度的 Decimal 类型以用于表示目的。将 Decimal 类型用于外部表示不是一个坏主意,但使用整数作为货币的内部表示是明智的。
    【解决方案3】:

    这是一个受@van 和@JosefAssad 启发的解决方案。

    class SqliteDecimal(TypeDecorator):
        # This TypeDecorator use Sqlalchemy Integer as impl. It converts Decimals
        # from Python to Integers which is later stored in Sqlite database.
        impl = Integer
    
        def __init__(self, scale):
            # It takes a 'scale' parameter, which specifies the number of digits
            # to the right of the decimal point of the number in the column.
            TypeDecorator.__init__(self)
            self.scale = scale
            self.multiplier_int = 10 ** self.scale
    
        def process_bind_param(self, value, dialect):
            # e.g. value = Column(SqliteDecimal(2)) means a value such as
            # Decimal('12.34') will be converted to 1234 in Sqlite
            if value is not None:
                value = int(Decimal(value) * self.multiplier_int)
            return value
    
        def process_result_value(self, value, dialect):
            # e.g. Integer 1234 in Sqlite will be converted to Decimal('12.34'),
            # when query takes place.
            if value is not None:
                value = Decimal(value) / self.multiplier_int
            return value
    

    就像@Jinghui Niu 提到的,当十进制作为字符串存储在sqlite 中时,某些查询不会总是按预期运行,例如session.query(T).filter(T.value > 100) 或sqlalchemy 之类的东西.sql.expression.func.min,甚至 order_by,因为 SQL 比较的是字符串(例如字符串中的“9.2”>“19.2”),而不是我们在这些情况下预期的数值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-11
      相关资源
      最近更新 更多