我正在建立一个涉及跟踪付款的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
列的最佳方法是什么?谢谢您的帮助。
答案 0 :(得分:12)
SQLAlchemy有效地将以Python表示的查询转换为SQL。但这是基于定义模型时分配给Column
的数据类型而在相对肤浅的级别上完成的。
这意味着它不一定会在其datetime.datetime
构造上复制Python的DateTime
API-毕竟,这两个类的作用是完全不同的! ({datetime.datetime
为Python提供了日期时间功能,而SQLAlchemy的DateTime
告诉其SQL转换逻辑它正在处理SQL DATETIME或TIMESTAMP列)。
但是不用担心!您可以通过多种方法来实现自己的目标,其中一些方法非常简单。我认为最简单的三个是:
datetime
实例而不是其组成部分(天,月,年)来构造过滤器。extract
构造。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_month
,due_date_year
,Payment
。这是如何工作的:
... 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()
这是上面的操作:
Payment
模型了。 due_date_year
,due_date_month
和due_date_day
的只读实例属性。以due_date_year
为例,这是一个实例属性,它对Payment
类的 instances 起作用。这意味着当您执行one_of_my_payments.due_date_year
时,该属性将从Python实例中提取due_date
值。由于这一切都是在Python中发生的(即不触摸数据库),因此它将对SQLAlchemy已存储在您的实例中的,已经翻译过的datetime.datetime
对象进行操作。它将返回due_date.year
的结果。@due_date_year.expression
装饰的位。该修饰器告诉SQLAlchemy,当将对due_date_year
的引用转换为SQL表达式时,它应该按照此方法中的定义进行操作。因此,上面的示例告诉SQLAlchemy“如果您需要在SQL表达式中使用due_date_year
,那么extract('year', Payment.due_date)
就是due_date_year
的表达方式。(注意:上面的示例假定due_date_year
,due_date_month
和due_date_day
都是只读属性。您当然也可以使用@due_date_year.setter
定义自定义设置器也接受参数(self, value)
)
在这三种方法中,我认为第一种方法(对datetime
进行过滤)既最容易理解,也最容易实现,并且执行速度最快。这可能是最好的方法。但是这三种方法的原理非常重要,我认为这将帮助您从SQLAlchemy中获得最大价值。我希望这对您有所帮助!