L'errore che si ottiene:
SU CONFLITTO DO comando UPDATE non può pregiudicare fila una seconda volta
indica che si sta tentando di upsert stessa riga più di una volta in un unico comando. In altre parole: hai duplicati su (name, url, email)
nell'elenco VALUES
. Piega i duplicati (se questa è un'opzione) e dovrebbe funzionare. Ma dovrai decidere quale riga scegliere tra ogni gruppo di duplicati.
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;
Dal momento che usiamo un free-standing VALUES
espressione ora, si deve aggiungere il tipo esplicito getta per i tipi non predefiniti. Come:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
vostri timestamptz
colonne hanno bisogno di un tipo di cast esplicito, mentre i tipi stringa possono funzionare con predefinito text
. (Si potrebbe ancora lanci di varchar(n)
subito.)
Ci sono modi per determinare quale riga di scegliere tra ogni serie di gonzi:
Hai ragione, c'è (al momento) nessun modo per ottenere escluso righe nella clausola RETURNING
.Cito il Postgres Wiki:
noti che RETURNING
non rende visibile il "EXCLUDED.*
" alias dal UPDATE
(solo il generico "TARGET.*
" alias è visibile lì). Si ritiene che ciò crei fastidiose ambiguità per i casi semplici semplici e poco vantaggiosi. Ad un certo punto in futuro, potremmo perseguire un modo di esporre se RETURNING
tuple -Projected sono stati inseriti e aggiornati, ma questo probabilmente non ha bisogno di riuscire ad entrare nella prima iterazione impegnato di la funzione [31].
Tuttavia, non dovrebbe essere aggiornando le righe che non dovrebbero essere aggiornati. Gli aggiornamenti vuoti sono costosi quasi quanto gli aggiornamenti regolari e potrebbero avere effetti collaterali indesiderati. Non hai assolutamente bisogno di UPSERT per iniziare, il tuo caso sembra più come "SELEZIONA o INSERISCI". CORRELATI:
Un più pulita modo per inserire un insieme di righe sarebbe stato con dati modificanti CTE:
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);
La complessità aggiunto dovrebbe pagare grandi tavoli dove INSERT
è la regola e SELECT
l'eccezione.
Originariamente, avevo aggiunto un predicato NOT EXISTS
sull'ultimo SELECT
per evitare duplicati nel risultato. Ma quello era ridondante. Tutti i CTE di una singola query visualizzano le stesse istantanee di tabelle. Il set restituito con ON CONFLICT (name, url, email) DO NOTHING
si esclude a vicenda per il set restituito dopo lo INNER JOIN
sulle stesse colonne.
Purtroppo questo apre anche una piccola finestra per una condizione di competizione. Se ...
- una transazione concorrente inserisce righe contrastanti
- non ha ancora commesso
- ma impegna alla fine
... alcune righe potrebbero andare persi.
Si potrebbe solo INSERT .. ON CONFLICT DO NOTHING
, seguito da una query SELECT
separata per tutte le righe, all'interno della stessa transazione per superare questo. Quale a sua volta apre un'altra finestra per una condizione di gara se le transazioni simultanee possono eseguire il commit sulla tabella tra INSERT
e SELECT
(nell'impostazione predefinita READ COMMITTED
isolation level). Può essere evitato con REPEATABLE READ
transaction isolation (o più severo). Oppure con un blocco di scrittura (possibilmente costoso o addirittura inaccettabile) sull'intera tabella. È possibile ottenere qualsiasi comportamento necessario, ma potrebbe esserci un prezzo da pagare.
correlati:
'Ho bisogno di ottenere l'id posteriore per tutto il rows'. Ovviamente, hai duplicati su '(nome, url, email)' nella tua lista 'VALORI'. Hai bisogno di preservare questi ingannatori o possono essere piegati? Se sì, quale peer scegliere tra set di dupes? E hai a che fare con l'accesso simultaneo in scrittura? –
@ErwinBrandstetter Sto scaricando decine di migliaia di record al minuto da fonti di dati esterne che richiedo periodicamente i dati. Ogni volta che richiedo i dati da una fonte esterna, restituisco alcuni set di dati che ho già con i nuovi dati. Non ho bisogno di aggiornare i dati che ho già. Non so perché non ha fatto clic con me che quell'errore indicava i duplicati nei miei VALORI. I duplicati nei VALORI possono essere piegati in un solo record. –