2016-01-13 46 views
15

Niezapisywalny CTE zostały uznane za rozwiązanie upsert przed do 9,5 tak jak opisano w Insert, on duplicate update in PostgreSQL?Jak sprawdzić, czy uaktualnienie było aktualizacją z PostgreSQL 9.5+ UPSERT?

Jest to możliwe do przeprowadzenia upsert z informacją, czy zakończył się jako aktualizację albo wkładka z następującym zapisujące CTE idiomu:

WITH 
    update_cte AS (
     UPDATE t SET v = $1 WHERE id = $2 RETURNING 'updated'::text status 
    ), 
    insert_cte AS (
     INSERT INTO t(id, v) SELECT $2, $1 WHERE NOT EXISTS 
      (SELECT 1 FROM update_cte) RETURNING 'inserted'::text status 
    ) 
(SELECT status FROM update_cte) UNION (SELECT status FROM insert_cte) 

To zapytanie zwróci albo „zaktualizowane” lub „wstawiony”, albo może (rzadko) nie z naruszenia ograniczenia w sposób opisany w https://dba.stackexchange.com/questions/78510/why-is-cte-open-to-lost-updates

czy coś podobnego można osiągnąć przy użyciu PostgreSQL 9.5+ nowego „upsert” składnia, benefi z jego optymalizacji i uniknięcia możliwego naruszenia ograniczeń?

Odpowiedz

6

Czerpiąc z @lad2025's answer, wynik można osiągnąć przez nadużycie settings i customized options wi th related functions w klauzulach WHERE, aby uzyskać wymagany efekt uboczny.

CREATE TABLE t(id INT PRIMARY KEY, v TEXT); 

INSERT INTO t (id, v) 
    SELECT $1, $2 
    WHERE 'inserted' = set_config('upsert.action', 'inserted', true) 
    ON CONFLICT (id) DO UPDATE 
     SET v = EXCLUDED.v 
     WHERE 'updated' = set_config('upsert.action', 'updated', true) 
RETURNING current_setting('upsert.action') AS "upsert.action"; 

Trzecim parametrem set_config jest is_local: true oznacza ustawienie odejdzie po zakończeniu transakcji. Dokładniej, current_setting('upsert.action') zwróci wartość NULL (i nie wygeneruje błędu) do końca sesji.

+0

Dobra sztuczka **** – lad2025

+5

O mój Boże, to brzydkie! To powoduje, że moja wizja się zaciera. Zdecydowanie nie chcę tego pamiętać, nie mówiąc już o zastosowaniu! ~ – Patrick

+0

możesz to zrobić za pomocą 'xmax :: text :: int> 0' - bez ustawienia poziomu transakcji (co jest rzeczywiście oryginalnym hackiem) –

4

W SQL ServerMERGE oświadczenie ma $action, który zwraca ciąg 'INSERT', 'UPDATE', or 'DELETE'.

Dla Postgresql Nie mogę znaleźć funkcji/zmiennej, która robi podobne rzeczy dla RETURNING.

Jednym ze sposobów, aby to obejść jest dodanie kolumny is_updated do stolika:

DROP TABLE IF EXISTS tab; 

CREATE TABLE tab(id INT PRIMARY KEY, col VARCHAR(100), 
       is_updated BOOLEAN DEFAULT false); 
INSERT INTO tab(id, col) VALUES (1,'a'), (2, 'b'); 


-- main query 
INSERT INTO tab(id, col) 
VALUES (3, 'c'), (4, 'd'), (1,'aaaa') 
ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col, is_updated = true 
RETURNING id,col, 
      CASE WHEN is_updated THEN 'UPDATED' ELSE 'INSERTED' END AS action; 

Rextester Demo

wyjściowa:

╔════╦══════╦══════════╗ 
║ id ║ col ║ action ║ 
╠════╬══════╬══════════╣ 
║ 3 ║ c ║ INSERTED ║ 
║ 4 ║ d ║ INSERTED ║ 
║ 1 ║ aaaa ║ UPDATED ║ 
╚════╩══════╩══════════╝ 
+0

Gdzie jest VALUES (2, 'b') na wyjściu? – kometen

+0

@kometen Nie ma takiej potrzeby. Istnieje wcześniej. Dlaczego chcesz zwrócić całą tabelę? Tylko rekordy wstawione/zaktualizowane – lad2025

+0

Ale (2, "b") również jest wstawiane. I is_updated domyślnie przyjmuje wartość false. Pomyślałem więc, że pojawi się jako INSERTED. – kometen

1

wierzę xmax::text::int > 0 byłby najłatwiejszy trick:

so=# DROP TABLE IF EXISTS tab; 
NOTICE: table "tab" does not exist, skipping 
DROP TABLE 
so=# CREATE TABLE tab(id INT PRIMARY KEY, col text); 
CREATE TABLE 
so=# INSERT INTO tab(id, col) VALUES (1,'a'), (2, 'b'); 
INSERT 0 2 
so=# INSERT INTO tab(id, col) 
VALUES (3, 'c'), (4, 'd'), (1,'aaaa') 
ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col 
returning *,case when xmax::text::int > 0 then 'updated' else 'inserted' end,ctid; 
id | col | case | ctid 
----+------+----------+------- 
    3 | c | inserted | (0,3) 
    4 | d | inserted | (0,4) 
    1 | aaaa | updated | (0,5) 
(3 rows) 

INSERT 0 3 
so=# INSERT INTO tab(id, col) 
VALUES (3, 'c'), (4, 'd'), (1,'aaaa') 
ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col 
returning *,case when xmax::text::int > 0 then 'updated' else 'inserted' end,ctid; 
id | col | case | ctid 
----+------+---------+------- 
    3 | c | updated | (0,6) 
    4 | d | updated | (0,7) 
    1 | aaaa | updated | (0,8) 
(3 rows) 

INSERT 0 3 
+0

Możesz użyć: '' ' POWRÓT (xmax = 0) AS wstawiony ' '' –