【问题标题】:SQLAlchemy Mapping Multiple Columns to Single PropertySQLAlchemy 将多列映射到单个属性
【发布时间】:2019-01-29 17:07:19
【问题描述】:

我正在使用 Flask 和 SQLAlchemy(通过 Flask-SQLAlchemy;使用 MySQL 或 SQLite)在 Python 3 中构建一个 Web 应用程序,我遇到了一种情况,我想在我的在我的数据库中封装多个列的模型类。我非常精通 MySQL,但这是我第一次真正涉足 SQLAlchemy,超出了基础。阅读文档、搜索 SO 和搜索 Google 让我找到了两种可能的解决方案:混合属性 (docs) 或复合列 (docs)。

我的问题是,使用这些方法的含义是什么?哪一种方法适合我的情况?我在下面包含了示例代码,这是我正在做的事情。


背景:我正在开发一个应用程序来跟踪和排序照片,并有一个数据库表,用于存储这些照片的元数据,包括照片的拍摄时间。由于照片是在特定地点拍摄的,因此拍摄的日期和时间具有关联的时区。由于 SQL 与时区有着众所周知的爱/恨关系,我选择在两列中记录照片的拍摄时间:存储日期和时间的日期时间和存储时区名称的字符串。 (我想回避关于如何在 SQL 中存储时区感知日期和时间的不可避免的争论。)我想要的是模型类上的单个参数,我可以使用它来获取正确的 python 日期时间对象,并且我也可以像任何其他列一样设置。

这是我的桌子:

class Photo(db.Model):

    __tablename__ = 'photos'

    id = db.Column(db.Integer, primary_key=True)
    ...
    taken_dt = db.Column(db.datetime, nullable=False)
    taken_tz = db.Column(db.String(64), nullable=False)
    ...

这是我使用混合参数(添加到上面的类,datetime/pytz 代码是伪代码):

    @hybrid_parameter
    def taken(self):
        return datetime.datetime(self.taken_dt, self.taken_tz)

    @taken.setter(self, dt):
        self.taken_dt = dt
        self.taken_tz = dt.tzinfo

从那里我不确定我还需要@taken.expression@taken.comparator 的方式,或者为什么我会选择其中一个。

这是我使用复合列的内容(再次,添加到上面的类中,datetime/pytz 代码是伪代码):

    taken = composite(DateTimeTimeZone._make, taken_dt, taken,tz)

class DateTimeTimeZone(object):

    def __init__(self, dt, tz):
        self.dt = dt
        self.tz = tz

    @classmethod
    def from_db(cls, dt, tz):
        return DateTimeTimeZone(dt, tz)

    @classmethod
    def from_dt(cls, dt):
        return DateTimeTimeZone(dt, dt.tzinfo)

    def __composite_values__(self):
        return (self.dt, self.tz)

    def value(self): 
        #This is here so I can get the actual datetime.datetime object
        return datetime.datetime(self.dt, self.tz)

看起来这个方法有相当多的额外开销,我想不出一种方法来设置它,就像我直接从 datetime.datetime 对象中的任何其他列一样,而不首先使用 @ 实例化值对象987654328@.

欢迎任何关于我是否走错路的指导。谢谢!

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    TL;DR:考虑将AttributeEvent 连接到您的列并让它检查具有tz 属性集的datetime 实例,然后返回DateTimeTimeZone 对象。如果您查看SQLAlchemy docs for Attribute Events,您会发现您可以告诉SQLAlchemy 监听一个属性集事件并调用您的代码。在那里,您可以根据需要对设置的值进行任何修改。但是,您当时无法访问该类的其他属性。我还没有尝试将它与复合材料结合使用,所以我不知道这是否会被称为在复合材料的类型转换之前或之后。你必须尝试。

    edit:这都是关于您想要实现的目标。 AttributeEvent 可以帮助您保持数据一致性,而hybrid_property 和朋友将使您更轻松地查询。您应该将每一个用于其预期的用例。

    更详细地讨论各种解决方案之间的差异:

    hybrid_attributecomposite 是两个完全不同的野兽。要了解hybrid_attribute,首先必须了解column_property 是什么以及可以做什么。

    1) 列属性

    这个放在映射器上,可以包含任何selectable。因此,如果您将具体的子选择放入 column_property 中,您可以像访问具体列一样只读 访问它。计算是即时完成的。您甚至可以使用它来搜索条目。 SQLAlchemy 将为您构建包含您的子选择的正确选择。

    示例:

    class User(Base):
       id = Column(Integer, primary_key=True)
       first_name = Column(Unicode)
       last_name = Column(Unicode)
    
       name = column_property(first_name + ' ' + last_name)
       category = column_property(select([CategoryName.name])
                                  .select_from(Category.__table__
                                               .join(CategoryName.__table__))
                                  .where(Category.user_id == id))
    
    db.query(User).filter(User.name == 'John Doe').all()
    
    db.query(User).filter(User.category == 'Paid').all()
    

    如您所见,这可以简化大量代码,但必须小心考虑性能影响。

    2) hybrid_method 和 hybrid_attribute

    hybrid_attributecolumn_property 类似,但在实例上下文 中可以调用不同的代码路径。因此,您可以在类级别拥有selectable,但在实例级别拥有不同的实现。使用hybrid_method,您甚至可以对两边进行参数化。

    3) 复合属性

    这使您能够将多个具体列组合成一个逻辑列。您必须为此逻辑列编写一个类,以便SQLAlchemy 可以从那里提取正确的值并在选择中使用它。这巧妙地集成在查询框架中,不应该带来任何额外的问题。以我的经验,复合列的用例相当罕见。您的用例似乎很好。对于值的修改,您始终可以使用AttributeEvents。如果您想让整个实例可用,则必须在刷新之前调用MapperEvent。这当然有效,因为我使用它来实现一个完全透明的 Audit Trail 跟踪系统,该系统将每个表中更改的每个值存储在一组单独的表中。

    【讨论】:

    • 感谢您的简洁比较,它有很大帮助!我肯定在寻找一种解决方案,它允许设置和获取尽可能透明,并且理想情况下也可以过滤。属性事件替代方案是我没有偶然发现的,看起来很有趣,我将不得不进一步调查。但在我看来,混合属性方法最接近我正在寻找的东西,因为它允许我使用现有的日期时间对象获取和设置拍摄日期,而不是需要构建一个新的值类来包装它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-31
    • 1970-01-01
    • 2021-11-06
    • 2015-01-11
    • 1970-01-01
    相关资源
    最近更新 更多