SQLAlchemy-使用DateTime列查询以按月/日/年过滤

时间:2018-07-21 00:18:46

标签: python flask sqlalchemy flask-sqlalchemy

我正在建立一个涉及跟踪付款的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列的最佳方法是什么?谢谢您的帮助。

1 个答案:

答案 0 :(得分:12)

SQLAlchemy有效地将以Python表示的查询转换为SQL。但这是基于定义模型时分配给Column的数据类型而在相对肤浅的级别上完成的。

这意味着它不一定会在其datetime.datetime构造上复制Python的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天内到期的 AND 付款,可以使用:

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可以在需要时从数据库列中进行即时计算。

在您的特定问题中,我们将定义三个混合属性:您的due_date_day模型中的due_date_monthdue_date_yearPayment。这是如何工作的:

... 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中获得最大价值。我希望这对您有所帮助!