【问题标题】:SQLAlchemy - Querying with DateTime columns to filter by month/day/yearSQLAlchemy - 使用 DateTime 列查询以按月/日/年过滤
【发布时间】:2018-12-29 07:27:05
【问题描述】:

我正在构建一个涉及跟踪付款的 Flask 网站,但我遇到了一个问题,我似乎无法按日期过滤我的一个数据库模型。

例如,如果我的桌子是这样的:

payment_to, amount, due_date (a DateTime object)

company A, 3000, 7-20-2018
comapny B, 3000, 7-21-2018
company C, 3000, 8-20-2018

我想过滤它,以便获得 7 月 20 日之后的所有行,或 8 月的所有行,等等。

我可以想出一种粗略的蛮力方法来过滤所有付款,然后遍历列表以按月/年进行过滤,但我宁愿远离这些方法。

这是我的支付数据库模型:

class Payment(db.Model, UserMixin):
    id = db.Column(db.Integer, unique = True, primary_key = True)

    payment_to = db.Column(db.String, nullable = False)
    amount = db.Column(db.Float, nullable = False)

    due_date = db.Column(db.DateTime, nullable = False, default = datetime.strftime(datetime.today(), "%b %d %Y"))
    week_of = db.Column(db.String, nullable = False)

这是我试图按日期过滤Payment

Payment.query.filter(Payment.due_date.month == today.month, Payment.due_date.year == today.year, Payment.due_date.day >= today.day).all()

其中today 就是datetime.today()

我假设due_date 列在我调用它时将具有所有 DateTime 属性(例如.month),但似乎我错了。

按日期过滤Payment 的列的最佳方法是什么?感谢您的帮助。

【问题讨论】:

    标签: python flask sqlalchemy flask-sqlalchemy


    【解决方案1】:

    SQLAlchemy 有效地将您用 Python 表达的查询转换为 SQL。但它在相对肤浅的层面上做到了这一点,这取决于您在定义模型时分配给Column 的数据类型。

    这意味着它不一定会在其 DateTime 构造上复制 Python 的 datetime.datetime API - 毕竟,这两个类的目的是做非常不同的事情! (datetime.datetime 为 Python 提供日期时间功能,而 SQLAlchemy 的 DateTime 告诉其 SQL 翻译逻辑它正在处理 SQL DATETIME 或 TIMESTAMP 列)。

    但别担心!有很多不同的方法可以让你实现你想要做的事情,其中​​一些非常简单。我认为最简单的三个是:

    1. 使用完整的 datetime 实例而不是其组件(日、月、年)来构建您的过滤器。
    2. 在过滤器中使用 SQLAlchemy 的 extract 构造。
    3. 在您的模型中定义三个混合属性,它们返回您可以过滤的付款月、日和年。

    过滤datetime 对象

    这是实现您正在尝试的三种(简单)方法中最简单的一种,而且它的执行速度也应该是最快的。基本上,与其尝试在查询中分别过滤每个组件(日、月、年),不如使用单个 datetime 值。

    基本上,以下内容应该等同于您在上面的查询中尝试执行的操作:

    from datetime import datetime
    
    todays_datetime = datetime(datetime.today().year, datetime.today().month, datetime.today().day)
    
    payments = Payment.query.filter(Payment.due_date >= todays_datetime).all()
    

    现在,payments 应该是到期日在系统当前日期开始(时间 00:00:00)之后的所有付款。

    如果您想变得更复杂,例如过滤过去 30 天内的付款。您可以使用以下代码做到这一点:

    from datetime import datetime, timedelta
    
    filter_after = datetime.today() - timedelta(days = 30)
    
    payments = Payment.query.filter(Payment.due_date >= filter_after).all()
    

    您可以使用and_or_ 组合多个过滤器目标。例如,要退回过去 30 天内到期超过 15 天到期的付款,您可以使用:

    from datetime import datetime, timedelta
    from sqlalchemy import and_
    
    thirty_days_ago = datetime.today() - timedelta(days = 30)
    fifteen_days_ago = datetime.today() - timedelta(days = 15)
    
    # Using and_ IMPLICITLY:
    payments = Payment.query.filter(Payment.due_date >= thirty_days_ago,
                                    Payment.due_date <= fifteen_days_ago).all()
    
    # Using and_ explicitly:
    payments = Payment.query.filter(and_(Payment.due_date >= thirty_days_ago,
                                         Payment.due_date <= fifteen_days_ago)).all()
    

    这里的技巧 - 从您的角度来看 - 是在执行查询之前正确构建过滤器目标 datetime 实例。

    使用extract 构造

    SQLAlchemy 的 extract 表达式(记录在案的 here)用于执行 SQL EXTRACT 语句,这是在 SQL 中您可以从 DATETIME/TIMESTAMP 值中提取月、日或年的方法。

    使用这种方法,SQLAlchemy 告诉您的 SQL 数据库“首先,从我的 DATETIME 列中提取月、日和年,然后然后过滤提取的值”。请注意,这种方法比过滤datetime 值要慢,如上所述。但这就是它的工作原理:

    from sqlalchemy import extract
    
    payments = Payment.query.filter(extract('month', Payment.due_date) >= datetime.today().month,
                                    extract('year', Payment.due_date) >= datetime.today().year,
                                    extract('day', Payment.due_date) >= datetime.today().day).all()
    

    使用混合属性

    SQLAlchemy Hybrid Attributes 是很棒的东西。它们允许您在不修改数据库的情况下透明地应用 Python 功能。我怀疑对于这个特定的用例,它们可能有点矫枉过正,但它们是实现您想要的第三种方法。

    基本上,您可以将混合属性视为“虚拟列”,它们实际上并不存在于您的数据库中,但 SQLAlchemy 可以在需要时从您的数据库列中即时计算。

    在您的具体问题中,我们将在您的 Payment 模型中定义三个混合属性:due_date_daydue_date_monthdue_date_year。以下是它的工作原理:

    ... your existing import statements
    
    from sqlalchemy import extract
    from sqlalchemy.ext.hybrid import hybrid_property
    
    class Payment(db.Model, UserMixin):
        id = db.Column(db.Integer, unique = True, primary_key = True)
    
        payment_to = db.Column(db.String, nullable = False)
        amount = db.Column(db.Float, nullable = False)
    
        due_date = db.Column(db.DateTime, nullable = False, default = datetime.strftime(datetime.today(), "%b %d %Y"))
        week_of = db.Column(db.String, nullable = False)
    
        @hybrid_property
        def due_date_year(self):
            return self.due_date.year
    
        @due_date_year.expression
        def due_date_year(cls):
            return extract('year', cls.due_date)
    
        @hybrid_property
        def due_date_month(self):
            return self.due_date.month
    
        @due_date_month.expression
        def due_date_month(cls):
            return extract('month', cls.due_date)
    
        @hybrid_property
        def due_date_day(self):
            return self.due_date.day
    
        @due_date_day.expression
        def due_date_day(cls):
            return extract('day', cls.due_date)
    
    payments = Payment.query.filter(Payment.due_date_year >= datetime.today().year,
                                    Payment.due_date_month >= datetime.today().month,
                                    Payment.due_date_day >= datetime.today().day).all()
    

    上面的内容是这样的:

    1. 您正在定义您的Payment 模型,就像您已经做的那样。
    2. 但随后您将添加一些名为due_date_yeardue_date_monthdue_date_day 的只读实例属性。以due_date_year 为例,这是一个实例属性,作用于Payment 类的instances。这意味着当您执行one_of_my_payments.due_date_year 时,该属性将从Python 实例中提取due_date 值。因为这一切都发生在 Python 中(即不接触您的数据库),它将在 SQLAlchemy 存储在您的实例中的已经翻译的datetime.datetime 对象上运行。它会返回due_date.year的结果。
    3. 然后您将添加一个 class 属性。这是用@due_date_year.expression 装饰的位。这个装饰器告诉 SQLAlchemy,当它将对 due_date_year 的引用转换为 SQL 表达式时,它应该按照此方法中的定义进行操作。所以上面的例子告诉SQLAlchemy“如果你需要在SQL表达式中使用due_date_year,那么extract('year', Payment.due_date)就是due_date_year的表达方式。

    (注意:上面的示例假定due_date_yeardue_date_monthdue_date_day 都是只读属性。当然,您也可以使用@due_date_year.setter 定义自定义设置器,它也接受参数(self, value) )

    结论

    在这三种方法中,我认为第一种方法(过滤datetime)既最容易理解,也最容易实现,而且执行速度最快。这可能是最好的方法。但这三种方法的原则非常重要,我认为这将帮助您从 SQLAlchemy 中获得最大价值。我希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 2020-08-25
      • 2019-10-27
      • 1970-01-01
      • 1970-01-01
      • 2013-03-08
      • 1970-01-01
      • 2015-01-24
      • 2011-09-11
      • 2016-04-23
      相关资源
      最近更新 更多