angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

SQLAlchemy calculs côté serveur datetime

Pourquoi vous devriez essayer d'éviter les datetime calculs côté client à SQLAlchemy moins que...

24 juin 2019
Dans SQLAlchemy
post main image
Original photo unsplash.com/@nputra.

Vous trouverez de nombreux exemples de SQLAlchemy datetime calcul utilisant par exemple la timedelta fonction s. PythonPourquoi ? Je ne comprends pas cela, sauf que c'est facile. Mais est-ce correct ?

Supposons que nous voulons que tous les enregistrements utilisateur ou objets créés il y a deux heures et la définition de l'enregistrement / objet est :

class User(Base):

    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    created_on = Column(DateTime, server_default=func.now(), index=True)
    email = Column(String(100), server_default='', index=True)

Ensuite, si nous pouvions utiliser Python pour sélectionner les enregistrements / objets ajoutés au cours des 10 dernières minutes, nous pourrions faire quelque chose comme ceci :

from datetime import datetime, timedelta

now = datetime.now()
two_hours_ago = now - timedelta(hours=2)

# return all users created less then 2 hours ago
db.query(User).filter(User.created_on > two_hours_ago).all()

Le généré est :

SELECT user.id AS user_id, user.created_on AS user_created_on, user.email AS user_email
FROM user 
WHERE user.created_on > %(created_on_1)s
INFO sqlalchemy.engine.base.Engine {'created_on_1': datetime.datetime(2019, 6, 25, 7, 31, 58, 630959)}

Cela ne fonctionne, donne des résultats valides que si :

  • le serveur de la base de données est exécuté sur le même serveur que celui sur lequel le Python code est exécuté
  • le serveur de base de données fonctionne sur un serveur différent du serveur de Python code et le temps sur les deux serveurs est parfaitement synchronisé.

Supposons que vous avez un serveur de base de données séparé et que le temps de ce serveur est de 2 minutes désynchronisé. Alors vous vous trompez, les résultats sont incomplets. J'écris des requêtes côté serveur depuis de nombreuses années et je suis surpris qu'il y ait peu d'attention pour cela dans les SQLAlchemy questions et réponses.

La seule façon d'obtenir les bons résultats est d'utiliser les datetime timbres des enregistrements du serveur de base de données et d'y ajouter datetime ou d'en soustraire datetime des données. Avec MariaDB / MySQL vous pouvez utiliser l'instruction d'intervalle :

SELECT user.* FROM user WHERE created_on > (NOW() - INTERVAL 2 HOUR)

Malheureusement, je n'ai pas pu trouver une solution SQLAlchemy qui serait valable pour toutes les bases de données. SQLAlchemy a l' text() objet, il passe la valeur à la requête. Avec text(), la SQLAlchemy requête devient :

from sqlalchemy import text

two_hours_ago = text('NOW() - INTERVAL 2 HOURS')

# return all users created less then 2 hours ago
db.query(User).filter(User.created_on > two_hours_ago).all()

Le généré est :

SELECT user.id AS user_id, user.created_on AS user_created_on, user.email AS user_email
FROM user 
WHERE user.created_on > NOW() - INTERVAL 2 HOUR

Sachez que cette requête peut ne pas fonctionner sur tous les systèmes de base de données. Il fonctionne avec MariaDB / MySQL mais il ne fonctionne certainement pas avec SQLite.

Si vous développez une application et exécutez tout sur un seul ordinateur, gardez toujours à l'esprit qu'à l'avenir vous voudrez peut-être exécuter la base de données sur un serveur séparé. Ce n'est donc pas une mauvaise idée de développer vos requêtes pour cette situation.

Liens / crédits

Flask-sqlalchemy query datetime intervals
https://stackoverflow.com/questions/30495935/flask-sqlalchemy-query-datetime-intervals

SQLAlchemy datetime operations on server side
https://stackoverflow.com/questions/12540175/sqlalchemy-datetime-operations-on-server-side

SQLAlchemy default DateTime
https://stackoverflow.com/questions/13370317/sqlalchemy-default-datetime

Using DATEADD in sqlalchemy
https://stackoverflow.com/questions/15572292/using-dateadd-in-sqlalchemy/15573750#15573750

En savoir plus...

SQLAlchemy

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires (5)

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.

avatar

Another great post. I have to agree it is surprising most solutions on the web ignore the potential Python app versus DBMS clock difference problem. The kind of bug that waits patiently until its time!
FWIW `sqlalchemy.sql.expression.func.now() - timedelta(minutes=2)` seems to work for Postgres and I would expect others with a NOW() function.

avatar
Visiteur anonyme (non connecté) 3 ans il y a Visiteur anonyme (non connecté)

I just noticed the post above immediately displayed as posted 1 hour ago . . . I'm on GMT (London).

avatar
Visiteur anonyme (non connecté) 3 ans il y a Visiteur anonyme (non connecté)

Me again! But after posting the second post, the first changed to "2 minutes ago", the second started at "0 seconds ago"!

avatar
Visiteur anonyme (non connecté) 3 ans il y a Visiteur anonyme (non connecté)

Then after the third post all three now say "1 hour" . . . had to wait 5 minutes to post this one :-)

avatar
peter 3 ans il y a Visiteur anonyme (non connecté)

Thank you for reporting this. I am still working on many parts of this website ... will fix this soon.