Jak zaktualizować wpis wiersza SQLAlchemy?

148

Zakładamy Stół ma trzy kolumny: username, passwordi no_of_logins.

Kiedy użytkownik próbuje się zalogować, jest sprawdzany pod kątem wpisu z zapytaniem, takim jak

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

Jeśli hasło się zgadza, idzie dalej. Chciałbym policzyć, ile razy zalogował się użytkownik. Dlatego po pomyślnym zalogowaniu chciałbym zwiększyć no_of_loginspole i zapisać je z powrotem w tabeli użytkowników. Nie jestem pewien, jak uruchomić kwerendę aktualizującą za pomocą SqlAlchemy.

webminal.org
źródło

Odpowiedzi:

141
user.no_of_logins += 1
session.commit()
Denis
źródło
15
stackoverflow.com/a/2334917/125507 mówi, że to zły sposób. A co, jeśli wiersz jeszcze nie istnieje?
endolith
9
Jak na łącze endolitu, user.no_of_logins += 1może stworzyć warunki wyścigu. Zamiast tego użyj user.no_of_logins = user.no_of_logins + 1. Tłumaczone na SQL drugi właściwy sposób staje: SET no_of_logins = no_of_logins + 1.
ChaimG,
7
@ChaimG Chyba miałeś na myśli user.no_of_logins = User.no_of_logins + 1, lub innymi słowy, użyj oprzyrządowanego atrybutu modelu do stworzenia wyrażenia SQL. Twój komentarz przedstawia 2 sposoby na stworzenie takiego samego stanu wyścigu.
Ilja Everilä
2
Oni nie są. Pierwsza ustawia atrybut na wyrażenie SQL, druga wykonuje w Pythonie dodatek w miejscu i ponownie wprowadza wyścig.
Ilja Everilä
1
@jayrizzo Nie jestem zaznajomiony z poziomem izolacji transakcji SERIALIZABLE, ale według mojego rozumienia pozwoliłoby to na wykonanie dodawania w Pythonie przy użyciu dodawania w miejscu. W przypadku wyścigu jedna z transakcji zakończy się sukcesem, a pozostałe zakończą się niepowodzeniem i będą musiały ponowić próbę (z nowym stanem bazy danych). Ale mogłem źle zrozumieć SERIALIZOWALNY.
Ilja Everilä
361

Istnieje kilka sposobów UPDATEkorzystania zsqlalchemy

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
źródło
3
Używam setattr(query_result, key, value)do niektórych aktualizacji w Flask SQLAlchemy, po których następuje zatwierdzenie. Czy jest jakiś powód, aby dyskontować ten wzór?
Marc
2
@datamafia: Możesz użyć setattr(query_result, key, value), co jest dokładnie równoważne z pisaniemquery_result.key = value
Nima Soroush
9
Czy można wyjaśnić różnice między tymi przypadkami? Dzięki!
Hatszepsut
1
@Hatshepsut Jedna różnica, na którą natknąłem się dzisiaj między 4 a 1/2, to: groups.google.com/forum/#!topic/sqlalchemy/wGUuAy27otM
Vikas Prasad
1
punkt numer 2 zawodzi, jeśli nie określisz obiektu klasy w zapytaniu (). To znaczy, ostatnie zapytanie tosession.query(User).filter(User.username == form.username.data).update({"no_of_logins": (User.no_of_logins +1)})
venkat
18

Przykłady wyjaśniające ważną kwestię w komentarzach zaakceptowanej odpowiedzi

Nie rozumiałem tego, dopóki sam się tym nie bawiłem, więc pomyślałem, że będą też inni, którzy będą zdezorientowani. Powiedzmy, że pracujesz nad użytkownikiem, którego id == 6i którego w no_of_logins == 30momencie rozpoczęcia.

# 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

Punkt

Odwołując się do klasy zamiast do instancji, możesz sprawić, że SQLAlchemy będzie mądrzejszy w zwiększaniu, sprawiając, że dzieje się to po stronie bazy danych zamiast po stronie Pythona. Robienie tego w ramach bazy danych jest lepsze, ponieważ jest mniej podatne na uszkodzenie danych (np. Dwóch klientów próbuje dokonać inkrementacji w tym samym czasie z wynikiem netto tylko jednej inkrementacji zamiast dwóch). Zakładam, że możliwe jest zwiększenie wartości w Pythonie, jeśli ustawisz blokady lub podniesiesz poziom izolacji, ale po co zawracać sobie głowę, jeśli nie musisz?

Zastrzeżenie

Jeśli zamierzasz zwiększać dwukrotnie za pomocą kodu, który generuje SQL SET no_of_logins = no_of_logins + 1, wtedy będziesz musiał zatwierdzić lub przynajmniej opróżnić między przyrostami, w przeciwnym razie otrzymasz tylko jeden przyrost w sumie:

# 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
źródło
Witam, dziękuję za odpowiedź, zamiast zmiennej int próbuję zaktualizować zmienną łańcuchową, jak to zrobić? Używam bazy danych sqlite, a zmienne, które chcę zmienić, znajdują się w current_user, przesyłając formularz
dani bilel
1
@danibilel Sprawdź dokumentację, która jest dość dokładna. W tej części samouczka omawiamy aktualizację pól typu string: docs.sqlalchemy.org/en/13/orm/… . W przeciwnym razie proponuję zadać nowe pytanie pokazujące konkretnie, na czym utknąłeś.
MarredCheese
Dzięki za link, po całym dniu szukania wiem, że mam problem z db.session.commit (), nie utrwala zmian w konsoli tak, jak powinien, znalazłem podobne pytania, ale nie udzielono odpowiedzi działa dla mnie, w rzeczywistej aplikacji internetowej jest jeszcze gorzej! zmiany w ogóle nie są zapisywane, przepraszam za długi komentarz, ale nie mogę dodać pytania ze względu na politykę strony, każda pomoc byłaby mile widziana.
dani bilel
5

Za pomocą user=User.query.filter_by(username=form.username.data).first()instrukcji uzyskasz określonego użytkownika w userzmiennej.

Teraz możesz zmienić wartość nowej zmiennej obiektowej jak user.no_of_logins += 1i zapisać zmiany sessionmetodą zatwierdzenia.

Nilesh
źródło
2

Napisałem telegram bota i mam problem z aktualizacją wierszy. Użyj tego przykładu, jeśli masz 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')

Dlaczego używać db.session.flush()? Dlatego >>> SQLAlchemy: Jaka jest różnica między flush () a commit ()?

Andrew Lutsyuk
źródło
8
Kolor jest całkowicie zbędny tuż przed zatwierdzeniem. Zatwierdzenie zawsze niejawnie opróżnia. Jak wiele mówi się w Q / A ty związane z: „ flush()zawsze jest nazywany jako część wywołanie commit()
Ilja Everilä