2016-03-11 16 views
8

Ho questa tabella (generato da Django):Come includere file esclusi in ritorno da INSERT ... ON CONFLICT

CREATE TABLE feeds_person (
    id serial PRIMARY KEY, 
    created timestamp with time zone NOT NULL, 
    modified timestamp with time zone NOT NULL, 
    name character varying(4000) NOT NULL, 
    url character varying(1000) NOT NULL, 
    email character varying(254) NOT NULL, 
    CONSTRAINT feeds_person_name_ad8c7469_uniq UNIQUE (name, url, email) 
); 

Sto cercando di massa inserire un sacco di dati utilizzando INSERT con una clausola ON CONFLICT.

La ruga è che ho bisogno di ottenere il id indietro per tutte le delle righe, se sono già esistenti o meno.

In altri casi, vorrei fare qualcosa di simile:

INSERT INTO feeds_person (created, modified, name, url, email) 
VALUES blah blah blah 
ON CONFLICT (name, url, email) DO UPDATE SET url = feeds_person.url 
RETURNING id 

Fare la UPDATE provoca la dichiarazione di restituire il id di quella riga. Tranne che non funziona con questo tavolo. I penso che non funzioni perché ho diversi campi unici insieme mentre in altri casi ho usato questo metodo ho avuto solo un campo unico.

Ottengo questo errore quando si tenta di eseguire il codice SQL tramite il cursore di Django:

django.db.utils.ProgrammingError: ON CONFLICT DO UPDATE command cannot affect row a second time 
HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. 

come faccio a fare il grosso inserto con questa tabella e recuperare l'id inseriti e già esistenti?

+0

'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? –

+0

@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. –

risposta

15

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:

+0

Ho risposto al tuo commento sulla domanda, ma per elaborare non voglio davvero aggiornare nulla, l'unico motivo per cui stavo facendo un UPDATE era perché questo è l'unico modo in cui ho potuto capire come ottenere indietro l'ID del già righe esistenti. In altre parole, quando inserisco una grande porzione di nuovi dati, ho bisogno degli ID delle righe che ho inserito e degli ID delle righe già esistenti ... e ho bisogno di loro nell'ordine in cui li fornisco in VALORI. AFAICT, questo è il comportamento che PostgrSQL 9.5 mostra ... i documenti non mi sono del tutto chiari se questo è il comportamento garantito. –

+1

@DustinWyatt: considera l'aggiornamento e osserva i bit aggiunti per difendersi da una condizione di competizione. –

Problemi correlati