Je crée un site Web Flask qui implique de suivre les paiements, et j'ai rencontré un problème où je n'arrive pas vraiment à filtrer l'un de mes modèles de base de données par date.
Par exemple, si c'est à quoi ressemble ma table:
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
et je veux le filtrer de manière à obtenir toutes les lignes après le 20 juillet, ou toutes les lignes en août, etc.
Je peux penser à un moyen grossier et brutal de filtrer tous les paiements et ALORS parcourir la liste pour filtrer par mois/année, mais je préfère rester à l'écart de ces méthodes.
C'est mon modèle de paiement db:
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)
Et c'est moi qui essaie de filtrer Payment
par date:
Payment.query.filter(Payment.due_date.month == today.month, Payment.due_date.year == today.year, Payment.due_date.day >= today.day).all()
où today
est simplement datetime.today()
.
J'ai supposé que la colonne due_date
Aurait tous les attributs DateTime lorsque je l'appellerais (par exemple .month
), Mais il semble que j'avais tort.
Quelle est la meilleure façon de filtrer les colonnes de Payment
par date? Merci de votre aide.
SQLAlchemy traduit efficacement votre requête exprimée en Python en SQL. Mais il le fait à un niveau relativement superficiel, en fonction du type de données que vous affectez au Column
lors de la définition de votre modèle .
Cela signifie qu'il ne répliquera pas nécessairement l'API datetime.datetime
De Python sur sa construction DateTime
- après tout, ces deux classes sont censées faire des choses très différentes! (datetime.datetime
Fournit une fonctionnalité datetime à Python, tandis que DateTime
de SQLAlchemy indique à sa logique de traduction SQL qu'il s'agit d'une colonne SQL DATETIME ou TIMESTAMP).
Mais ne t'inquiète pas! Il y a plusieurs façons différentes de réaliser ce que vous essayez de faire, et certaines d'entre elles sont super faciles. Les trois plus faciles, je pense, sont:
datetime
, plutôt que ses composants (jour, mois, année).extract
de SQLAlchemy dans votre filtre.datetime
C'est le plus simple des trois moyens (faciles) de réaliser ce que vous essayez, et il devrait également être le plus rapide. Fondamentalement, au lieu d'essayer de filtrer séparément chaque composant (jour, mois, année) dans votre requête, utilisez simplement une seule valeur datetime
.
Fondamentalement, les éléments suivants devraient être équivalents à ce que vous essayez de faire dans votre requête ci-dessus:
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()
Maintenant, payments
devrait être tous les paiements dont la date d'échéance survient après le début (heure 00:00:00) de la date actuelle de votre système.
Si vous voulez devenir plus compliqué, comme filtrer les paiements effectués au cours des 30 derniers jours. Vous pouvez le faire avec le code suivant:
from datetime import datetime, timedelta
filter_after = datetime.today() - timedelta(days = 30)
payments = Payment.query.filter(Payment.due_date >= filter_after).all()
Vous pouvez combiner plusieurs cibles de filtre en utilisant and_
Et or_
. Par exemple, pour retourner des paiements dus au cours des 30 derniers jours [~ # ~] et [~ # ~] étaient dus il y a plus de 15 ans, vous peut utiliser:
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()
L'astuce ici - de votre point de vue - est de construire correctement vos instances cible de filtre datetime
avant d'exécuter votre requête.
extract
L'expression extract
de SQLAlchemy (documentée ici ) est utilisée pour exécuter une instruction SQL EXTRACT
, qui est de savoir comment en SQL vous pouvez extraire un mois, un jour ou une année d'un Valeur DATETIME/TIMESTAMP.
En utilisant cette approche, SQLAlchemy indique à votre base de données SQL "d'abord, retirez le mois, le jour et l'année de ma colonne DATETIME et puis filtrez cette valeur extraite ". Sachez que cette approche sera plus lente que le filtrage sur une valeur datetime
comme décrit ci-dessus. Mais voici comment cela fonctionne:
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 Attributs hybrides sont des choses merveilleuses. Ils vous permettent d'appliquer de manière transparente la fonctionnalité Python sans modifier votre base de données. Je soupçonne que pour ce cas d'utilisation spécifique, ils pourraient être exagérés, mais ils sont un troisième moyen d'atteindre ce que vous voulez.
Fondamentalement, vous pouvez considérer les attributs hybrides comme des "colonnes virtuelles" qui n'existent pas réellement dans votre base de données, mais que SQLAlchemy peut calculer à la volée à partir de vos colonnes de base de données lorsque cela est nécessaire.
Dans votre question spécifique, nous définirions trois propriétés hybrides: due_date_day
, due_date_month
, due_date_year
Dans votre modèle Payment
. Voici comment cela fonctionnerait:
... 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()
Voici ce que fait ce qui précède:
Payment
comme vous le faites déjà.due_date_year
, due_date_month
Et due_date_day
. En utilisant due_date_year
Comme exemple, il s'agit d'un attribut d'instance qui fonctionne sur les instances de votre classe Payment
. Cela signifie que lorsque vous exécutez one_of_my_payments.due_date_year
, La propriété extrait la valeur due_date
De l'instance Python. Parce que tout cela se passe dans Python (c'est-à-dire sans toucher à votre base de données), il fonctionnera sur l'objet datetime.datetime
déjà traduit que SQLAlchemy a stocké dans votre instance. Et il renverra le résultat de due_date.year
.@due_date_year.expression
. Ce décorateur indique à SQLAlchemy que lorsqu'il traduit des références à due_date_year
En expressions SQL, il doit le faire comme défini dans cette méthode. Ainsi, l'exemple ci-dessus indique à SQLAlchemy "si vous devez utiliser due_date_year
Dans une expression SQL, alors extract('year', Payment.due_date)
est la façon dont due_date_year
Doit être exprimé.(Remarque: l'exemple ci-dessus suppose que due_date_year
, due_date_month
et due_date_day
sont tous des propriétés en lecture seule. Vous pouvez bien sûr également définir des paramètres personnalisés à l'aide de @due_date_year.setter
qui accepte également les arguments (self, value)
)
De ces trois approches, je pense que la première approche (filtrage sur datetime
) est à la fois la plus facile à comprendre, la plus facile à implémenter et la plus performante. C'est probablement la meilleure façon de procéder. Mais les principes de ces trois approches sont très importants et je pense que cela vous aidera à tirer le meilleur parti de SQLAlchemy. J'espère que cela s'avère utile!