PostgreSQL ROLE(ユーザー)が存在しない場合は作成します


122

PostgreSQL 9.1でROLEを作成するSQLスクリプトを作成する方法はありますが、すでに存在する場合はエラーを発生させませんか?

現在のスクリプトは単に次のものを持っています:

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データベース、ロール、およびその他のいくつかを作成するバッチファイルがあります。実行するSQLスクリプトの名前を渡して、psql.exeを呼び出します。これまでのところ、これらのスクリプトはすべてプレーンSQLであり、可能であればPL / pgSQLなどを避けたいと思います。

回答:


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のコメントで改善されました。)

たとえば、とは異なり(少なくともpg 12まで)CREATE TABLEIF NOT EXISTS句はありませんCREATE ROLE。また、プレーンSQLで動的DDLステートメントを実行することはできません

別のPLを使用しない限り、「PL / pgSQLを回避する」という要求は不可能です。このDOステートメントでは、デフォルトの手続き型言語としてplpgsqlを使用しています。構文では、明示的な宣言を省略できます。

DO [ LANGUAGE lang_name ] code
... コードが記述される手続き言語の名前。省略された場合、デフォルトはです。
lang_name
plpgsql


1
@Alberto:pg_userpg_rolesはどちらも正しいです。それでも、現在のバージョン9.3の場合は、すぐには変更されません。
Erwin Brandstetter 2014年

2
@ケン:$クライアントで特別な意味がある場合は、クライアントの構文規則に従ってエスケープする必要があります。Linuxシェルでエスケープ$\$てみてください。または、新しい質問を開始します-コメントは場所ではありません。あなたはいつでもコンテキストのためにこれにリンクすることができます。
Erwin Brandstetter 2016年

1
私は9.6を使用していますが、ユーザーがNOLOGINで作成された場合、それらはpg_userテーブルには表示されませんが、pg_rolesテーブルには表示されます。ここでpg_rolesはより良い解決策でしょうか?
ジェス2017

2
@ErwinBrandstetterこれは、NOLOGINを持つロールには機能しません。それらはpg_rolesには表示されますが、pg_userには表示されません。
グレゴリーアレニウス2018年

2
このソリューションは競合状態に悩まされています。より安全なバリアントがこの回答に記載されています
blubb 2018

60

継続的統合環境では一般的であるように、同じPostgresクラスタ(DBサーバー)でこのような2つのスクリプトが同時に実行されると、受け入れられた回答は競合状態になります

一般に、ロールを作成して、作成時に問題を適切に処理する方が安全です。

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
存在することを通知するので、この方法が好きです。
Matias Barone

2
DUPLICATE_OBJECTこの場合の正確な条件ですOTHERS。でほぼすべての条件をキャッチしたくない場合。
Danek Duvall

43

または、ロールが使用できるdbオブジェクトの所有者でない場合:

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コマンドを発行します。これには1つの欠点があります。競合状態です。他の誰かがチェックとCREATE ROLEコマンドの発行の間に新しい役割を作成した場合CREATE ROLE明らかに致命的なエラーで失敗します。

上記の問題を解決するために、他の回答ではすでにの使用について言及しておりPL/pgSQLCREATE ROLE無条件に発行してから、その呼び出しからの例外をキャッチしています。これらのソリューションには問題が1つだけあります。それらは、ロールがすでに存在するという事実によって生成されないエラーを含め、すべてのエラーを静かにドロップします。CREATE ROLE他のエラーもスローする可能性がありIF NOT EXISTS、ロールがすでに存在する場合、シミュレーションはエラーのみを無音にします

CREATE ROLEduplicate_objectロールがすでに存在する場合はエラーをスローします。そして、例外ハンドラはこの1つのエラーのみをキャッチする必要があります。他の回答が述べたように、致命的なエラーを単純な通知に変換することは良い考えです。その他の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を介して2回呼び出され、その後直接呼び出されます):

$ 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ポイントを超えて収集した後に表示されたのは本当に残念です。
ヴォグ

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(*)into num_users FROM pg_roles WHERE rolname = 'data_rw';`でなければなりません
Miro

6

SELECT * FROM pg_catalog.pg_user@ erwin-brandstetterおよび@a_horse_with_no_nameによって提案されたように、私のチームは、1つのサーバー上に複数のデータベースがある状況に遭遇しました。接続しているデータベースによっては、問題の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を持っている場合、それは少なくともPostgreSQLの10で、pg_userに表示されない
グレゴリーArenius

2

次の出力を解析して、バッチファイルで実行できます。

SELECT * FROM pg_user WHERE usename = 'my_user'

psql.exeロールが存在しない場合は、もう一度実行します。


2
「ユーザー名」列は存在しません。「usename」である必要があります。
Mouhammed Soueidane 2013

3
「usename」は存在しないものです。:)
Garen

1
pg_userビュードキュメントを参照してください。バージョン7.4〜9.6には「username」列はなく、「usename」が正しい列です。
Sheva

1

PostgreSQLのCREATE DATABASE IF NOT EXISTSをシミュレートするのと同じ解決策動作するはずです-にを送信し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 シェルスクリプトをさらに並行して実行するとエラーが発生します:ロール "my_user"はすでに存在しています
Pali
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.