SQLAlchemy中查找值的最佳做法是什么?

时间:2020-02-26 14:03:47

标签: sqlalchemy flask-sqlalchemy

我正在使用Flask-SQLAlchemy编写一个非常基本的Flask应用程序,以跟踪库存和分销。我可以使用一些指导来指导如何使用最佳方法来处理常见值的查找表。我的数据库后端将是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条记录),因此不需要太快了,我只想遵循最佳实践即可。

1 个答案:

答案 0 :(得分: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环境中的某个位置。