Comment mettre à jour l'entrée de ligne SQLAlchemy?

139

Supposons que le tableau comporte trois colonnes: username, passwordet no_of_logins.

Lorsque l'utilisateur essaie de se connecter, il recherche une entrée avec une requête telle que

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

Si le mot de passe correspond, il continue. Ce que je voudrais faire, c'est compter le nombre de fois que l'utilisateur s'est connecté. Ainsi, chaque fois qu'il se connecte avec succès, je voudrais incrémenter le no_of_loginschamp et le stocker dans la table des utilisateurs. Je ne sais pas comment exécuter une requête de mise à jour avec SqlAlchemy.

webminal.org
la source

Réponses:

133
user.no_of_logins += 1
session.commit()
Denis
la source
12
stackoverflow.com/a/2334917/125507 dit que c'est une mauvaise façon de le faire. Et si la ligne n'existe pas encore?
endolith
6
Selon le lien de l'endolithe, user.no_of_logins += 1peut créer des conditions de course. Utilisez plutôt user.no_of_logins = user.no_of_logins + 1. Traduit en sql cette dernière manière correcte devient: SET no_of_logins = no_of_logins + 1.
ChaimG le
5
@ChaimG Je suppose que vous vouliez dire user.no_of_logins = User.no_of_logins + 1, ou en d'autres termes, utilisez l'attribut instrumenté du modèle pour produire une expression SQL. Tel quel, votre commentaire affiche 2 façons de produire la même condition de concurrence.
Ilja Everilä
2
Ils ne sont pas. Le premier définit l'attribut sur une expression SQL, le second effectue un ajout sur place en Python et introduit à nouveau la race.
Ilja Everilä
1
@jayrizzo Je ne suis pas très familier avec le niveau d'isolation des transactions SERIALIZABLE, mais à ma connaissance, cela permettrait d'effectuer l'ajout en Python à l'aide de l'ajout sur place. S'il y a une course, l'une des transactions réussira alors et les autres échoueront et devront réessayer (avec le nouvel état de la base de données). Mais j'ai peut-être mal compris SERIALIZABLE.
Ilja Everilä
343

Il existe plusieurs façons d' UPDATEutilisersqlalchemy

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
la source
3
J'utilise setattr(query_result, key, value)pour certaines mises à jour de Flask SQLAlchemy suivies d'un commit. Une raison d'écarter ce modèle?
Marc
2
@datamafia: Vous pouvez utiliser setattr(query_result, key, value), ce qui équivaut exactement à l'écriturequery_result.key = value
Nima Soroush
Thx @NimaSoroush, c'est ce que je fais et oui équivalent.
Marc
8
Est-il possible d'expliquer les différences entre ces cas? Merci!
Hatshepsut
1
@Hatshepsut Une différence que j'ai rencontrée aujourd'hui entre le 4 et le 1/2 est à l' adresse
Vikas Prasad
13

Exemples pour clarifier la question importante dans les commentaires des réponses acceptées

Je ne l'ai pas compris jusqu'à ce que j'aie joué avec moi-même, alors j'ai pensé qu'il y en aurait d'autres qui seraient également confus. Supposons que vous travaillez sur l'utilisateur dont id == 6et dont no_of_logins == 30vous démarrez.

# 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

Le point

En référençant la classe au lieu de l'instance, vous pouvez rendre SQLAlchemy plus intelligent dans l'incrémentation, en faisant en sorte que cela se produise du côté de la base de données au lieu du côté Python. Le faire dans la base de données est mieux car il est moins vulnérable à la corruption des données (par exemple, deux clients tentent d'incrémenter en même temps avec un résultat net d'un seul incrément au lieu de deux). Je suppose qu'il est possible de faire l'incrémentation en Python si vous définissez des verrous ou augmentez le niveau d'isolement, mais pourquoi vous embêter si vous n'êtes pas obligé?

Une mise en garde

Si vous allez incrémenter deux fois via du code qui produit du type SQL SET no_of_logins = no_of_logins + 1, vous devrez alors valider ou au moins vider entre les incréments, sinon vous n'obtiendrez qu'un seul incrément au 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
MarredFromage
la source
Bonjour, merci pour votre réponse, au lieu d'une variable int, j'essaie de mettre à jour une variable de chaîne, comment puis-je faire cela? J'utilise une base de données sqlite et les variables que je veux changer sont dans current_user, en soumettant un formulaire
dani bilel
1
@danibilel Consultez la documentation, qui est assez complète. Cette partie du didacticiel traite de la mise à jour des champs de chaîne: docs.sqlalchemy.org/en/13/orm/… . Sinon, je vous suggère de poster une nouvelle question montrant ce sur quoi vous êtes spécifiquement coincé.
MarredCheese
Merci pour le lien, après avoir passé toute la journée à chercher, je sais que mon problème est avec db.session.commit (), il ne persiste pas les changements dans la console comme il se doit, j'ai trouvé des questions similaires mais fourni des réponses non travaille pour moi, dans l'application Web actuelle, c'est encore pire! les modifications ne sont pas enregistrées du tout, désolé pour le long commentaire mais je ne peux pas ajouter de question en raison de la politique du site Web, toute aide serait appréciée.
dani bilel
4

Avec l'aide de la user=User.query.filter_by(username=form.username.data).first()déclaration, vous obtiendrez l'utilisateur spécifié dans la uservariable.

Vous pouvez maintenant changer la valeur de la nouvelle variable objet comme user.no_of_logins += 1et enregistrer les modifications avec la sessionméthode de commit de.

Nilesh
la source
2

J'ai écrit un robot de télégramme et j'ai des problèmes avec les lignes de mise à jour. Utilisez cet exemple, si vous avez Model

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

Pourquoi utiliser db.session.flush()? C'est pourquoi >>> SQLAlchemy: Quelle est la différence entre flush () et commit ()?

Andrew Lutsyuk
la source
7
Le vidage est entièrement redondant juste avant la validation. Un commit vide toujours implicitement. Comme on le dit dans le Q / R flush()commit()
auquel