IntegrityError pysqlite: различать "не NULL" от "уникального" нарушения

в pysqlite, нарушая NOT NULL или UNIQUE ограничение аналогично поднимает IntegrityError. К сожалению, этот тип исключения не предоставляет код ошибки, а только сообщение.

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

Я придумал следующее решение:

con = sqlite3.connect(':MEMORY:')
con.execute('''CREATE TABLE ABCD (A TEXT NOT NULL,
                                  B TEXT NOT NULL,
                                  C TEXT NOT NULL,
                                  D TEXT NOT NULL,
                                  PRIMARY KEY (A, B))''')
with con:
    for a, b, c, d in inputs:
        try:
            con.execute('INSERT INTO ABCD VALUES (?, ?, ?, ?)',
                        (a, b, c, d))
        except sqlite3.IntegrityError as e:
            # Skip 'not unique' errors, but raise others.
            if not e.message.endswith('not unique'):
                raise
con.close()

однако разбор сообщения об ошибке кажется неправильно и может быть ненадежно. Есть ли лучший способ сделать это, возможно, даже с помощью con.executemany()?

3 ответов


вот что я делал:

con = sqlite3.connect(':MEMORY:')
con.execute('''CREATE TABLE ABCD (A TEXT NOT NULL,
                                  B TEXT NOT NULL,
                                  C TEXT NOT NULL,
                                  D TEXT NOT NULL,
                                  PRIMARY KEY (A, B))''')
with con:
    for a, b, c, d in inputs:
        if any(elem is None for elem in (a, b, c, d)):
            raise ValueError('Fields must not be empty.')
        con.execute('INSERT OR IGNORE INTO ABCD VALUES (?, ?, ?, ?)',
                    (a, b, c, d))
con.close()

как это, пустые значения ловятся "вручную" перед выполнением операции БД. Если во время execute (например, нарушение UNIQUE ограничение), запись пропускается. Обратите внимание, что INSERT OR IGNORE не означает игнорирование ограничения уникальности, а скорее игнорирование (т. е. пропуск) входная строка.

недостатком этого решения является то, что проверка пустых значений выполняется дважды. Думаю, это ... однако не так уж плохо, поскольку это, по-видимому, довольно дешевая операция. Я думаю, это все еще чище, чем разбор сообщения об ошибке, и, вероятно, более устойчив к изменениям (например, обновление pysqlite, которое может изменить некоторые детали в сообщении об ошибке).

кредиты: идея возникла из обсуждения с лутца. Это было независимо также предложено Martijn.


более элегантное решение-полностью полагаться на функциональность SQL(ite). Указав предложение conflict для первичного ключа (ON CONFLICT IGNORE), желаемое поведение уже достигнуто:

con = sqlite3.connect(':memory:')
con.execute('''CREATE TABLE ABCD (A TEXT NOT NULL,
                                  B TEXT NOT NULL,
                                  C TEXT NOT NULL,
                                  D TEXT NOT NULL,
                                  PRIMARY KEY (A, B) ON CONFLICT IGNORE)''')

таким образом, повторяющиеся строки (которые нарушают ограничение уникальности первичного ключа) молча пропускаются, в то время как значения Null вызывают прерывание (в результате sqlite3 исключения). Это все достигается без предварительной фильтрации данных для значений Null / None или возиться с сообщениями об ошибках the sqlite3 API-интерфейс. Теперь мы можем просто позвонить con.executemany(), не мудрствуя лукаво:

with con:
    con.executemany('INSERT INTO ABCD VALUES (?, ?, ?, ?)', inputs)

Ниже приведен рабочий код:

import sqlite3

con = sqlite3.connect(':memory:')
con.execute('''CREATE TABLE ABCD (A TEXT NOT NULL,
                                  B TEXT NOT NULL,
                                  C TEXT NOT NULL,
                                  D TEXT NOT NULL,
                                  PRIMARY KEY (A, B));''')

inputs = [('cow', 'pig', 'cat', 'dog'), ('cow', 'pig', 'quail', 'turkey')]
with con:
    for a, b, c, d in inputs:
        try:
            con.execute('INSERT INTO ABCD VALUES (?, ?, ?, ?);',
                        (a, b, c, d))
        except sqlite3.IntegrityError as e:
            if 'not null' in e.args[0].lower():
                print('There is a NULL value')
            elif 'unique constraint' in e.args[0].lower():
                print('There is unique violation')
            else:
                raise

тест:

>>> 
There is a NULL value
>>> 

второй результат теста:

>>> 
There is unique violation
>>> 

надеется, может помочь вам.