web-dev-qa-db-ja.com

SQLAlchemy-月/日/年でフィルタリングするDateTime列を使用したクエリ

私はFlask支払いを追跡するWebサイトを構築していますが、日付によってdbモデルの1つを実際にフィルター処理できないように見える問題に遭遇しました。

たとえば、これが私のテーブルのように見える場合:

_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の列を日付でフィルタリングする最良の方法は何ですか?ご協力ありがとうございました。

7
LeetCoder

SQLAlchemyは、Pythonで表現されたクエリをSQLに効果的に変換します。しかし、モデルを定義するときにColumnに割り当てるデータ型に基づいて、比較的表面的なレベルでそれを行います。

これは、Pythonの_datetime.datetime_ APIをそのDateTime構造に必ずしも複製しないことを意味します-結局、これらの2つのクラスは非常に異なることを行うためのものです! (_datetime.datetime_はPythonに日時機能を提供し、SQLAlchemyのDateTimeはSQL DATETIMEまたはTIMESTAMP列を処理していることをSQL変換ロジックに伝えます)。

しかし、心配しないでください!あなたがやろうとしていることを達成するためには、いくつかの異なる方法があり、それらのいくつかは非常に簡単です。私が思う最も簡単な3つは:

  1. コンポーネント部分(日、月、年)ではなく、完全なdatetimeインスタンスを使用してフィルターを構築します。
  2. SQLAlchemyのextractコンストラクトをフィルターで使用します。
  3. モデルに3つのハイブリッドプロパティを定義します。これらのプロパティは、支払いの月、日、および年を返します。これらのプロパティは、フィルタリングの対象となります。

datetimeオブジェクトのフィルタリング

これは、試行していることを達成する3つの(簡単な)方法の中で最も単純であり、最速で実行する必要があります。基本的に、クエリで各コンポーネント(日、月、年)を個別にフィルタリングする代わりに、単一の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 [〜#〜]は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列から月、日、年を引き出し、その抽出された値に対してthenフィルターを使用します「。このアプローチは、上記の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 ハイブリッド属性 は素晴らしいものです。データベースを変更せずにPython機能を透過的に適用できます。この特定のユースケースでは過剰すぎるかもしれませんが、彼らはあなたが望むものを達成するための第三の方法です。

基本的に、ハイブリッド属性は、データベースには実際には存在しないが、必要に応じてSQLAlchemyがデータベース列からオンザフライで計算できる「仮想列」と考えることができます。

特定の質問では、Paymentモデルで3つのハイブリッドプロパティ_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()
_

上記の処理は次のとおりです。

  1. すでに行っているようにPaymentモデルを定義しています。
  2. ただし、その後、_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_の結果を返します。
  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_year_、_due_date_month_、および_due_date_day_はすべて読み取り専用プロパティであると想定しています。もちろん、引数を受け入れる_@due_date_year.setter_を使用してカスタムセッターを定義することもできます_(self, value)_も)

結論として

これらの3つのアプローチのうち、最初のアプローチ(datetimeでのフィルタリング)は、理解するのが最も簡単で、実装するのが最も簡単で、最も速く実行されると思います。おそらくこれが最善の方法です。しかし、これら3つのアプローチの原則は非常に重要であり、SQLAlchemyを最大限に活用するのに役立つと思います。これが役立つことを願っています!

16