IntegrityError:区分唯一约束和非空违规

时间:2012-09-19 07:23:04

标签: python django postgresql psycopg2

我有这段代码:

try:
    principal = cls.objects.create(
        user_id=user.id,
        email=user.email,
        path='something'
    )
except IntegrityError:
    principal = cls.objects.get(
        user_id=user.id,
        email=user.email
    )

它尝试创建具有给定ID和电子邮件的用户,如果已存在,则尝试获取现有记录。

我知道这是一个糟糕的结构,无论如何它都会被重构。但我的问题是:

如何确定发生了IntegrityError的类型:与unique约束违规相关的问题((user_id,email)上有唯一键)或与{{1}相关的关键字约束(not null不能为空)?

3 个答案:

答案 0 :(得分:8)

psycopg2为SQLSTATE提供了pgcode成员的异常,它为您提供了非常精细的错误信息以便匹配。

python3
>>> import psycopg2
>>> conn = psycopg2.connect("dbname=regress")
>>> curs = conn.cursor()
>>> try:
...     curs.execute("INVALID;")
... except Exception as ex:
...     xx = ex
>>> xx.pgcode
'42601'

有关代码含义,请参阅Appendix A: Error Codes in the PostgreSQL manual。请注意,对于大类,您可以粗略匹配前两个字符。在这种情况下,我可以看到syntax_error类别中的SQLSTATE 42601为Syntax Error or Access Rule Violation

您想要的代码是:

23505   unique_violation
23502   not_null_violation

所以你可以写:

try:
    principal = cls.objects.create(
        user_id=user.id,
        email=user.email,
        path='something'
    )
except IntegrityError as ex:
    if ex.pgcode == '23505':
        principal = cls.objects.get(
            user_id=user.id,
            email=user.email
        )
    else:
        raise

尽管如此,这是做upsertmerge的不好方法。 @ pr0gg3d可能正确地建议用Django做正确的方法;我不做Django所以我不能评论那一点。有关upsert / merge的一般信息,请参阅depesz's article on the topic

答案 1 :(得分:4)

自9-6-2017更新:

执行此操作的一种非常优雅的方法是try / except IntegrityError as exc,然后在exc.__cause__exc.__cause__.diag上使用一些有用的属性(一个诊断类可以为您提供一些关于手头错误的其他超级相关信息 - 您可以使用dir(exc.__cause__.diag))自行探索。

您可以使用的第一个如上所述。为了使您的代码更具未来性,您可以直接引用psycopg2代码,甚至可以使用上面提到的诊断类检查违反的约束:

except IntegrityError as exc:
    from psycopg2 import errorcodes as pg_errorcodes
    assert exc.__cause__.pgcode == pg_errorcodes.UNIQUE_VIOLATION
    assert exc.__cause__.diag.constraint_name == 'tablename_colA_colB_unique_constraint'

编辑以澄清:我必须使用__cause__访问器,因为我使用Django,所以要访问psycopg2 IntegrityError类,我必须调用exc.__cause__

答案 2 :(得分:3)

最好使用:

try:
    obj, created = cls.objects.get_or_create(user_id=user.id, email=user.email)
except IntegrityError:
    ....

,如https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

仅在出现IntegrityError约束违规的情况下才应提出NOT NULL。 此外,您可以使用created标志来了解对象是否已存在。