私は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
の列を日付でフィルタリングする最良の方法は何ですか?ご協力ありがとうございました。
SQLAlchemyは、Pythonで表現されたクエリをSQLに効果的に変換します。しかし、モデルを定義するときにColumn
に割り当てるデータ型に基づいて、比較的表面的なレベルでそれを行います。
これは、Pythonの_datetime.datetime
_ APIをそのDateTime
構造に必ずしも複製しないことを意味します-結局、これらの2つのクラスは非常に異なることを行うためのものです! (_datetime.datetime
_はPythonに日時機能を提供し、SQLAlchemyのDateTime
はSQL DATETIMEまたはTIMESTAMP列を処理していることをSQL変換ロジックに伝えます)。
しかし、心配しないでください!あなたがやろうとしていることを達成するためには、いくつかの異なる方法があり、それらのいくつかは非常に簡単です。私が思う最も簡単な3つは:
datetime
インスタンスを使用してフィルターを構築します。extract
コンストラクトをフィルターで使用します。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()
_
上記の処理は次のとおりです。
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)
_も)
これらの3つのアプローチのうち、最初のアプローチ(datetime
でのフィルタリング)は、理解するのが最も簡単で、実装するのが最も簡単で、最も速く実行されると思います。おそらくこれが最善の方法です。しかし、これら3つのアプローチの原則は非常に重要であり、SQLAlchemyを最大限に活用するのに役立つと思います。これが役立つことを願っています!