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


139

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

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

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

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

Ответы:


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

Здравствуйте, спасибо за ваш ответ, вместо переменной 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метода фиксации.


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()
вопросе /
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.