简体   繁体   English

pysqlite的IntegrityError:区分'NOT NULL'和'UNIQUE'违规

[英]pysqlite's IntegrityError: distinguish 'NOT NULL' from 'UNIQUE' violation

In pysqlite, violating a NOT NULL or a UNIQUE constraint likewise raise an IntegrityError. 在pysqlite中,违反NOT NULLUNIQUE约束同样会引发IntegrityError。 Unfortunately, this Exception type does not provide an error code, but only a message. 不幸的是,此Exception类型不提供错误代码,而只提供消息。

So, let's say I want to ignore unique-constraint violations, because I know this is safe on the given data, but Null values in the key columns should be reported. 所以,假设我想忽略唯一约束违规,因为我知道这对给定数据是安全的,但是应该报告关键列中的Null值。

I've come up with the following solution: 我想出了以下解决方案:

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

However, parsing the error message seems wrong and might be unreliable. 但是,解析错误消息似乎是错误的,可能不可靠。 Is there a better way to do this, maybe even using con.executemany() ? 有没有更好的方法来做到这一点,甚至可能使用con.executemany()

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. 像这样,在执行DB操作之前,“手动”捕获空值。 If any error occurs during execute (such as a violation of the UNIQUE constraint), the entry is skipped. 如果在execute期间发生任何错误(例如违反UNIQUE约束),则跳过该条目。 Please note that INSERT OR IGNORE does not mean ignoring the uniqueness constraint, but rather ignoring (ie. skipping) an input line. 请注意, INSERT OR IGNORE并不意味着忽略唯一性约束,而是忽略(即跳过)输入行。

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). 我认为,它仍然比解析错误消息更清晰,并且可能对更改更加健壮(例如pysqlite更新,这可能会更改错误消息中的某些细节)。

Credits: The idea emerged from discussion with Lutz. 致谢:这个想法来自与Lutz的讨论。 It was independently also suggested by Martijn. 它也是Martijn独立提出的。

A more elegant solution is to rely on SQL(ite) functionality entirely. 更优雅的解决方案是完全依赖SQL(ite)功能。 By specifying a conflict clause for the primary key ( ON CONFLICT IGNORE ), the desired behaviour is already achieved: 通过为主键指定冲突子句( 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)''')

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). 因此,静默跳过重复行(违反主键的唯一性约束),而Null值导致中止(导致sqlite3异常)。 This is all achieved without pre-filtering the data for Null/None values or fiddling with error messages of the sqlite3 API. 这一切都是在没有预先过滤Null / None值的数据或摆弄sqlite3 API的错误消息的情况下实现的。 We can now simply call con.executemany() , without further ado: 我们现在可以简单地调用con.executemany() ,而con.executemany()

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

Below is a working code: 以下是工作代码:

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

Test: 测试:

>>> 
There is a NULL value
>>> 

Second Test result: 第二次测试结果:

>>> 
There is unique violation
>>> 

Hopes, can help you. 希望,可以帮助你。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM