Comment dois-je gérer décimal dans SQLalchemy & SQLite

SQLalchemy me donner l'avertissement suivant lorsque j'utilise une colonne numérique avec un moteur de base de données SQLite.

SAWarning: Dialecte sqlite+pysqlite ne pas soutien Décimal objets nativement

j'essaie de trouver la meilleure façon d'avoir pkgPrice = Column(Numeric(12,2)) dans SQLalchemy tout en utilisant encore SQLite.

cette question [1] comment convertir Python décimal en SQLite numeric? montre une façon d'utiliser sqlite3.register_adapter(D, adapt_decimal) pour avoir SQLite réception et retour décimale, mais stocker les chaînes, mais je ne sais pas comment creuser dans le noyau SQLAlchemy pour le faire encore. Les décorateurs de Type ressemblent à la bonne approche, mais je ne les grok pas encore.

est-ce que quelqu'un a une recette de décorateur de type SQLAlchemy qui aura des nombres numériques ou décimaux dans le modèle SQLAlchemy, mais les stocker comme des cordes en SQLite?

21
demandé sur Community 2012-04-27 22:12:46

3 réponses

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
16
répondu van 2012-04-30 16:15:16

Puisqu'il semble que vous utilisez des décimales pour les valeurs des devises, je vous suggère de faire la chose sûre et de stocker la valeur de la devise dans sa plus basse dénomination, par exemple 1610 cents au lieu de 16.10 dollars. Alors vous pouvez juste utiliser un type de colonne entier.

ce n'est peut-être pas la réponse à laquelle vous vous attendiez, mais cela résout votre problème et est généralement considéré comme une conception saine.

15
répondu JosefAssad 2012-04-29 09:49:30

Voici une solution inspirée à la fois de @van et de @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

comme @Jinghui Niu mentionné, quand la décimale est stockée sous forme de chaînes de caractères dans sqlite, certaines requêtes ne fonctionneront pas toujours comme prévu, comme la session.query (T).filtre (valeur de T. > 100), ou des choses comme sqlalchemy.SQL.expression.la touche func.min, ou même order_by, parce que SQL Compare les chaînes (par exemple" 9.2 " > "19.2" dans les chaînes) au lieu de valeurs numériques comme nous nous y attendions dans ces cas.

0
répondu zhukailei 2018-09-26 22:28:21