建议使用模式的一些答案:检查角色是否不存在,如果不存在则发出CREATE ROLE 命令。这有一个缺点:竞争条件。如果其他人在检查和发出CREATE ROLE 命令之间创建了一个新角色,那么CREATE ROLE 显然会失败并出现致命错误。
为了解决上述问题,更多其他答案已经提到了PL/pgSQL 的用法,无条件发出CREATE ROLE,然后从该调用中捕获异常。这些解决方案只有一个问题。他们默默地删除任何错误,包括那些不是由角色已经存在的事实产生的错误。 CREATE ROLE 也可以抛出其他错误,模拟 IF NOT EXISTS 应该只在角色已经存在时静默错误。
CREATE ROLE 在角色已经存在时抛出duplicate_object 错误。异常处理程序应该只捕获这一个错误。正如其他答案所提到的,将致命错误转换为简单通知是个好主意。其他 PostgreSQL IF NOT EXISTS 命令将 , skipping 添加到他们的消息中,所以为了保持一致性,我也在此处添加它。
这里是模拟CREATE ROLE IF NOT EXISTS 的完整 SQL 代码,带有正确的异常和 sqlstate 传播:
DO $$
BEGIN
CREATE ROLE test;
EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
END
$$;
测试输出(通过 DO 调用两次,然后直接调用):
$ sudo -u postgres psql
psql (9.6.12)
Type "help" for help.
postgres=# \set ON_ERROR_STOP on
postgres=# \set VERBOSITY verbose
postgres=#
postgres=# DO $$
postgres$# BEGIN
postgres$# CREATE ROLE test;
postgres$# EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
DO
postgres=#
postgres=# DO $$
postgres$# BEGIN
postgres$# CREATE ROLE test;
postgres$# EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
NOTICE: 42710: role "test" already exists, skipping
LOCATION: exec_stmt_raise, pl_exec.c:3165
DO
postgres=#
postgres=# CREATE ROLE test;
ERROR: 42710: role "test" already exists
LOCATION: CreateRole, user.c:337