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

时间:2011-11-11 09:39:27

标签: sql postgresql roles dynamic-sql

如何编写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;

...但是这不起作用 - 纯SQL中似乎不支持IF

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

11 个答案:

答案 0 :(得分:118)

以与您的想法类似的方式简化:

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

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

(以@a_horse_with_no_name的答案为基础,并在@Gregory's comment后改进。)

例如,与CREATE TABLE不同,CREATE ROLE({尚未)没有IF NOT EXISTS子句。并且不能在纯SQL中执行动态DDL语句。

除了使用另一个PL之外,你的“避免PL / pgSQL”的请求是不可能的。 DO statement使用plpgsql作为默认过程语言。语法允许省略显式声明:

  

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

答案 1 :(得分:39)

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

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

但是只有放弃这个用户才不会造成任何伤害。

答案 2 :(得分:13)

如果在同一Postgres群集(数据库服务器)上同时执行两个此类脚本,则接受的答案会遇到竞争条件,这在持续集成环境中常见的

尝试创建角色并在创建角色时优雅地处理问题通常更安全:

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

答案 3 :(得分:11)

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';"

(这不是问题的答案!只适用于那些可能有用的人)

答案 4 :(得分: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)

答案 5 :(得分:7)

当您使用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$
;

答案 6 :(得分:7)

我的团队遇到了一个服务器上有多个数据库的情况,这取决于你连接到哪个数据库,SELECT * FROM pg_catalog.pg_user没有返回有问题的ROLE,正如@ erwin-brandstetter和@a_horse_with_no_name所提出的那样。执行条件块,然后我们点击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$

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

答案 7 :(得分:3)

您可以通过解析输出:

在批处理文件中执行此操作
SELECT * FROM pg_user WHERE usename = 'my_user'

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

答案 8 :(得分:1)

建议使用模式的一些答案:检查角色是否不存在,如果不存在,则发出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

答案 9 :(得分:0)

Simulate CREATE DATABASE IF NOT EXISTS for PostgreSQL?相同的解决方案应该起作用-向CREATE USER …发送\gexec

psql中的解决方法

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

shell的解决方法

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

有关更多详细信息,请参见accepted answer there

答案 10 :(得分:0)

如果您可以访问 shell,则可以执行此操作。

psql -tc "SELECT 1 FROM pg_user WHERE usename = 'some_use'" | grep -q 1 || psql -c "CREATE USER some_user"

对于那些想要解释的人:

-c = run command in database session, command is given in string
-t = skip header and footer
-q = silent mode for grep 
|| = logical OR, if grep fails to find match run the subsequent command