http://en.wikipedia.org/wiki/Upsert
Wstaw aktualizację przechowywaną proc na SQL Server
Czy jest jakiś sprytny sposób na zrobienie tego w SQLite, o którym nie myślałem?
Zasadniczo chcę zaktualizować trzy z czterech kolumn, jeśli rekord istnieje, jeśli nie istnieje, chcę WSTAWić rekord z wartością domyślną (NUL) dla czwartej kolumny.
Identyfikator jest kluczem podstawowym, więc UPSERT będzie miał tylko jeden rekord.
(Próbuję uniknąć narzutu SELECT, aby ustalić, czy muszę oczywiście ZAKTUALIZOWAĆ, czy WSTAWIĆ)
Propozycje?
Nie mogę potwierdzić tej składni na stronie SQLite dla TABLE CREATE. Nie zbudowałem wersji demo, aby ją przetestować, ale wydaje się, że nie jest obsługiwana.
Gdyby tak było, mam trzy kolumny, więc faktycznie wyglądałoby to tak:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
Blob1 BLOB ON CONFLICT REPLACE,
Blob2 BLOB ON CONFLICT REPLACE,
Blob3 BLOB
);
ale pierwsze dwa obiekty BLOB nie spowodują konfliktu, tylko identyfikator byłby więc więc asusme Blob1 i Blob2 nie zostaną zastąpione (zgodnie z życzeniem)
AKTUALIZACJE w SQLite, gdy powiązanie danych jest kompletną transakcją, co oznacza, że każdy wysłany wiersz do aktualizacji wymaga: Przygotuj / Powiąż / Krok / Zakończ instrukcje w przeciwieństwie do INSERT, który pozwala na użycie funkcji resetowania
Życie obiektu instrukcji przebiega mniej więcej tak:
- Utwórz obiekt za pomocą sqlite3_prepare_v2 ()
- Powiąż wartości z parametrami hosta za pomocą interfejsów sqlite3_bind_.
- Uruchom SQL, wywołując sqlite3_step ()
- Zresetuj instrukcję za pomocą sqlite3_reset (), a następnie wróć do kroku 2 i powtórz.
- Zniszcz obiekt instrukcji za pomocą narzędzia sqlite3_finalize ().
AKTUALIZACJA Zgaduję, że jest powolny w porównaniu do INSERT, ale jak to się ma do SELECT przy użyciu klucza podstawowego?
Być może powinienem użyć polecenia select, aby odczytać czwartą kolumnę (Blob3), a następnie użyć REPLACE, aby napisać nowy rekord mieszający oryginalną czwartą kolumnę z nowymi danymi dla pierwszych 3 kolumn?
Odpowiedzi:
Zakładając trzy kolumny w tabeli: ID, NAZWA, ROLA
ZŁE: Spowoduje to wstawienie lub zastąpienie wszystkich kolumn nowymi wartościami dla ID = 1:
ŹLE: Spowoduje to wstawienie lub zastąpienie 2 kolumn ... kolumna NAME zostanie ustawiona na NULL lub wartość domyślna:
DOBRY : Użyj klauzuli konfliktu SQLite Obsługa UPSERT w SQLite! Składnia UPSERT została dodana do SQLite w wersji 3.24.0!
UPSERT to specjalny dodatek do składni INSERT, który powoduje, że INSERT zachowuje się jak AKTUALIZACJA lub nie działa, jeśli INSERT naruszy ograniczenie związane z unikalnością. UPSERT nie jest standardowym SQL. UPSERT w SQLite jest zgodny ze składnią ustaloną przez PostgreSQL.
DOBRY, ale delikatny: zaktualizuje 2 kolumny. Gdy ID = 1 istnieje, NAZWA nie ulegnie zmianie. Gdy ID = 1 nie istnieje, nazwa będzie domyślna (NULL).
Spowoduje to zaktualizowanie 2 kolumn. Gdy istnieje identyfikator = 1, nie ma to wpływu na rolę ROLE. Gdy ID = 1 nie istnieje, rola zostanie ustawiona na „Ogrzewacz stołowy” zamiast wartości domyślnej.
źródło
INSERT OR REPLACE
podając wartości dla wszystkich kolumn.WSTAW LUB WYMIANA NIE JEST równoważne z „UPSERT”.
Powiedzmy, że mam tabelę Pracownik z polami id, nazwa i rola:
Boom, straciłeś nazwisko pracownika numer 1. SQLite zastąpił go wartością domyślną.
Oczekiwanym wyjściem UPSERT byłaby zmiana roli i zachowanie nazwy.
źródło
coalesce((select ..), 'new value')
klauzuli osadzonej . Myślę, że odpowiedź Erica wymaga więcej głosów.Odpowiedź Erica B jest OK, jeśli chcesz zachować tylko jedną lub dwie kolumny z istniejącego wiersza. Jeśli chcesz zachować wiele kolumn, staje się zbyt kłopotliwy.
Oto podejście, które dobrze skaluje się do dowolnej liczby kolumn po obu stronach. Aby to zilustrować, przyjmuję następujący schemat:
Zwróć uwagę w szczególności, że
name
jest to naturalny klucz wiersza -id
jest używany tylko dla kluczy obcych, więc chodzi o to, aby SQLite sam wybrał wartość identyfikatora podczas wstawiania nowego wiersza. Ale aktualizując istniejący wiersz na jego podstawiename
, chcę, aby nadal miał starą wartość identyfikatora (oczywiście!).Osiągam prawdę
UPSERT
dzięki następującym konstrukcjom:Dokładna forma tego zapytania może się nieco różnić. Kluczem jest użycie
INSERT SELECT
z lewym łączeniem zewnętrznym, aby połączyć istniejący wiersz z nowymi wartościami.Tutaj, jeśli wiersz wcześniej nie istniał,
old.id
będzie,NULL
a SQLite automatycznie przypisze identyfikator, ale jeśli już taki wiersz był,old.id
będzie miał rzeczywistą wartość i zostanie on ponownie wykorzystany. Właśnie tego chciałem.W rzeczywistości jest to bardzo elastyczne. Zwróć uwagę, że
ts
kolumna jest kompletnie brakująca ze wszystkich stron - ponieważ maDEFAULT
wartość, SQLite i tak zrobi właściwą rzecz w każdym przypadku, więc nie muszę się tym zajmować.Można również dołączyć kolumnę na obu
new
iold
po bokach, a następnie użyć na przykładCOALESCE(new.content, old.content)
w zewnętrznejSELECT
powiedzieć „Włóż nowe treści, jeśli nie było w ogóle, inaczej zachować starą treść” - np jeśli używasz stałego zapytania i są wiążące nowa wartości z symbolami zastępczymi.źródło
WHERE name = "about"
ograniczenie,SELECT ... AS old
aby przyspieszyć. Jeśli masz 1m + wierszy, jest to bardzo wolne.WHERE
klauzuli wymaga po prostu tego rodzaju redundancji w zapytaniu, którego starałem się w pierwszej kolejności uniknąć, kiedy wymyśliłem takie podejście. Jak zawsze: kiedy potrzebujesz wydajności, denormalizuj - w tym przypadku strukturę zapytania.INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
ON DELETE
wyzwalaczy podczas przeprowadzania zamiany (czyli aktualizacji)?ON DELETE
wyzwalacze. Nie wiem o niepotrzebnie. Dla większości użytkowników byłoby to prawdopodobnie niepotrzebne, a nawet niepożądane, ale może nie dla wszystkich użytkowników. Podobnie z tego powodu, że kasuje również kaskadowo wszelkie wiersze z kluczami obcymi do danego wiersza - prawdopodobnie problem dla wielu użytkowników. Niestety, SQLite nie ma nic bliższego prawdziwemu UPSERT. (INSTEAD OF UPDATE
ChybaJeśli generalnie wykonujesz aktualizacje, zrobiłbym ...
Jeśli ogólnie robisz wstawki, zrobiłbym to
W ten sposób unikasz wyboru i jesteś transakcyjnie zdrowy na Sqlite.
źródło
Ta odpowiedź została zaktualizowana, więc poniższe komentarze nie mają już zastosowania.
2018-05-18 STOP PRESS.
Obsługa UPSERT w SQLite!Składnia UPSERT została dodana do SQLite w wersji 3.24.0 (w toku)!
UPSERT to specjalny dodatek do składni INSERT, który powoduje, że INSERT zachowuje się jak AKTUALIZACJA lub nie działa, jeśli INSERT naruszy ograniczenie związane z unikalnością. UPSERT nie jest standardowym SQL. UPSERT w SQLite jest zgodny ze składnią ustaloną przez PostgreSQL.
alternatywnie:
Innym całkowicie odmiennym sposobem jest: w mojej aplikacji ustawiam w wierszu memoryID wartość long.MaxValue podczas tworzenia wiersza w pamięci. (MaxValue nigdy nie zostanie użyty jako identyfikator, którego nie będziesz żył wystarczająco długo ... Zatem jeśli rowID nie jest taką wartością, to musi już znajdować się w bazie danych, więc potrzebuje UPDATE, jeśli jest MaxValue, wówczas wymaga wstawienia. Jest to przydatne tylko wtedy, gdy możesz śledzić rowIDs w swojej aplikacji.
źródło
WHERE
Klauzula nie może być dodawana doINSERT
rachunku: sqlite-wkładki ...Zdaję sobie sprawę, że to stary wątek, ale ostatnio pracowałem w sqlite3 i opracowałem tę metodę, która lepiej odpowiada moim potrzebom dynamicznego generowania sparametryzowanych zapytań:
Nadal są 2 zapytania z klauzulą where w aktualizacji, ale wydaje się, że załatwia sprawę. Mam też w głowie tę wizję, że sqlite może całkowicie zoptymalizować instrukcję aktualizacji, jeśli wywołanie change () jest większe od zera. Niezależnie od tego, czy tak się dzieje, czy nie, ale człowiek może śnić, prawda? ;)
W przypadku punktów bonusowych możesz dołączyć tę linię, która zwraca identyfikator wiersza, niezależnie od tego, czy jest to nowo wstawiony wiersz, czy istniejący wiersz.
źródło
Update <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
Oto rozwiązanie, które naprawdę jest UPSERT (UPDATE lub INSERT) zamiast INSERT OR REPLACE (który działa inaczej w wielu sytuacjach).
Działa to tak:
1. Spróbuj zaktualizować, jeśli istnieje rekord o tym samym identyfikatorze.
2. Jeśli aktualizacja nie zmieniła żadnych wierszy (
NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
), następnie wstaw rekord.Tak więc albo istniejący rekord został zaktualizowany, albo zostanie wstawione.
Ważnym szczegółem jest użycie funkcji SQL zmian () do sprawdzenia, czy instrukcja aktualizacji trafiła do istniejących rekordów i wykonanie instrukcji insert tylko, jeśli nie trafiła do żadnego rekordu.
Należy wspomnieć, że funkcja zmiany () nie zwraca zmian wykonanych przez wyzwalacze niższego poziomu (patrz http://sqlite.org/lang_corefunc.html#changes ), więc pamiętaj o tym.
Oto SQL ...
Aktualizacja testowa:
Wkładka testowa:
źródło
INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;
powinno również działać.Począwszy od wersji 3.24.0 UPSERT jest obsługiwany przez SQLite.
Z dokumentacji :
Źródło obrazu: https://www.sqlite.org/images/syntax/upsert-clause.gif
źródło
Rzeczywiście możesz zrobić wstawkę w SQLite, wygląda ona nieco inaczej niż zwykle. Wyglądałoby to tak:
źródło
Rozwijając odpowiedź Arystotelesa, możesz WYBRAĆ z obojętnego stołu „singleton” (stół własnego dzieła z jednym rzędem). Pozwala to uniknąć powielania.
Zachowałem również przykład przenośny między MySQL i SQLite i wykorzystałem kolumnę „data_added” jako przykład tego, jak można ustawić kolumnę tylko za pierwszym razem.
źródło
Najlepszym podejściem, jakie znam, jest aktualizacja, a następnie wstawienie. „Narzut zaznaczenia” jest konieczny, ale nie jest to straszne obciążenie, ponieważ szukasz klucza podstawowego, który jest szybki.
Powinieneś być w stanie zmodyfikować poniższe instrukcje za pomocą nazw tabel i pól, aby robić to, co chcesz.
źródło
Jeśli ktoś chce przeczytać moje rozwiązanie dla SQLite w Cordova, dostałem tę ogólną metodę js dzięki powyższej odpowiedzi @david.
Więc najpierw wybierz nazwy kolumn za pomocą tej funkcji:
Następnie zbuduj transakcje programowo.
„Wartości” to tablica, którą powinieneś zbudować wcześniej i reprezentuje ona wiersze, które chcesz wstawić lub zaktualizować w tabeli.
„remoteid” to identyfikator, którego użyłem jako odniesienia, ponieważ synchronizuję się z moim zdalnym serwerem.
Aby skorzystać z wtyczki SQLite Cordova, zapoznaj się z oficjalnym linkiem
źródło
Ta metoda remiksuje kilka innych metod z odpowiedzi na to pytanie i obejmuje użycie CTE (Common Table Expressions). Przedstawię zapytanie, a następnie wyjaśnię, dlaczego zrobiłem to, co zrobiłem.
Chciałbym zmienić nazwisko pracownika 300 na DAVIS, jeśli istnieje pracownik 300. W przeciwnym razie dodam nowego pracownika.
Nazwa tabeli: pracownicy Kolumny: id, imię, nazwisko
Zapytanie to:
Zasadniczo użyłem CTE, aby zmniejszyć liczbę przypadków, gdy instrukcja select musi być użyta do ustalenia wartości domyślnych. Ponieważ jest to CTE, po prostu wybieramy kolumny, które chcemy z tabeli, a instrukcja INSERT używa tego.
Teraz możesz zdecydować, jakie wartości domyślne chcesz zastosować, zastępując wartości null w funkcji COALESCE odpowiednimi wartościami.
źródło
Podążając za Arystotelesem Pagaltzisem i pomysłem
COALESCE
z odpowiedzi Erica B , tutaj jest opcja upsert, aby zaktualizować tylko kilka kolumn lub wstawić pełny wiersz, jeśli nie istnieje.W takim przypadku wyobraź sobie, że tytuł i treść powinny zostać zaktualizowane, zachowując pozostałe stare wartości, gdy istnieją i wstawiając dostarczone, gdy nie znaleziono nazwy:
UWAGA
id
jest wymuszone, aby mieć wartość NULL, gdyINSERT
ma to być autowzrost. Jeśli jest to tylko wygenerowany klucz podstawowy,COALESCE
można go również użyć (patrz komentarz Arystotelesa Pagaltza ).Tak więc ogólna zasada brzmi: jeśli chcesz zachować stare wartości, użyj
COALESCE
, gdy chcesz zaktualizować wartości, użyjnew.fieldname
źródło
COALESCE(old.id, new.id)
jest zdecydowanie nie tak z kluczem autoinkrementacji. I chociaż „nie zmieniaj większości wiersza bez zmian, z wyjątkiem przypadków, w których brakuje wartości”, brzmi to jak przypadek użycia, który ktoś może faktycznie mieć, nie sądzę, że ludzie tego szukają, gdy szukają sposobu na wykonanie UPSERT.old
tabeli, do której zostały przypisaneNULL
, a nie wartości podane wnew
. To jest powód do użyciaCOALESCE
. Nie jestem ekspertem od sqlite, testowałem to zapytanie i wydaje się, że działa w tej sprawie, bardzo dziękuję, jeśli możesz wskazać mi rozwiązanie z autoinkrementamiNULL
jako klucz, ponieważ to mówi SQLite, aby zamiast tego wstawił następną dostępną wartość.Myślę, że może to być to, czego szukasz: klauzula ON CONFLICT .
Jeśli zdefiniujesz swoją tabelę w ten sposób:
Teraz, jeśli wykonasz INSERT z identyfikatorem, który już istnieje, SQLite automatycznie wykonuje UPDATE zamiast INSERT.
Hth ...
źródło
REPLACE
oświadczenia.Jeśli nie masz nic przeciwko robieniu tego w dwóch operacjach.
Kroki:
1) Dodaj nowe elementy za pomocą „INSERT OR IGNORE”
2) Zaktualizuj istniejące elementy za pomocą „UPDATE”
Dane wejściowe do obu kroków to ta sama kolekcja nowych lub aktualizowanych elementów. Działa dobrze z istniejącymi elementami, które nie wymagają zmian. Będą aktualizowane, ale z tymi samymi danymi, a zatem wynik netto nie będzie żadnych zmian.
Jasne, wolniejsze itp. Nieefektywne. Tak.
Łatwo napisać sql, utrzymać i zrozumieć? Zdecydowanie.
To kompromis do rozważenia. Działa świetnie w przypadku małych upserts. Działa świetnie dla tych, którym nie przeszkadza poświęcenie wydajności w celu utrzymania kodu.
źródło
Po przeczytaniu tego wątku i rozczarowaniu, że nie było łatwo przejść do tego „UPSERT”, zbadałem dalej ...
Możesz to zrobić bezpośrednio i łatwo w SQLITE.
Zamiast używać:
INSERT INTO
Posługiwać się:
INSERT OR REPLACE INTO
To robi dokładnie to, co chcesz!
źródło
INSERT OR REPLACE
nie jest anUPSERT
. Zobacz „odpowiedź” gregschloma, z jakiego powodu. Rozwiązanie Erica B faktycznie działa i wymaga pewnych pozytywnych opinii.gdyby
COUNT(*) = 0
inaczej jeśli
COUNT(*) > 0
źródło