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

SQLAlchemy datetime cálculos del lado del servidor

Por qué debería tratar de evitar los cálculos del lado del cliente datetime a menos SQLAlchemy que ....

24 junio 2019
post main image
Original photo unsplash.com/@nputra.

Encontrará muchos ejemplos de SQLAlchemy datetime cálculos usando, por ejemplo, la timedelta función Python's. Por qué? No lo entiendo, excepto que esto es fácil. ¿Pero es correcto?

Supongamos que queremos todos los registros de usuario u objetos creados hace dos horas y la definición de registro/objeto es:

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)

Entonces si pudiéramos usar Python para seleccionar los registros/objetos añadidos en los últimos 10 minutos, podríamos hacer algo como esto:

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()

El generado SQL es:

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)}

Esto sólo funciona, da resultados válidos, si:

  • el servidor de base de datos se ejecuta en el mismo servidor en el que se ejecuta el Python código
  • el servidor de base de datos se ejecuta en un servidor diferente del servidor de Python código y la hora en ambos servidores está perfectamente sincronizada.

Suponga que tiene un servidor de base de datos separado y el tiempo de este servidor es de 2 minutos fuera de sincronización. Entonces te equivocas, resultados incompletos. He estado escribiendo consultas del lado del servidor durante muchos años y me sorprende que haya poca atención para esto en SQLAlchemy preguntas y respuestas.

La única manera de obtener los resultados correctos es utilizando los datetime sellos de los registros del servidor de la base de datos y sumando datetime o restando datetime de ellos. Con MariaDB / MySQL puede utilizar la sentencia de intervalos:

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

Desafortunadamente no pude encontrar una solución para SQLAlchemy que sea válida para todas las bases de datos. SQLAlchemy tiene el text() objeto, pasa el valor a la consulta. Con text(), la SQLAlchemy consulta se convierte en:

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()

El generado SQL es:

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

Tenga en cuenta que esta consulta puede no funcionar en todos los sistemas de bases de datos. Funciona con MariaDB / MySQL pero ciertamente no funciona con SQLite.

Si usted desarrolla una aplicación y ejecuta todo en un solo ordenador, tenga siempre en cuenta que en el futuro puede que desee ejecutar la base de datos en un servidor separado. Por lo tanto, no es una mala idea desarrollar sus consultas sobre esta situación.

Enlaces / créditos

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

Leer más

SQLAlchemy

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios (5)

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.

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
Visitante anónimo (no conectado) 3 años hace Visitante anónimo (no conectado)

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

avatar
Visitante anónimo (no conectado) 3 años hace Visitante anónimo (no conectado)

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

avatar
Visitante anónimo (no conectado) 3 años hace Visitante anónimo (no conectado)

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

avatar
peter 3 años hace Visitante anónimo (no conectado)

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