2011-12-13 10 views
17

Używanie Pythona 2.7 iObsługa błędnego klucza głównego w Pythonie/psycopg2

W [150]: psycopg2. wersja Out [150]: '2.4.2 (dt dec ext PQ3)'

Mam proste skrypty Pythona, że ​​przetwarzanie transakcji i zapisuje dane do bazy danych. Czasami istnieje wkładka, która narusza mój klucz podstawowy. To jest w porządku, chcę tylko zignorować tę płytę i kontynuować ją na wesoło. Problem polega na tym, że błąd klucza głównego psycopg2 powoduje przerwanie całego bloku transakcji i wszystkich wstawień po błędzie. Oto przykładowy błąd:

ERROR: duplicate key value violates unique constraint "encounter_id_pkey" 
DETAIL: Key (encounter_id)=(9012235) already exists. 

To jest na następnej wkładce. nie naruszenie.

Inserting: 0163168~9024065 
ERROR: current transaction is aborted, commands ignored until end of transaction block 

Drugi błąd powtarza się dla każdej wstawki. Oto uproszczona pętla. Pętlę przechodzę przez ramkę danych pandy, ale może to być jakakolwiek pętla.

conn = psycopg2.connect("dbname='XXXX' user='XXXXX' host='XXXX' password='XXXXX'") 

cur = conn.cursor() 

for i, val in df2.iteritems(): 
    try: 
     cur = conn.cursor() 
     cur.execute("""insert into encounter_id_table (
     encounter_id,current_date ) 
     values  
     (%(create_date)s, %(encounter_id)s) ;""", 
     'encounter_id':i.split('~')[1], 
     'create_date': datetime.date.today() })   
     cur.commit() 
     cur.close() 
    except Exception , e: 
     print 'ERROR:', e[0] 
     cur.close() 
conn.close() 

Ponownie podstawową ideą jest umiejętne obsługiwanie błędu. W sentencji admirała Nelsona z Królewskiej Marynarki Wojennej: "Niech szlag trafi prosto na nich". Lub w naszym przypadku cholerne Błędy idą prosto na nich. "Pomyślałem, otwierając kursor na każdej wstawce, że chciałbym zresetować blok transakcji.Nie chcę, aby zresetować połączenie tylko z powodu błędu klucza podstawowego. coś jestem po prostu brakuje?

przed ręką dzięki za poświęcony czas.

John

Odpowiedz

19

należy wycofać transakcję w przypadku błędu.

Dodałem jeszcze jedną try..except..else budowę w mieszku kodu aby pokazać dokładne miejsce, w którym wystąpi wyjątek:

try: 
    cur = conn.cursor() 

    try: 
     cur.execute("""insert into encounter_id_table (
      encounter_id,current_date ) 
      values  
      (%(create_date)s, %(encounter_id)s) ;""", 
      'encounter_id':i.split('~')[1], 
      'create_date': datetime.date.today() }) 
    except psycopg2.IntegrityError: 
     conn.rollback() 
    else: 
     conn.commit() 

    cur.close() 
except Exception , e: 
    print 'ERROR:', e[0] 
+0

To jest prawie taka odpowiedź. Ale muszę wywołać metodę rollback() na obiekcie połączenia (conn) tutaj. Kursor ma tylko zatwierdzenie, a nie wycofanie. Dzięki – jdennison

+0

Więcej błędów metod. Musisz wywołać commit na połączeniu, a nie na kursorze. – jdennison

+0

Zastanawiam się, że zatwierdzanie i wycofywanie zmian jest metodami połączenia. Pomyśl, że ktoś powinien edytować mój kod, aby to poprawić. Niestety nie jestem zaznajomiony z bezpośrednim używaniem 'psycopg', ponieważ zazwyczaj korzystam z ORM lub nierelacyjnych baz danych. – lig

2

Po pierwsze: CURRENT_DATE jest zastrzeżonym słowem w każdym standardzie SQL, a także w PostgreSQL. Nie można go używać jako identyfikatora bez podwójnego cytowania. Zdecydowanie radzę nie używać go w ogóle. I przemianowany na kolumnę curdate w moim przykładzie

Dalej, nie jestem ekspertem w składni Pythona, ale wydaje się, że odwrócona kolejność wstawiania kolumn:

(%(create_date)s, %(encounter_id)s) 

Powinno być:

(%(encounter_id)s, %(create_date)s) 

do głównego pytania: można uniknąć tego problemu całkowicie przez sprawdzenie, czy klucz jest już w tabeli przed użyciem go w komendzie wpisać:

INSERT INTO encounter_id_table (encounter_id, curdate) 
SELECT 1234, now()::date 
WHERE NOT EXISTS (SELECT * FROM encounter_id_table t 
        WHERE t.encounter_id = 1234); 

W składni Pythona, który powinien być:

cur.execute("""INSERT INTO encounter_id_table (encounter_id, curdate) 
    SELECT %(encounter_id)s, %(create_date)s, 
    WHERE NOT EXISTS (
      SELECT * FROM encounter_id_table t 
      WHERE t.encounter_id = %(encounter_id)s);""", 
    {'encounter_id':i.split('~')[1], 
    'create_date': datetime.date.today()})  
+0

Jeśli konkretnie nie ustawisz poziomu izolacji na coś bardziej rygorystycznego, użycie składni WHERE NOT EXISTS nadal spowoduje spełnienie warunków wyścigu. – lusional

+1

@ Alternional: W Postgres 9.5 lub nowszym najlepiej używać 'INSERT .. ON CONFLICT DO NOTHING'. Porównaj: http://stackoverflow.com/questions/17267417/how-to-upsert-merge-insert-on-duplicate-update-in-postgresql. Zmiana poziomu izolacji na "Serializable" byłaby znacznie droższa. –

+0

Uzgodnione - użycie składni INSERT .. ON CONFLICT jest znacznie lepsze, jeśli to możliwe. Warunki wyścigu są nieprzyjemne, gdy nie są odpowiednio traktowane. – lusional