pysqlite's IntegrityError: distinguish 'NOT NULL' from 'UNIQUE' violation pysqlite's IntegrityError: distinguish 'NOT NULL' from 'UNIQUE' violation sqlite sqlite

pysqlite's IntegrityError: distinguish 'NOT NULL' from 'UNIQUE' violation


This is what I ended up doing:

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

Like this, empty values are caught "manually" before executing the DB operation. If any error occurs during execute (such as a violation of the UNIQUE constraint), the entry is skipped. Please note that INSERT OR IGNORE does not mean ignoring the uniqueness constraint, but rather ignoring (ie. skipping) an input line.

The downside of this solution is that the check for empty values is done twice. I guess this is not too bad however, since it is presumably rather cheap an operation. I think, it is still cleaner than parsing the error message, and probably more robust to changes (such as a pysqlite update, which might change some detail in the error message).

Credits: The idea emerged from discussion with Lutz. It was independently also suggested by Martijn.


A more elegant solution is to rely on SQL(ite) functionality entirely.By specifying a conflict clause for the primary key (ON CONFLICT IGNORE), the desired behaviour is already achieved:

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

Thus, duplicate lines (which violate the uniqueness constraint of the primary key) are silently skipped, while Null values cause an abort (resulting in an sqlite3 exception). This is all achieved without pre-filtering the data for Null/None values or fiddling with error messages of the sqlite3 API.We can now simply call con.executemany(), without further ado:

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


Below is a working code:

import sqlite3con = 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

Test:

>>> There is a NULL value>>> 

Second Test result:

>>> There is unique violation>>> 

Hopes, can help you.