Como atualizar a entrada da linha SQLAlchemy?

137

Suponha tabela tem três colunas: username, passwordeno_of_logins .

Quando o usuário tenta fazer login, é verificada uma entrada com uma consulta como

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

Se a senha corresponder, ele prosseguirá. O que eu gostaria de fazer é contar quantas vezes o usuário efetuou login. Assim, sempre que ele efetua login com êxito, eu gostaria de incrementar o no_of_loginscampo e armazená-lo na tabela de usuários. Não sei como executar a consulta de atualização com o SqlAlchemy.

webminal.org
fonte

Respostas:

132
user.no_of_logins += 1
session.commit()
Denis
fonte
12
stackoverflow.com/a/2334917/125507 diz que esta é uma má maneira de fazê-lo. E se a linha ainda não existir?
endolith
6
Conforme o link do endólito, user.no_of_logins += 1pode criar condições de corrida. Em vez disso, use user.no_of_logins = user.no_of_logins + 1. Traduzido para sql a maneira correta última torna-se: SET no_of_logins = no_of_logins + 1.
ChaimG
5
@ChaimG Eu acho que você quis dizer user.no_of_logins = User.no_of_logins + 1, ou em outras palavras, use o atributo instrumentado do modelo para produzir uma expressão SQL. Como é o seu comentário, exibe duas maneiras de produzir a mesma condição de corrida.
Ilja Everilä
2
Eles não são. O primeiro define o atributo como uma expressão SQL, o último executa uma adição no local no Python e novamente introduz a corrida.
Ilja Everilä 28/09
1
@jayrizzo Não estou familiarizado com o nível de isolamento de transação SERIALIZABLE, mas, pelo que entendi, isso permitiria realizar a adição no Python usando a adição no local. Se houver uma corrida, uma das transações será bem-sucedida e as outras falharão e devem tentar novamente (com o novo estado do DB). Mas eu poderia ter entendido mal SERIALIZABLE.
Ilja Everilä 28/09
343

Existem várias maneiras 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
fonte
3
Uso setattr(query_result, key, value)para algumas atualizações no Flask SQLAlchemy, seguidas por uma confirmação. Algum motivo para descontar esse padrão?
Marc
2
@datamafia: Você pode usar setattr(query_result, key, value), que é exatamente equivalente à escritaquery_result.key = value
Nima Soroush
Thx @NimaSoroush, é isso que eu faço e sim equivalente.
Marc
8
É possível explicar as diferenças entre esses casos? Obrigado!
Hatshepsut
1
@Hatshepsut Uma diferença que me deparei hoje entre 4 e 1/2 está em: groups.google.com/forum/#!topic/sqlalchemy/wGUuAy27otM
Vikas Prasad
13

Exemplos para esclarecer a questão importante nos comentários da resposta aceita

Eu não entendi até brincar comigo mesma, então imaginei que haveria outros que também estavam confusos. Digamos que você esteja trabalhando no usuário de quem id == 6e de quem no_of_logins == 30quando iniciar.

# 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

O ponto

Ao referenciar a classe em vez da instância, você pode fazer com que o SQLAlchemy seja mais inteligente em incrementar, fazendo com que isso aconteça no lado do banco de dados e não no lado do Python. Fazê-lo no banco de dados é melhor, pois é menos vulnerável à corrupção de dados (por exemplo, dois clientes tentam incrementar ao mesmo tempo com um resultado líquido de apenas um incremento em vez de dois). Presumo que seja possível fazer o incremento no Python se você definir bloqueios ou aumentar o nível de isolamento, mas por que se preocupar se não for necessário?

Uma ressalva

Se você pretende incrementar duas vezes por meio de um código que produz SQL SET no_of_logins = no_of_logins + 1, precisará confirmar ou, pelo menos, liberar entre os incrementos; caso contrário, você receberá apenas um incremento no 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
MarredCheese
fonte
Olá, obrigado pela resposta, em vez de uma variável int, estou tentando atualizar uma variável de string, como faço isso? Eu estou usando um banco de dados sqlite e as variáveis ​​que eu quero alterar estão no current_user, através do envio de um formulário
dani bilel
1
@danibilel Confira a documentação, que é bastante completa. Esta parte do tutorial aborda a atualização dos campos de string: docs.sqlalchemy.org/en/13/orm/… . Caso contrário, sugiro que você publique uma nova pergunta mostrando o que você está preso especificamente.
MarredCheese
Obrigado pelo link, depois de passar o dia inteiro pesquisando, sei que meu problema é com db.session.commit (), ele não persiste as alterações no console como deveria, encontrei perguntas semelhantes, mas as respostas fornecidas não trabalhe para mim, no aplicativo da Web real é ainda pior! as alterações não são salvas, desculpe pelo longo comentário, mas não posso adicionar uma pergunta por causa da política do site, qualquer ajuda seria apreciada.
dani bilel
4

Com a ajuda de user=User.query.filter_by(username=form.username.data).first() instrução, você obterá o usuário especificado emuser variável

Agora você pode alterar o valor da nova variável de objeto user.no_of_logins += 1e salvar as alterações com o sessionmétodo de confirmação do.

Nilesh
fonte
2

Eu escrevi bot de telegrama e tenho algum problema com as linhas de atualização. Use este exemplo, se você tiver 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')

Por que usar db.session.flush()? É por isso que >>> SQLAlchemy: Qual é a diferença entre flush () e commit ()?

Andrew Lutsyuk
fonte
7
O flush é totalmente redundante antes do commit. Um commit sempre liberta implicitamente. O mesmo é dito no Q / A ao qual você vinculou: " flush()é sempre chamado como parte de uma chamada para commit()"
Ilja Everilä