【问题标题】:What is the best practice for lookup values in SQLAlchemy?SQLAlchemy 中查找值的最佳实践是什么?
【发布时间】:2020-02-26 14:03:47
【问题描述】:

我正在编写一个非常基本的 Flask 应用程序,使用 Flask-SQLAlchemy 来跟踪库存和分销。我可以使用一些指导来了解如何以最佳方式处理常见值的查找表。我的数据库后端将是用于搜索的 MySQL 和 ElasticSearch。

如果我有一个通用映射结构,其中所有数据都进入一个特定的表,比如Vehicle,有一个通用的值列表来查找Vehicle.make 列,那么实现这一点的最佳方法是什么?

我对解决这个问题的想法是以下两种方法之一:

查找表

我可以在我有关系的地方设置类似的东西,并将品牌存储在VehicleMake。但是,如果我的 预期 品牌列表很少(比如 10 个),这似乎没有必要。

class VehicleMake(Model):
    id = Column(Integer, primary_key=True)
    name = Column(String(16))
    cars = relationship('Vehicle', backref='make', lazy='dynamic')

class Vehicle(Model):
    id = Column(Integer, primary_key=True)
    name = Column(String(32))

存储为字符串

我可以将它作为字符串存储在Vehicle 模型上。但是将公共值存储为字符串会不会浪费空间?

class Vehicle(Model):
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
    make = Column(String(16))

我最初的想法只是有一个包含这样的映射的字典,并在模型中根据需要引用它。我只是不清楚在退回车辆模型时如何配合。

MAKE_LIST = {
    1: 'Ford',
    2: 'Dodge',
    3: 'Chevrolet'
}

欢迎任何反馈 - 如果有涵盖此特定场景的文档,我很乐意阅读并自己回答这个问题。我的预期数量会很低(每周 40-80 条记录),所以不需要太快,我只想遵循最佳实践。

【问题讨论】:

    标签: sqlalchemy flask-sqlalchemy


    【解决方案1】:

    简短的回答是视情况而定。

    答案很长,这取决于您存储的内容以及所述车辆的品牌以及您希望添加新类型的频率。

    如果您需要存储的不仅仅是每个品牌的名称,还需要一些额外的元数据,例如油箱的大小、货物空间,甚至是排序键,请使用附加表。这样一个小表的开销是最小的,如果你使用 make ids 而不是 make names 与前端通信,这完全没有问题。只需记住将索引添加到 vehicle.make_id 以提高查找效率。

    class VehicleMake(Model):
        id = Column(Integer, primary_key=True)
        name = Column(String(16))
        cars = relationship('Vehicle', back_populates="make", lazy='dynamic')
    
    class Vehicle(Model):
        id = Column(Integer, primary_key=True)
        name = Column(String(32))
        make_id = Column(Integer, ForeignKey('vehicle_make.id'), nullable=False)
        make = relationship("VehicleType", innerjoin=True)
    
    Vehicle.query.get(1).make.name # == 'Ford', the make for vehicle 1
    Vehicle.query.filter(Vehicle.make_id == 2).all() # all Vehicles with make id 2
    Vehicle.query.join(VehicleMake)\
        .filter(VehicleMake.name == 'Ford').all() # all Vehicles with make name 'Ford'
    

    如果您不需要存储任何元数据,则不再需要单独的表。但是,字符串的一般问题是拼写错误和大写/小写字母会破坏数据一致性的风险很高。如果您不需要添加太多新产品,最好使用Enums,SQLAlchemy 中甚至还有 MySQL 特定的。

    import enum
    
    class VehicleMake(enum.Enum):
        FORD = 1
        DODGE = 2
        CHEVROLET = 3
    
    class Vehicle(Model):
        id = Column(Integer, primary_key=True)
        name = Column(String(32))
        make = Column(Enum(VehicleMake), nullable=False)
    
    Vehicle.query.get(1).make.name # == 'FORD', the make for vehicle 1
    Vehicle.query.filter(Vehicle.make == VehicleMake(2)).all() # all Vehicles with make id 2
    Vehicle.query.filter(Vehicle.make == VehicleMake.FORD).all() # all Vehicles with make name 'Ford'
    

    枚举的主要缺点是它们可能难以使用新值进行扩展,尽管至少对于 Postgres 方言特定版本比一般的 SQLAlchemy 版本要好得多,请查看sqlalchemy.dialects.mysql.ENUM。虽然如果你想扩展你现有的枚举,你总是可以在你的 Flask-Migrate/Alembic 迁移中执行raw SQL

    最后,使用字符串的好处。主要是,您可以始终以编程方式强制执行数据一致性。但是,这是以您必须以编程方式强制执行数据一致性为代价的。如果外部用户甚至同事可以更改或插入车辆制造商,除非您对进入数据库的内容非常严格,否则这会给您带来麻烦。例如,将所有值大写以方便分组可能会很好,因为它有效地减少了可能出错的程度。您可以在编写过程中这样做,也可以在sqlalchemy.func.upper(Vehicle.make) 上添加索引并使用hybrid properties 始终查询大写值。

    class Vehicle(Model):
        id = Column(Integer, primary_key=True)
        name = Column(String(32))
        _make = Column('make', String(16))
    
        @hybrid_property
        def make(self):
            return self._make.upper()
    
        @make.expression
        def make(cls):
            return func.upper(cls._make)
    
    Vehicle.query.get(1).make.upper() # == 'FORD', the make for vehicle 1
    Vehicle.query.filter(Vehicle.make == 'FORD').all() # all Vehicles with make name 'FORD'
    

    在做出选择之前,还要考虑一下您希望如何向用户展示它。如果他们应该能够自己添加新选项,请使用字符串或单独的表格。如果要显示可能性的下拉列表,请使用枚举或表格。如果您有一个空数据库,那么很难收集所有字符串值以显示在前端,而无需将其作为列表存储在 Flask 环境中的某个位置。

    【讨论】:

    • 谢谢!在某些情况下,这些将是预定义的数据列表 - 在示例中,我只会将其限制为现代北美主要汽车制造商,但在其他情况下,您是对的,可能有几个不同的属性可能会有所不同。我将进行试验,看看哪种方法最适合我的用例。
    猜你喜欢
    • 1970-01-01
    • 2011-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-07
    • 1970-01-01
    相关资源
    最近更新 更多