SQLAlchemy 有效地将您用 Python 表达的查询转换为 SQL。但它在相对肤浅的层面上做到了这一点,这取决于您在定义模型时分配给Column 的数据类型。
这意味着它不一定会在其 DateTime 构造上复制 Python 的 datetime.datetime API - 毕竟,这两个类的目的是做非常不同的事情! (datetime.datetime 为 Python 提供日期时间功能,而 SQLAlchemy 的 DateTime 告诉其 SQL 翻译逻辑它正在处理 SQL DATETIME 或 TIMESTAMP 列)。
但别担心!有很多不同的方法可以让你实现你想要做的事情,其中一些非常简单。我认为最简单的三个是:
- 使用完整的
datetime 实例而不是其组件(日、月、年)来构建您的过滤器。
- 在过滤器中使用 SQLAlchemy 的
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 天内到期且超过 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_day、due_date_month、due_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()
上面的内容是这样的:
- 您正在定义您的
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的结果。
- 然后您将添加一个 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_year、due_date_month 和due_date_day 都是只读属性。当然,您也可以使用@due_date_year.setter 定义自定义设置器,它也接受参数(self, value) )
结论
在这三种方法中,我认为第一种方法(过滤datetime)既最容易理解,也最容易实现,而且执行速度最快。这可能是最好的方法。但这三种方法的原则非常重要,我认为这将帮助您从 SQLAlchemy 中获得最大价值。我希望这会有所帮助!