PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak uwzględnić wykluczone wiersze w RETURNING z INSERT ... ON CONFLICT

Otrzymany błąd:

Polecenie ON CONFLICT DO UPDATE nie może wpłynąć na wiersz po raz drugi

wskazuje, że próbujesz przesunąć ten sam wiersz więcej niż raz w jednym poleceniu. Innymi słowy:masz duplikaty na (name, url, email) w Twoich VALUES lista. Złóż duplikaty (jeśli jest taka opcja) i powinno działać. Ale będziesz musiał zdecydować, który wiersz wybrać z każdego zestawu duplikatów.

INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM  (
   VALUES
   ('blah', 'blah', 'blah', 'blah', 'blah')
   -- ... more
   ) v(created, modified, name, url, email)  -- match column list
ON     CONFLICT (name, url, email) DO UPDATE
SET    url = feeds_person.url
RETURNING id;

Ponieważ używamy wolnostojących VALUES wyrażenie teraz musisz dodać jawne rzutowania typu dla typów innych niż domyślne. Na przykład:

VALUES
    (timestamptz '2016-03-12 02:47:56+01'
   , timestamptz '2016-03-12 02:47:56+01'
   , 'n3', 'u3', 'e3')
   ...

Twój timestamptz kolumny wymagają jawnego rzutowania typu, podczas gdy typy ciągów mogą działać z domyślnym text . (Nadal możesz rzutować na varchar(n) od razu.)

Istnieją sposoby na określenie, który wiersz wybrać z każdego zestawu duplikatów:

  • Wybrać pierwszy wiersz w każdej grupie GROUP BY?

Masz rację, nie ma (obecnie) sposobu na wykluczenie wiersze w RETURNING klauzula. Cytuję Postgres Wiki:

Zwróć uwagę, że RETURNING nie uwidacznia „EXCLUDED.* alias z UPDATE (tylko ogólne „TARGET.* " alias jest tam widoczny). Uważa się, że spowoduje to irytującą niejednoznaczność dla tych prostych, powszechnych przypadków [30] z niewielką lub żadną korzyścią. W pewnym momencie w przyszłości możemy poszukać sposobu na ujawnienie, jeśliRETURNING -projected krotki zostały wstawione i zaktualizowane, ale prawdopodobnie nie musi to zrobić w pierwszej zatwierdzonej iteracji funkcji [31].

Jednak , nie należy aktualizować wierszy, które nie powinny być aktualizowane. Puste aktualizacje są prawie tak samo drogie jak zwykłe aktualizacje – i mogą mieć niezamierzone skutki uboczne. Na początku nie potrzebujesz koniecznie UPSERT, twoja sprawa wygląda bardziej jak „WYBIERZ lub WSTAW”. Powiązane:

  • Czy SELECT lub INSERT w funkcji podatnej na wyścigi?

Jeden czystszy sposób wstawiania zestawu wierszy byłby z CTE modyfikującymi dane:

WITH val AS (
   SELECT DISTINCT ON (name, url, email) *
   FROM  (
      VALUES 
      (timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
    , ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
      -- more (type cast only needed in 1st row)
      ) v(created, modified, name, url, email)
   )
, ins AS (
   INSERT INTO feeds_person (created, modified, name, url, email)
   SELECT created, modified, name, url, email FROM val
   ON     CONFLICT (name, url, email) DO NOTHING
   RETURNING id, name, url, email
   )
SELECT 'inserted' AS how, id FROM ins  -- inserted
UNION  ALL
SELECT 'selected' AS how, f.id         -- not inserted
FROM   val v
JOIN   feeds_person f USING (name, url, email);

Dodatkowa złożoność powinna opłacać się za duże tabele, w których INSERT jest regułą i SELECT wyjątek.

Pierwotnie dodałem NOT EXISTS predykat na ostatnim SELECT aby uniknąć duplikatów w wyniku. Ale to było zbędne. Wszystkie CTE pojedynczego zapytania wyświetlają te same migawki tabel. Zestaw zwrócony z ON CONFLICT (name, url, email) DO NOTHING wyklucza się wzajemnie w zestawie zwróconym po INNER JOIN na tych samych kolumnach.

Niestety otwiera to również małe okno na sytuację wyścigową . Jeśli...

  • jednoczesna transakcja wstawia sprzeczne wiersze
  • jeszcze nie popełnił zobowiązania
  • ale ostatecznie zobowiązuje

... niektóre wiersze mogą zostać utracone.

Możesz po prostu INSERT .. ON CONFLICT DO NOTHING , po którym następuje oddzielny SELECT zapytanie dla wszystkich wierszy - w ramach tej samej transakcji, aby rozwiązać ten problem. Co z kolei otwiera kolejne małe okno na sytuację wyścigową czy współbieżne transakcje mogą zatwierdzać zapisy do tabeli między INSERT i SELECT (domyślnie READ COMMITTED poziom izolacji). Można tego uniknąć za pomocą REPEATABLE READ izolacja transakcji (lub bardziej rygorystyczna). Lub z (prawdopodobnie kosztowną lub nawet niedopuszczalną) blokadą zapisu na całym stole. Możesz uzyskać dowolne zachowanie, ale może to być cena do zapłacenia.

Powiązane:

  • Jak używać RETURNING z ON CONFLICT w PostgreSQL?
  • Zwróć wiersze z INSERT z ON CONFLICT bez konieczności aktualizacji



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Czy istnieje coś takiego jak funkcja zip() w PostgreSQL, która łączy dwie tablice?

  2. Przegląd różnych węzłów planu pomocniczego w PostgreSQL

  3. Samodzielne dostarczanie kont użytkowników w PostgreSQL za pośrednictwem nieuprzywilejowanego dostępu anonimowego

  4. Heroku Postgres:Za dużo połączeń. Jak zabić te połączenia?

  5. Postgres Query Plan, dlaczego szacowanie wierszy jest tak błędne