Muszę wykonać UPSERT / INSERT LUB UPDATE w bazie danych SQLite.
Istnieje polecenie WSTAW LUB ZAMIEŃ, które w wielu przypadkach może być przydatne. Ale jeśli chcesz zachować swój identyfikator z autoincrement na miejscu z powodu kluczy obcych, to nie działa, ponieważ usuwa wiersz, tworzy nowy, aw konsekwencji nowy wiersz ma nowy identyfikator.
To byłaby tabela:
gracze - (klucz główny na id, unikalna nazwa_użytkownika)
| id | user_name | age |
------------------------------
| 1982 | johnny | 23 |
| 1983 | steven | 29 |
| 1984 | pepee | 40 |
db.execSQL("insert into bla(id,name) values (?,?) on conflict(id) do update set name=?")
. Daje mi błąd składniowy w słowie „wł.”Styl pytań i odpowiedzi
Cóż, po godzinach badań i walki z tym problemem, dowiedziałem się, że są dwa sposoby na osiągnięcie tego, w zależności od struktury twojego stołu i czy masz włączone ograniczenia kluczy obcych, aby zachować integralność. Chciałbym podzielić się tym w przejrzystym formacie, aby zaoszczędzić trochę czasu ludziom, którzy mogą być w mojej sytuacji.
Opcja 1: Możesz sobie pozwolić na usunięcie wiersza
Innymi słowy, nie masz klucza obcego lub jeśli go masz, silnik SQLite jest skonfigurowany tak, aby nie było żadnych wyjątków integralności. Najlepszą drogą jest WSTAWIĆ LUB WYMIENIĆ . Jeśli próbujesz wstawić / zaktualizować odtwarzacz, którego identyfikator już istnieje, silnik SQLite usunie ten wiersz i wstawi podane przez Ciebie dane. Teraz pojawia się pytanie: co zrobić, aby stary identyfikator był powiązany?
Powiedzmy, że chcemy UPSERT z danymi user_name = 'steven' i age = 32.
Spójrz na ten kod:
Sztuczka polega na połączeniu. Zwraca identyfikator użytkownika „steven”, jeśli taki istnieje, w przeciwnym razie zwraca nowy, świeży identyfikator.
Opcja 2: Nie możesz sobie pozwolić na usunięcie wiersza
Po małpowaniu z poprzednim rozwiązaniem zdałem sobie sprawę, że w moim przypadku może to skończyć się zniszczeniem danych, ponieważ ten identyfikator działa jako klucz obcy dla innej tabeli. Poza tym stworzyłem tabelę z klauzulą ON DELETE CASCADE , co oznaczałoby, że po cichu usuwałoby dane. Niebezpieczny.
Tak więc najpierw pomyślałem o klauzuli IF, ale SQLite ma tylko CASE . A tego CASE nie można użyć (a przynajmniej mi się to nie udało) do wykonania jednej AKTUALIZACJI zapytania jeśli ISTNIEJE (wybierz identyfikator z graczy, gdzie nazwa_użytkownika = 'steven') i WSTAWIĆ, jeśli nie. Nie idź.
A potem, w końcu, z powodzeniem użyłem brutalnej siły. Logika jest taka, że dla każdego UPSERT , który chcesz wykonać, najpierw wykonaj INSERT LUB IGNORUJ aby upewnić się, że jest wiersz z naszym użytkownikiem, a następnie wykonaj zapytanie UPDATE z dokładnie tymi samymi danymi, które próbujesz wstawić.
Te same dane co poprzednio: user_name = 'steven' i age = 32.
I to wszystko!
EDYTOWAĆ
Jak skomentował Andy, próba wstawienia najpierw, a następnie aktualizacji może prowadzić do wyzwalania wyzwalaczy częściej niż oczekiwano. Nie jest to moim zdaniem kwestia bezpieczeństwa danych, ale prawdą jest, że odpalanie niepotrzebnych zdarzeń ma niewielki sens. Dlatego ulepszonym rozwiązaniem byłoby:
źródło
Oto podejście, które nie wymaga brutalnej siły „ignoruj”, która działałaby tylko w przypadku naruszenia klucza. W ten sposób działa w oparciu o wszelkie warunki określone w aktualizacji.
Spróbuj tego...
Jak to działa
„Magiczny sos” jest używany
Changes()
wWhere
klauzuli.Changes()
reprezentuje liczbę wierszy, na które ma wpływ ostatnia operacja, którą w tym przypadku jest aktualizacja.W powyższym przykładzie, jeśli nie ma zmian od aktualizacji (tj. Rekord nie istnieje), to
Changes()
= 0, więcWhere
klauzula wInsert
instrukcji przyjmuje wartość prawda i wstawiany jest nowy wiersz z określonymi danymi.Jeśli
Update
nie aktualizacji istniejącego wiersza, aChanges()
= 1 (albo dokładniej, nie zero, jeśli więcej niż jeden rząd został zaktualizowany), więc „gdzie” klauzulaInsert
teraz ma wartość false, a tym samym nie odbędzie się wkładka.Piękno tego polega na tym, że nie jest potrzebna żadna brutalna siła, ani niepotrzebne usuwanie, a następnie ponowne wstawianie danych, co może spowodować zepsucie kolejnych kluczy w relacjach kluczy obcych.
Ponadto, ponieważ jest to tylko standardowa
Where
klauzula, może opierać się na wszystkim, co zdefiniujesz, a nie tylko na kluczowych naruszeniach. Podobnie możesz używaćChanges()
w połączeniu z czymkolwiek innym, czego chcesz / potrzebujesz, wszędzie tam, gdzie dozwolone są wyrażenia.źródło
Changes() = 0
zwróci false i dwa wiersze spowodują WSTAWIENIE LUB WYMIENIENIEUPSERT
w pierwszej kolejności? Ale mimo to dobrze się stało, że aktualizacja się wydarzy, ustawienieChanges=1
lub w przeciwnym razieINSERT
instrukcja zostanie nieprawidłowo uruchomiona, czego nie chcesz.Problem ze wszystkimi przedstawionymi odpowiedziami polega na całkowitym braku uwzględnienia wyzwalaczy (i prawdopodobnie innych skutków ubocznych). Rozwiązanie jak
prowadzi do wykonania obu wyzwalaczy (do wstawienia, a następnie do aktualizacji), gdy wiersz nie istnieje.
Właściwym rozwiązaniem jest
w takim przypadku wykonywana jest tylko jedna instrukcja (jeśli wiersz istnieje lub nie).
źródło
UPDATE OR IGNORE
jest to konieczne, ponieważ aktualizacja nie ulegnie awarii, jeśli nie zostaną znalezione żadne wiersze.Aby mieć czysty UPSERT bez dziur (dla programistów), które nie polegają na unikalnych i innych kluczach:
Funkcja SELECT changes () zwróci liczbę aktualizacji wykonanych w ostatnim zapytaniu. Następnie sprawdź, czy wartość zwracana przez changes () wynosi 0, jeśli tak, wykonaj:
źródło
Możesz także po prostu dodać klauzulę ON CONFLICT REPLACE do unikalnego ograniczenia nazwa_użytkownika, a następnie po prostu WSTAWIĆ, pozostawiając SQLite, aby dowiedzieć się, co zrobić w przypadku konfliktu. Zobacz: https://sqlite.org/lang_conflict.html .
Zwróć także uwagę na zdanie dotyczące wyzwalaczy usuwania: gdy strategia rozwiązywania konfliktów REPLACE usuwa wiersze w celu spełnienia ograniczenia, wyzwalacze usuwania są uruchamiane wtedy i tylko wtedy, gdy są włączone wyzwalacze rekurencyjne.
źródło
Opcja 1: Wstaw -> Aktualizuj
Jeśli chcesz uniknąć obu
changes()=0
iINSERT OR IGNORE
nawet jeśli nie możesz sobie pozwolić na usunięcie wiersza - możesz użyć tej logiki;Najpierw włóż (jeśli nie istnieje), a następnie zaktualizuj , filtrując za pomocą unikalnego klucza.
Przykład
Odnośnie wyzwalaczy
Uwaga: nie testowałem tego, aby zobaczyć, które wyzwalacze są wywoływane, ale zakładam że:
jeśli wiersz nie istnieje
jeśli wiersz istnieje
Opcja 2: Włóż lub wymień - zachowaj własny identyfikator
w ten sposób możesz mieć jedno polecenie SQL
Edycja: dodana opcja 2.
źródło