Как обновить запись строки SQLAlchemy?

139

Предположим , таблица имеет три колонки: username, passwordи no_of_logins.

Когда пользователь пытается войти в систему, он проверяет наличие записи с таким запросом, как

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

Если пароль совпадает, он продолжает. Я бы хотел подсчитать, сколько раз пользователь входил в систему. Таким образом, всякий раз, когда он успешно входит в систему, я хотел бы увеличить no_of_loginsполе и сохранить его обратно в пользовательскую таблицу. Я не уверен, как выполнить запрос на обновление с помощью SqlAlchemy.

webminal.org
источник

Ответы:

133
user.no_of_logins += 1
session.commit()
Денис
источник
12
stackoverflow.com/a/2334917/125507 говорит, что это плохой способ сделать это. А что, если строка еще не существует?
Эндолит
6
Согласно ссылке endolith, 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 способа создания одного и того же состояния гонки.
Ilja Everilä 01
2
Они не. Первый устанавливает атрибут в выражение SQL, второй выполняет добавление на месте в Python и снова вводит гонку.
Илья Эвериля
1
@jayrizzo Я не так хорошо знаком с уровнем изоляции транзакций SERIALIZABLE, но, насколько я понимаю, это позволит выполнять добавление в Python с использованием добавления на месте. Если есть гонка, тогда одна из транзакций будет успешной, а остальные завершатся ошибкой и должны будут повторить попытку (с новым состоянием БД). Но, возможно, я неправильно понял SERIALIZABLE.
Илья Эвериля
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 с последующей фиксацией. Есть ли причина отказаться от этого шаблона?
Marc
2
@datamafia: Вы можете использовать setattr(query_result, key, value), что в точности эквивалентно написаниюquery_result.key = value
Нима Соруш
Спасибо @NimaSoroush, это то, что я делаю, да и то же самое.
Марк
8
Можно ли объяснить различия между этими случаями? Спасибо!
Хатшепсут
1
@Hatshepsut Одно различие, с которым я столкнулся сегодня между 4 и 1/2, находится по адресу: groups.google.com/forum/#!topic/sqlalchemy/wGUuAy27otM
Vikas Prasad
13

Примеры для разъяснения важного вопроса в комментариях к принятому ответу

Я не понял этого, пока сам не поигрался с этим, поэтому я подумал, что будут и другие, которые тоже будут сбиты с толку. Допустим, вы работаете с пользователем, чей id == 6и чей no_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. Лучше делать это в базе данных, поскольку она менее уязвима для повреждения данных (например, два клиента пытаются выполнить приращение одновременно с чистым результатом только одного приращения вместо двух). Я предполагаю, что в Python можно сделать приращение, если вы установите блокировки или повысите уровень изоляции, но зачем беспокоиться, если вам не нужно?

Предостережение

Если вы собираетесь увеличивать дважды с помощью кода, который производит SQL-подобное SET no_of_logins = no_of_logins + 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
MarredCheese
источник
Здравствуйте, спасибо за ваш ответ, вместо переменной 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метода фиксации.

Nilesh
источник
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
Флеш полностью избыточен непосредственно перед фиксацией. Коммит всегда неявно сбрасывается. Как много сказано в flush()commit()
вопросе /