¿Cómo actualizar la entrada de fila SQLAlchemy?

137

Asumir tabla tiene tres columnas: username, passwordy no_of_logins.

Cuando el usuario intenta iniciar sesión, se verifica si hay una entrada con una consulta como

user = User.query.filter_by(username=form.username.data).first()

Si la contraseña coincide, él continúa. Lo que me gustaría hacer es contar cuántas veces el usuario inició sesión. Por lo tanto, cada vez que inicie sesión con éxito, me gustaría incrementar el no_of_loginscampo y almacenarlo de nuevo en la tabla de usuarios. No estoy seguro de cómo ejecutar la consulta de actualización con SqlAlchemy.

webminal.org
fuente

Respuestas:

132
user.no_of_logins += 1
session.commit()
Denis
fuente
12
stackoverflow.com/a/2334917/125507 dice que esta es una mala manera de hacerlo. Además, ¿qué pasa si la fila aún no existe?
endolito el
66
Según el enlace del endolito, user.no_of_logins += 1puede crear condiciones de carrera. En lugar de usar user.no_of_logins = user.no_of_logins + 1. Traducido a sql la forma correcta este último se convierte en: SET no_of_logins = no_of_logins + 1.
ChaimG
55
@ChaimG Supongo que te referías user.no_of_logins = User.no_of_logins + 1, o en otras palabras, usa el atributo instrumentado del modelo para producir una expresión SQL. Como es, su comentario muestra 2 formas de producir la misma condición de carrera.
Ilja Everilä
2
Ellos no son. El primero establece el atributo en una expresión SQL, el segundo realiza una adición in situ en Python y nuevamente presenta la carrera.
Ilja Everilä
1
@jayrizzo No estoy tan familiarizado con el nivel de aislamiento de transacciones SERIALIZABLE, pero a mi entender, permitiría realizar la adición en Python usando la adición en el lugar. Si hay una carrera, una de las transacciones tendrá éxito y las demás fallarán y deberán volver a intentarlo (con el nuevo estado de la base de datos). Pero podría haber entendido mal SERIALIZABLE.
Ilja Everilä
343

Hay varias formas de UPDATEusarsqlalchemy

1) user.no_of_logins += 1
   session.commit()

2) session.query().\
       filter(User.username == form.username.data).\
       update({"no_of_logins": (User.no_of_logins +1)})
   session.commit()

3) conn = engine.connect()
   stmt = User.update().\
       values(no_of_logins=(User.no_of_logins + 1)).\
       where(User.username == form.username.data)
   conn.execute(stmt)

4) setattr(user, 'no_of_logins', user.no_of_logins+1)
   session.commit()
Nima Soroush
fuente
3
Utilizo setattr(query_result, key, value)algunas actualizaciones en Flask SQLAlchemy seguido de un commit. ¿Alguna razón para descontar este patrón?
Marc
2
@datamafia: Puedes usar setattr(query_result, key, value), que es exactamente equivalente a escribirquery_result.key = value
Nima Soroush
Thx @NimaSoroush, eso es lo que hago y sí equivalente.
Marc
8
¿Es posible explicar las diferencias entre estos casos? ¡Gracias!
Hatshepsut
1
@Hatshepsut Una diferencia que encontré hoy entre 4 y 1/2 está en: groups.google.com/forum/#!topic/sqlalchemy/wGUuAy27otM
Vikas Prasad
13

Ejemplos para aclarar la cuestión importante en los comentarios de las respuestas aceptadas

No lo entendí hasta que jugué con él, así que pensé que habría otros que también estarían confundidos. Digamos que está trabajando en el usuario cuyo id == 6y no_of_logins == 30cuándo comienza.

# 1 (bad)
user.no_of_logins += 1
# result: UPDATE user SET no_of_logins = 31 WHERE user.id = 6

# 2 (bad)
user.no_of_logins = user.no_of_logins + 1
# result: UPDATE user SET no_of_logins = 31 WHERE user.id = 6

# 3 (bad)
setattr(user, 'no_of_logins', user.no_of_logins + 1)
# result: UPDATE user SET no_of_logins = 31 WHERE user.id = 6

# 4 (ok)
user.no_of_logins = User.no_of_logins + 1
# result: UPDATE user SET no_of_logins = no_of_logins + 1 WHERE user.id = 6

# 5 (ok)
setattr(user, 'no_of_logins', User.no_of_logins + 1)
# result: UPDATE user SET no_of_logins = no_of_logins + 1 WHERE user.id = 6

El punto

Al hacer referencia a la clase en lugar de la instancia, puede hacer que SQLAlchemy sea más inteligente sobre el incremento, haciendo que suceda en el lado de la base de datos en lugar del lado de Python. Es mejor hacerlo dentro de la base de datos, ya que es menos vulnerable a la corrupción de datos (por ejemplo, dos clientes intentan aumentar al mismo tiempo con un resultado neto de solo un incremento en lugar de dos). Supongo que es posible hacer el incremento en Python si establece bloqueos o aumenta el nivel de aislamiento, pero ¿por qué molestarse si no tiene que hacerlo?

Una advertencia

Si va a incrementar dos veces a través de un código que produce SQL como SET no_of_logins = no_of_logins + 1, entonces necesitará confirmar o al menos vaciar entre incrementos, de lo contrario solo obtendrá un incremento en total:

# 6 (bad)
user.no_of_logins = User.no_of_logins + 1
user.no_of_logins = User.no_of_logins + 1
session.commit()
# result: UPDATE user SET no_of_logins = no_of_logins + 1 WHERE user.id = 6

# 7 (ok)
user.no_of_logins = User.no_of_logins + 1
session.flush()
# result: UPDATE user SET no_of_logins = no_of_logins + 1 WHERE user.id = 6
user.no_of_logins = User.no_of_logins + 1
session.commit()
# result: UPDATE user SET no_of_logins = no_of_logins + 1 WHERE user.id = 6
Queso Marred
fuente
Hola, gracias por su respuesta, en lugar de una variable int que estoy tratando de actualizar una variable de cadena, ¿cómo hago eso? Estoy usando una base de datos sqlite y las variables que quiero cambiar están en current_user, mediante el envío de un formulario
dani bilel
1
@danibilel Consulte la documentación, que es bastante exhaustiva. Esta parte del tutorial trata sobre la actualización de los campos de cadena: docs.sqlalchemy.org/en/13/orm/… . De lo contrario, le sugiero que publique una nueva pregunta que muestre en qué se atasca específicamente.
MarredCheese
Gracias por el enlace, después de pasar todo el día buscando, sé que mi problema es con db.session.commit (), no persiste los cambios en la consola como debería, encontré preguntas similares pero no respondí. trabaja para mí, ¡en la aplicación web real es aún peor! los cambios no se guardan en absoluto, lo siento por el largo comentario, pero no puedo agregar una pregunta debido a la política del sitio web, agradecería cualquier ayuda.
Dani Bilel
4

Con la ayuda de la user=User.query.filter_by(username=form.username.data).first()declaración obtendrá el usuario especificado en uservariable.

Ahora puede cambiar el valor de la nueva variable de objeto como user.no_of_logins += 1y guardar los cambios con el sessionmétodo de confirmación.

Nilesh
fuente
2

Escribí telegram bot y tengo algún problema con las filas de actualización. Use este ejemplo, si tiene Modelo

def update_state(chat_id, state):
    try:
        value = Users.query.filter(Users.chat_id == str(chat_id)).first()
        value.state = str(state)
        db.session.flush()
        db.session.commit()
        #db.session.close()
    except:
        print('Error in def update_state')

¿Por qué usar db.session.flush()? Es por eso que >>> SQLAlchemy: ¿Cuál es la diferencia entre flush () y commit ()?

Andrew Lutsyuk
fuente
77
La descarga es completamente redundante justo antes de la confirmación. Un commit siempre se vacía implícitamente. Tanto se dice en el Q / A al que se vinculó: " flush()siempre se llama como parte de una llamada a commit()"
Ilja Everilä