2010-03-03 7 views
8

Sto eseguendo un inserimento di record in un database da un file di registro. Occasionalmente (~ 1 riga su ogni mille) una delle righe viola la chiave primaria e causa il fallimento della transazione. Attualmente, l'utente deve passare manualmente attraverso il file che ha causato l'errore e rimuovere la riga incriminata prima di tentare di reimportarla. Dato che ci sono centinaia di questi file da importare, non è pratico.Continuazione di una transazione dopo l'errore di violazione della chiave primaria

La mia domanda: Come posso ignorare l'inserimento di record che violeranno il vincolo di chiave primaria, senza dover fare una dichiarazione SELECT prima di ogni riga per vedere se esiste già?

Nota: sono a conoscenza della domanda molto simile #1054695, ma sembra essere una risposta specifica di SQL Server e sto utilizzando PostgreSQL (importazione tramite Python/psycopg2).

risposta

12

È inoltre possibile utilizzare SAVEPOINT in una transazione.

Pythonish pseudocodice è illustrare dal lato applicazione:

database.execute("BEGIN") 
foreach data_row in input_data_dictionary: 
    database.execute("SAVEPOINT bulk_savepoint") 
    try: 
     database.execute("INSERT", table, data_row) 
    except: 
     database.execute("ROLLBACK TO SAVEPOINT bulk_savepoint") 
     log_error(data_row) 
     error_count = error_count + 1 
    else: 
     database.execute("RELEASE SAVEPOINT bulk_savepoint") 

if error_count > error_threshold: 
    database.execute("ROLLBACK") 
else: 
    database.execute("COMMIT") 

Edit: Ecco un esempio reale di questo in azione in psql sulla base di una leggera variazione dell'esempio nella documentazione (istruzioni SQL con prefisso "> "):

> CREATE TABLE table1 (test_field INTEGER NOT NULL PRIMARY KEY); 
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index "table1_pkey" for table "table1" 
CREATE TABLE 

> BEGIN; 
BEGIN 
> INSERT INTO table1 VALUES (1); 
INSERT 0 1 
> SAVEPOINT my_savepoint; 
SAVEPOINT 
> INSERT INTO table1 VALUES (1); 
ERROR: duplicate key value violates unique constraint "table1_pkey" 
> ROLLBACK TO SAVEPOINT my_savepoint; 
ROLLBACK 
> INSERT INTO table1 VALUES (3); 
INSERT 0 1 
> COMMIT; 
COMMIT 
> SELECT * FROM table1; 
test_field 
------------ 
      1 
      3 
(2 rows) 

Si noti che il valore 3 è stato inserito dopo l'errore, ma sempre all'interno della stessa transazione!

La documentazione di SAVEPOINT è http://www.postgresql.org/docs/8.4/static/sql-savepoint.html.

+0

Non funzionerà, quando si verifica un errore, la transazione viene interrotta e il rollback. È necessario un gestore di eccezioni all'interno del database. Richiesta non riuscita: ERRORE: la transazione corrente viene interrotta, i comandi ignorati fino alla fine del blocco di transazione –

+0

Sì, lo sarà. Questo è il punto principale di SAVEPOINT. Ho modificato la mia risposta per dare un esempio concreto. –

+1

---- edit ---- Scusa, ho sbagliato ... peccato per me;) Funziona bene, hai ragione. –

4

Vorrei utilizzare una procedura memorizzata per rilevare le eccezioni sulle vostre violazioni uniche. Esempio:

CREATE OR REPLACE FUNCTION my_insert(i_foo text, i_bar text) 
    RETURNS boolean LANGUAGE plpgsql AS 
$BODY$ 
begin 
    insert into foo(x, y) values(i_foo, i_bar); 
    exception 
     when unique_violation THEN -- nothing 

    return true; 
end; 
$BODY$; 

SELECT my_insert('value 1','another value'); 
+0

Perfetto, grazie. – John

+0

È sempre meglio registrare le eccezioni. È possibile modificare il blog delle eccezioni per registrarlo e continuare comunque. – Guru

+0

È possibile consentire alla funzione di registrare le eccezioni, nessun problema. –

0

Oppure si può usare SSIS e hanno le righe falliti prendono un percorso differente rispetto a quelli di successo.

STATO che si utilizza un database diverso è possibile inserire in blocco i file su una tabella di gestione temporanea e quindi utilizzare il codice SQL per selezionare solo quei record che non hanno un ID esistente?

+0

Puoi approfondire cosa intendi per SSIS? – John

+0

SSIS è lo strumento di importazione dati fornito con SQL Server. Non ho capito che stai usando Postgre. Può ancora fare il lavoro per Postgre, ma non sono sicuro di come lo otterresti dato che non penso che venga fornito con la versione gratuita di SQL Server. – HLGEM

1

si può fare un rollback alla transazione o un rollback a un punto di salvataggio appena prima che il codice che solleva l'eccezione (cr è il cursore):

name = uuid.uuid1().hex 
cr.execute('SAVEPOINT "%s"' % name) 
try: 
    # your failing query goes here 
except Exception: 
    cr.execute('ROLLBACK TO SAVEPOINT "%s"' % name) 
    # your alternative code goes here 
else: 
    cr.execute('RELEASE SAVEPOINT "%s"' % name) 

Questo codice presuppone non è in esecuzione delle transazioni, altrimenti non si riceverebbe questo messaggio di errore.

Django postgresql backend creates cursors direttamente da psycopg. Forse in futuro fanno una classe proxy per il cursore Django, simile allo cursor of odoo. Si estendono il cursore con il following code (auto è il cursore):

@contextmanager 
@check 
def savepoint(self): 
    """context manager entering in a new savepoint""" 
    name = uuid.uuid1().hex 
    self.execute('SAVEPOINT "%s"' % name) 
    try: 
     yield 
    except Exception: 
     self.execute('ROLLBACK TO SAVEPOINT "%s"' % name) 
     raise 
    else: 
     self.execute('RELEASE SAVEPOINT "%s"' % name) 

In questo modo il contesto rende il codice più facile, sarà:

try: 
    with cr.savepoint(): 
     # your failing query goes here 
except Exception: 
    # your alternative code goes here 

e il codice è più leggibile, in quanto la roba di transazione non c'è.

Problemi correlati