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 ....
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.
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.
I just noticed the post above immediately displayed as posted 1 hour ago . . . I'm on GMT (London).
Me again! But after posting the second post, the first changed to "2 minutes ago", the second started at "0 seconds ago"!
Then after the third post all three now say "1 hour" . . . had to wait 5 minutes to post this one :-)
Thank you for reporting this. I am still working on many parts of this website ... will fix this soon.
Recientes
- Don't Repeat Yourself (DRY) con Jinja2
- SQLAlchemy, PostgreSQL, número máximo de filas por user
- Mostrar los valores en filtros dinámicos SQLAlchemy
- Transferencia de datos segura con cifrado de Public Key y pyNaCl
- rqlite: una alternativa de alta disponibilidad y dist distribuida SQLite
- ¿Debo migrar mi Docker Swarm a Kubernetes?
Más vistos
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados
- Usando PyInstaller y Cython para crear un ejecutable de Python
- Flask RESTful API validación de parámetros de solicitud con esquemas Marshmallow