Продолжение транзакции после ошибки нарушения первичного ключа

Я делаю массовая вставка записей в базу данных из файла журнала. Иногда (~1 строка из каждой тысячи) одна из строк нарушает первичный ключ и вызывает сбой транзакции. В настоящее время пользователь должен вручную пройти через файл, вызвавший сбой, и удалить оскорбительную строку перед попыткой повторного импорта. Учитывая, что есть сотни этих файлов для импорта это непрактично.

мой вопрос: как я могу пропустить ввод записи, которые нарушат ограничение первичного ключа, без необходимости делать SELECT оператор перед каждой строкой, чтобы увидеть, существует ли он уже?

примечание: Я знаю очень похожий вопрос #1054695, но это, похоже, конкретный ответ SQL Server, и я использую PostgreSQL (импорт через Python/psycopg2).

4 ответов


вы также можете использовать точки сохранения в транзакции.

Pythonish псевдокод иллюстрируется со стороны приложения:

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: вот фактический пример этого в действии в psql на основе небольшого изменения примера в документации (операторы SQL с префиксом">"):

> 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)

обратите внимание, что значение 3 было вставлено после ошибки, но все еще внутри той же транзакции!

документация для SAVEPOINT находится на http://www.postgresql.org/docs/8.4/static/sql-savepoint.html.


Я бы использовал хранимую процедуру, чтобы поймать исключения из ваших уникальных нарушений. Пример:

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');

можно сделать rollback к транзакции или откату к точке сохранения непосредственно перед кодом, который вызывает исключение (CR-курсор):

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)

этот код предполагает, что выполняется транзакция, иначе вы не получили бы это сообщение об ошибке.

в Django PostgreSQL в серверной создает курсоры непосредственно с psycopg. Возможно, в будущем они сделают прокси-класс для курсора Django, похожий на курсор оду. Они расширяют курсор с помощью следующий код (self является курсором):

@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)

таким образом, контекст упрощает ваш код, это будет:

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

и код более удобочитаем, потому что материала транзакции там нет.


или вы можете использовать SSIS, и неудачные строки принимают другой путь, чем успешные.

поскольку вы используете различную базу данных, можете ли вы массово вставлять файлы в промежуточную таблицу, а затем использовать код SQL для выбора только тех записей, у которых нет идентификатора exisitng?