创建PostgreSQL ROLE(用户)(如果不存在)


122

如何编写SQL脚本在PostgreSQL 9.1中创建ROLE,但是如果已经存在则不引发错误?

当前脚本仅具有:

CREATE ROLE my_user LOGIN PASSWORD 'my_password';

如果用户已经存在,则失败。我想要类似的东西:

IF NOT EXISTS (SELECT * FROM pg_user WHERE username = 'my_user')
BEGIN
    CREATE ROLE my_user LOGIN PASSWORD 'my_password';
END;

...但是那行不通- IF普通的SQL似乎不支持该功能。

我有一个批处理文件,该文件创建了PostgreSQL 9.1数据库,角色和其他一些东西。它调用psql.exe,并传入要运行的SQL脚本的名称。到目前为止,所有这些脚本都是纯SQL,如果可能的话,我想避免使用PL / pgSQL。

Answers:


156

按照您的想法进行简化:

DO
$do$
BEGIN
   IF NOT EXISTS (
      SELECT FROM pg_catalog.pg_roles  -- SELECT list can be empty for this
      WHERE  rolname = 'my_user') THEN

      CREATE ROLE my_user LOGIN PASSWORD 'my_password';
   END IF;
END
$do$;

(以@a_horse_with_no_name的答案为基础,并通过@Gregory 的注释进行了改进。)

例如,与之不同的CREATE TABLE是,没有IF NOT EXISTSfor子句CREATE ROLE(至少第12页)。而且,您无法在普通SQL中执行动态DDL语句。

您无法“避免PL / pgSQL”,除非使用另一个PL。该DO语句使用plpgsql作为默认过程语言。该语法允许省略显式声明:

DO [ LANGUAGE lang_name ] code
... 编写代码所用的过程语言的名称。如果省略,则默认为。
lang_name
plpgsql


1
@Alberto:pg_userpg_roles都是正确的。当前版本9.3仍然如此,并且不会很快改变。
Erwin Brandstetter

2
@Ken:如果$在客户端中有特殊含义,则需要根据客户端的语法规则对其进行转义。尝试逃脱$\$在Linux外壳。或提出一个新问题-评论不是地方。您可以始终链接到此上下文。
Erwin Brandstetter,

1
我使用的是9.6,如果使用NOLOGIN创建了用户,则它们不会显示在pg_user表中,而是显示在pg_roles表中。pg_roles在这里会更好吗?
杰西

2
@ErwinBrandstetter这不适用于具有NOLOGIN的角色。它们显示在pg_roles中,而不显示在pg_user中。
格雷戈里·阿雷纽斯

2
该解决方案具有竞争条件。此答案中记录了一个更安全的变体。
blubb

60

如果两个这样的脚本在同一个Postgres集群(数据库服务器)上同时执行,那么在连续集成环境中很常见那么可接受的答案就会出现竞争。

通常,尝试创建角色并在创建角色时优雅地处理问题是较为安全的:

DO $$
BEGIN
  CREATE ROLE my_role WITH NOLOGIN;
  EXCEPTION WHEN DUPLICATE_OBJECT THEN
  RAISE NOTICE 'not creating role my_role -- it already exists';
END
$$;

2
我喜欢这种方式,因为它通知存在。
马蒂亚斯·巴隆

2
DUPLICATE_OBJECT如果您不想使用捕获所有条件,则在这种情况下是精确条件OTHERS
Danek Duvall

43

或者,如果角色不是任何数据库对象的所有者,则可以使用:

DROP ROLE IF EXISTS my_user;
CREATE ROLE my_user LOGIN PASSWORD 'my_password';

但是只有丢弃该用户才不会造成任何伤害。


10

Bash替代品(用于Bash脚本):

psql -h localhost -U postgres -tc \
"SELECT 1 FROM pg_user WHERE usename = 'my_user'" \
| grep -q 1 \
|| psql -h localhost -U postgres \
-c "CREATE ROLE my_user LOGIN PASSWORD 'my_password';"

(不是问题的答案!仅适用于可能有用的人)


3
它应该阅读FROM pg_roles WHERE rolname,而不是FROM pg_user WHERE usename
巴特

8

这是使用plpgsql的通用解决方案:

CREATE OR REPLACE FUNCTION create_role_if_not_exists(rolename NAME) RETURNS TEXT AS
$$
BEGIN
    IF NOT EXISTS (SELECT * FROM pg_roles WHERE rolname = rolename) THEN
        EXECUTE format('CREATE ROLE %I', rolename);
        RETURN 'CREATE ROLE';
    ELSE
        RETURN format('ROLE ''%I'' ALREADY EXISTS', rolename);
    END IF;
END;
$$
LANGUAGE plpgsql;

用法:

posgres=# SELECT create_role_if_not_exists('ri');
 create_role_if_not_exists 
---------------------------
 CREATE ROLE
(1 row)
posgres=# SELECT create_role_if_not_exists('ri');
 create_role_if_not_exists 
---------------------------
 ROLE 'ri' ALREADY EXISTS
(1 row)

8

建议使用模式的一些答案:检查角色是否不存在,如果不存在,则发出CREATE ROLE命令。这有一个缺点:比赛条件。如果其他人在检查和发出CREATE ROLE命令之间创建了新角色,则CREATE ROLE显然会因致命错误而失败。

为了解决上述问题,已经提到了更多的其他答案PL/pgSQLCREATE ROLE无条件地发出,然后从该调用中捕获异常。这些解决方案只有一个问题。他们会静默删除任何错误,包括那些由于角色已经存在而不会产生的错误。CREATE ROLE可能还会引发其他错误,并且IF NOT EXISTS当角色已经存在时,模拟应仅使错误静音。

CREATE ROLEduplicate_object当角色已经存在时抛出错误。并且异常处理程序应该只捕获这一错误。正如其他答案所述,将致命错误转换为简单通知是一个好主意。其他PostgreSQL IF NOT EXISTS命令会添加, skipping到其消息中,因此为了保持一致,我也在此处添加了它。

这是用于模拟CREATE ROLE IF NOT EXISTS具有正确异常和sqlstate传播的完整SQL代码:

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

2
谢谢。没有竞争条件,严格的异常捕获,包装了Postgres自己的消息,而不是重写您自己的消息。
Stefano Taschini,

1
确实!这是当前唯一正确的答案,它不受竞争条件的影响,并使用必要的选择性错误处理。非常可惜的是,这个答案出现在(不是完全正确的)最佳答案收集了超过100分之后。
vog

1
别客气!我的解决方案还传播SQLSTATE,因此,如果您使用SQL连接器从其他PL / SQL脚本或其他语言调用语句,则会收到正确的SQLSTATE。
巴厘岛

6

在9.x上,您可以将其包装到DO语句中:

do 
$body$
declare 
  num_users integer;
begin
   SELECT count(*) 
     into num_users
   FROM pg_user
   WHERE usename = 'my_user';

   IF num_users = 0 THEN
      CREATE ROLE my_user LOGIN PASSWORD 'my_password';
   END IF;
end
$body$
;

选择应该是“ SELECT count(*)到num_users中,从pg_roles那里rolname ='data_rw';`否则将无法工作
Miro

6

我的团队遇到了一个服务器上有多个数据库的情况,具体取决于您所连接的数据库SELECT * FROM pg_catalog.pg_user,@ erwin-brandstetter和@a_horse_with_no_name提出的问题ROLE并未返回。条件块被执行,我们点击role "my_user" already exists

不幸的是,我们不确定确切的条件,但是此解决方案可以解决此问题:

        DO  
        $body$
        BEGIN
            CREATE ROLE my_user LOGIN PASSWORD 'my_password';
        EXCEPTION WHEN others THEN
            RAISE NOTICE 'my_user role exists, not re-creating';
        END
        $body$

排除其他例外可能更为具体。


3
pg_user表似乎仅包含具有LOGIN的角色。如果角色有NOLOGIN它不pg_user显示,至少在PostgreSQL的10
格雷戈里Arenius

2

您可以通过解析以下输出,在批处理文件中执行此操作:

SELECT * FROM pg_user WHERE usename = 'my_user'

psql.exe如果角色不存在,然后再次运行。


2
“用户名”列不存在。它应该是“ usename”。
Mouhammed Soueidane

3
“用户名”是不存在的。:)
Garen 2013年

1
请参考pg_user查看文档。在版本7.4-9.6中没有“用户名”列,“用户名”是正确的。
希瓦

1

PostgreSQL不存在时模拟创建数据库的解决方案相同应该工作-发送CREATE USER …\gexec

从psql内部解决

SELECT 'CREATE USER my_user'
WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec

从外壳解决

echo "SELECT 'CREATE USER my_user' WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec" | psql

有关更多详细信息,请参见此处的已接受答案


您的解决方案仍然存在竞态条件,我在我的答案stackoverflow.com/a/55954480/7878845中描述了这种情况, 如果您多次并行运行Shell脚本,则会出现错误:角色“ my_user”已经存在
巴利
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.