15

我有这个代码:

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,电子邮件)上有唯一键)或与not null约束相关的事件(path不能为空)?

4

3 回答 3

13

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'

有关代码含义,请参见PostgreSQL 手册中的附录 A:错误代码。请注意,您可以粗略地匹配广泛类别的前两个字符。在这种情况下,我可以看到 SQLSTATE 42601 属于syntax_errorSyntax 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

也就是说,这是执行upsertor的不好方法merge。@pr0gg3d 在建议使用 Django 的正确方法方面可能是正确的;我不做 Django,所以我不能评论那一点。有关 upsert/merge 的一般信息,请参阅depesz 关于该主题的文章

于 2012-09-19T13:14:27.263 回答
9

2017 年 9 月 6 日更新:

一个非常优雅的方法是try/ except IntegrityError as exc,然后在exc.__cause__and上使用一些有用的属性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__

于 2017-09-06T20:44:07.113 回答
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标志来了解对象是否已经存在。

于 2012-09-19T07:44:05.690 回答