SQLAlchemyの行エントリを更新する方法は?


137

テーブルに3つの列usernameがあるpasswordとしno_of_loginsます。

ユーザーがログインしようとすると、次のようなクエリでエントリがチェックされます

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

パスワードが一致する場合、彼はさらに先へ進みます。私がやりたいのは、ユーザーがログインした回数を数えることです。したがって、彼が正常にログインしたときはいつでも、no_of_loginsフィールドをインクリメントしてユーザーテーブルに戻したいと思います。SqlAlchemyで更新クエリを実行する方法がわかりません。

回答:


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

12
stackoverflow.com/a/2334917/125507によると、これは悪い方法です。また、行がまだ存在しない場合はどうなりますか?
内部石2014

6
内部石のリンクに従って、user.no_of_logins += 1競合状態を作成できます。代わりにを使用してくださいuser.no_of_logins = user.no_of_logins + 1。後者の正しい方法はSQLに変換されますSET no_of_logins = no_of_logins + 1
ChaimG

5
@ChaimG user.no_of_logins = User.no_of_logins + 1つまり、つまり、モデルのインストルメント化された属性を使用してSQL式を生成したのでしょう。それはあなたのコメントが同じ競合状態を生成する2つの方法を表示するためです。
IljaEverilä2017

2
ではない。前者は属性をSQL式に設定し、後者はPythonでインプレース追加を実行し、再び競合を導入します。
IljaEverilä18年

1
@jayrizzo私はSERIALIZABLEトランザクション分離レベルについてあまり詳しくありませんが、私の理解では、インプレース追加を使用してPythonで追加を実行できます。競合がある場合、トランザクションの1つは成功し、他のトランザクションは失敗し、再試行する必要があります(DBの新しい状態で)。しかし、私はシリアライズ可能であると誤解したかもしれません。
IljaEverilä18年

343

UPDATE使用するにはいくつかの方法がありますsqlalchemy

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

3
setattr(query_result, key, value)はFlask SQLAlchemyのいくつかの更新に使用し、その後にコミットします。このパターンを割り引く理由はありますか?
マルク

2
@datamafia:を使用できますsetattr(query_result, key, value)。これは、書き込みとまったく同じですquery_result.key = value
Nima Soroush '25

Thx @NimaSoroush、それは私がやっていることであり、同等です。
マルク

8
これらのケースの違いを説明することは可能ですか?ありがとう!
ハトシェプスト2017年

1
私は間今日出くわしたことを一つの違い@Hatshepsut 41/2:であるgroups.google.com/forum/#!topic/sqlalchemy/wGUuAy27otM
ヴィカスプラサド

13

承認された回答のコメントで重要な問題を明確にする例

自分でいじってみないとわからなかったので、戸惑う人もいると思いました。あなたがだれで、いつあなたが始めたのかを考えているid == 6no_of_logins == 30しましょう。

# 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

ポイント

インスタンスの代わりにクラスを参照することで、SQLAlchemyをインクリメントについてより賢くし、Python側ではなくデータベース側でそれを行うことができます。データ破損の影響を受けにくいため、データベース内で実行することをお勧めします(たとえば、2つのクライアントが同時にインクリメントしようとすると、最終的に2つではなく1つしかインクリメントされません)。ロックを設定したり、分離レベルを上げたりすると、Pythonでインクリメントを行うことが可能だと思いますが、なぜそうする必要がないのでしょうか。

注意点

のようなSQLを生成するコードを介して2回インクリメントSET no_of_logins = no_of_logins + 1する場合は、コミットするか、少なくともインクリメント間でフラッシュする必要があります。そうしないと、合計で1つのインクリメントしか取得できません。

# 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

こんにちは、回答ありがとうございます。int変数の代わりに、文字列変数を更新しようとしています。どうすればよいですか?私はsqliteデータベースを使用していて、フォームを送信することで、変更したい変数がcurrent_userにあります
ダニビレル

1
@danibilelかなり詳細なドキュメントを確認してください。チュートリアルのこの部分では、文字列フィールドの更新について説明します:docs.sqlalchemy.org/en/13/orm/…。それ以外の場合は、具体的に何に行き詰まっているのかを示す新しい質問を投稿することをお勧めします。
MarredCheese

リンクをありがとう、一日中検索した後、問題はdb.session.commit()にあることがわかり、コンソールの変更が適切に保持されず、同様の質問が見つかりましたが、回答が提供されませんでした私のために働く、実際のウェブアプリではそれはさらに悪いです!変更はまったく保存されません。長いコメントで申し訳ありませんが、ウェブサイトのポリシーにより質問を追加できません。ご協力いただければ幸いです。
ダニビレル

4

user=User.query.filter_by(username=form.username.data).first()ステートメントの助けを借りて、user変数で指定されたユーザーを取得します。

これで、のように新しいオブジェクト変数の値をuser.no_of_logins += 1変更し、sessionのcommitメソッドで変更を保存できます。


2

電報ボットを作成しましたが、更新行に問題があります。モデルがある場合は、この例を使用してください

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

なぜ使用db.session.flush()?だからこそ>>> SQLAlchemy:flush()とcommit()の違いは何ですか?


7
フラッシュはコミットの直前に完全に冗長です。コミットは常に暗黙的にフラッシュされます。あなたがリンクしたQ / Aで多くのことが言われています:「flush()は常に呼び出しの一部として呼び出されますcommit()
IljaEverilä'23 / 02/23
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.