SQLAlchemy 按即将到来的生日/周年纪念日过滤

use*_*863 2 python sqlalchemy flask-sqlalchemy

环境:python 3.8、flask-sqlalchemy、postgres

class User(db.Model):
    name = db.Column(db.Text)
    birthday = db.Column(db.DateTime)

    @classmethod
    def upcoming_birthdays(cls):
        return (cls.query
                .filter("??")
                .all()
                )
Run Code Online (Sandbox Code Playgroud)

我想创建一个 sqlalchemy 查询来过滤 X 天内即将生日的用户。我考虑过使用该extract函数从生日中提取月份和日期,但这对于月底或年底的日子不起作用。我还考虑过尝试将生日转换为儒略日期以进行比较,但我不知道该怎么做。

例如,如果今天是 2020 年 8 月 30 日,它将返回用户的生日

  • 1995 年 9 月 1 日
  • 2010 年 8 月 31 日 .... 等

感谢您的帮助

van*_*van 5

您可以通过如下简单查询来实现您的目标:

q = (
    db.session.query(User)
    .filter(has_birthday_next_days(User.birthday, 7))
)
Run Code Online (Sandbox Code Playgroud)

这不是 @classmethod on User,但如果您愿意,您可以将解决方案转换为一个。


剩下要做的就是实际实现has_birthday_next_days(...),如下所示,主要是原则的文档:

def has_birthday_next_days(sa_col, next_days: int = 0):
    """ 
    sqlalchemy expression to indicate that an sa_col (such as`User.birthday`)
    has anniversary within next `next_days` days.

    It is implemented by simply checking if the 'age' of the person (in years)
    has changed between today and the `next_days` date.
    """
    return age_years_at(sa_col, next_days) > age_years_at(sa_col)
Run Code Online (Sandbox Code Playgroud)

可以有多种实现,age_years_at下面只是一种可能性,具体为postgresql(包括所需的导入):

import datetime
import sqlalchemy as sa

def age_years_at(sa_col, next_days: int = 0):
    """
    Generates a postgresql specific statement to return 'age' (in years)'
    from an provided field either today (next_days == 0) or with the `next_days` offset.
    """
    stmt = func.age(
        (sa_col - sa.func.cast(datetime.timedelta(next_days), sa.Interval))
        if next_days != 0
        else sa_col
    )
    stmt = func.date_part("year", stmt)
    return stmt
Run Code Online (Sandbox Code Playgroud)

最后q = db.session.query(User).filter(has_birthday_next_days(User.birthday, 30)),生成所需的查询:

SELECT "user".id,
       "user".name,
       "user".birthday
FROM   "user"
WHERE   date_part(%(date_part_1)s, age("user".birthday - CAST(%(param_1)s AS INTERVAL))) 
      > date_part(%(date_part_2)s, age("user".birthday))

{'date_part_1': 'year', 'param_1': datetime.timedelta(days=30), 'date_part_2': 'year'}
Run Code Online (Sandbox Code Playgroud)

好处:使用泛型函数实现此功能后,它不仅可以用于列User.birthday,还可以用于任何其他类型兼容的值。此外,这些函数可以在语句的select和部分中单独使用。where例如:

q = (
    db.session.query(
        User,
        age_years_at(User.birthday).label("age_today"),
        age_years_at(User.birthday, 7).label("age_in_a_week"),
        has_birthday_next_days(User.birthday, 7).label("has_bday_7-days"),
        has_birthday_next_days(User.birthday, 30).label("has_bday_30-days"),
    )
    .filter(has_birthday_next_days(User.birthday, 30))
)
Run Code Online (Sandbox Code Playgroud)