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 데이터베이스, 역할 및 기타 몇 가지를 생성하는 배치 파일이 있습니다. psql.exe를 호출하여 실행할 SQL 스크립트의 이름을 전달합니다. 지금까지이 모든 스크립트는 일반 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 의 의견으로 개선되었습니다 .)

예를 들어 with CREATE TABLE에 대한 IF NOT EXISTS절이 없습니다 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 2014 년

2
@Ken : $클라이언트에 특별한 의미가있는 경우 클라이언트의 구문 규칙에 따라 이스케이프해야합니다. Linux 쉘에서 이스케이프 $를 시도하십시오 \$. 또는 새로운 질문을 시작하십시오-댓글은 장소가 아닙니다. 컨텍스트를 위해 언제든지이 링크에 연결할 수 있습니다.
Erwin Brandstetter

1
9.6을 사용하고 있으며 사용자가 NOLOGIN으로 생성 된 경우 pg_user 테이블에는 표시되지 않지만 pg_roles 테이블에는 표시됩니다. 여기서 pg_roles가 더 나은 솔루션이 될까요?
Jess

2
@ErwinBrandstetter NOLOGIN이있는 역할에 대해서는 작동하지 않습니다. pg_roles에는 표시되지만 pg_user에는 표시되지 않습니다.
Gregory Arenius

2
이 솔루션은 경쟁 조건이 있습니다. 이 답변 에는 더 안전한 변형이 문서화되어 있습니다.
blubb

60

연속 통합 환경에서 일반적으로 사용 되는 것처럼 두 개의 스크립트가 동일한 Postgres 클러스터 (DB 서버)에서 동시에 실행되는 경우 허용되는 답변은 경합 상태가됩니다 .

일반적으로 역할을 생성하고 생성 할 때 문제를 정상적으로 처리하는 것이 더 안전합니다.

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
Barth

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/pgSQL하고 CREATE ROLE무조건 실행 한 다음 해당 호출에서 예외를 포착합니다. 이러한 솔루션에는 한 가지 문제가 있습니다. 역할이 이미 존재한다는 사실로 인해 생성되지 않은 오류를 포함하여 모든 오류를 자동으로 삭제합니다. CREATE ROLE다른 오류도 발생할 수 있으며 시뮬레이션 IF NOT EXISTS은 역할이 이미 존재하는 경우에만 오류를 침묵시켜야합니다.

CREATE ROLE던져 duplicate_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를 전파하므로 다른 PL / SQL 스크립트 또는 SQL 커넥터를 사용하여 다른 언어에서 문을 호출하는 경우 올바른 SQLSTATE를 받게됩니다.
Pali

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이 제안한대로 문제의 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

3
"usename"은 존재하지 않는 것입니다. :)
Garen 2013-12-08

1
pg_user 뷰 문서를 참조하십시오 . 버전 7.4-9.6에는 "username"열이 없으며 "usename"이 올바른 열입니다.
Sheva

1

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

셸에서 해결 방법

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.