PostgreSQL에 대해 존재하지 않는 경우 CREATE DATABASE를 시뮬레이션 하시겠습니까?


115

JDBC를 통해 존재하지 않는 데이터베이스를 만들고 싶습니다. MySQL과 달리 PostgreSQL은 create if not exists구문을 지원하지 않습니다 . 이를 수행하는 가장 좋은 방법은 무엇입니까?

응용 프로그램은 데이터베이스가 있는지 여부를 알지 못합니다. 데이터베이스가 존재하는지 확인하고 사용해야합니다. 따라서 원하는 데이터베이스에 연결하는 것이 합리적이며 데이터베이스가 존재하지 않아 연결이 실패하면 기본 postgres데이터베이스 에 연결하여 새 데이터베이스를 만들어야 합니다. Postgres에서 반환 한 오류 코드를 확인했지만 동일한 종류의 관련 코드를 찾을 수 없습니다.

이를 달성하는 또 다른 방법은 postgres데이터베이스 에 연결 하고 원하는 데이터베이스가 있는지 확인하고 그에 따라 조치를 취하는 것입니다. 두 번째는 운동하기가 조금 지루합니다.

Postgres에서이 기능을 달성하는 방법이 있습니까?

답변:


111

제한

pg_database동일한 데이터베이스 클러스터의 모든 데이터베이스에서 액세스 할 수 있는 시스템 카탈로그를 요청할 수 있습니다 . 까다로운 부분은 CREATE DATABASE단일 문으로 만 실행할 수 있다는 것 입니다. 매뉴얼 :

CREATE DATABASE 트랜잭션 블록 내에서 실행할 수 없습니다.

따라서 DO암시 적으로 트랜잭션 블록 내부에있는 함수 또는 문 내에서 직접 실행할 수 없습니다 .

(Postgres 11에 도입 된 SQL 프로시 저는 이것에도 도움이되지 않습니다 .)

psql 내에서 해결 방법

조건부로 DDL 문을 실행하여 psql 내에서이 문제를 해결할 수 있습니다.

SELECT 'CREATE DATABASE mydb'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'mydb')\gexec

매뉴얼 :

\gexec

현재 쿼리 버퍼를 서버로 보낸 다음 쿼리 출력 (있는 경우)의 각 행에있는 각 열을 실행할 SQL 문으로 처리합니다.

셸에서 해결 방법

으로 \gexec만 호출 psql의 필요 :

echo "SELECT 'CREATE DATABASE mydb' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'mydb')\gexec" | psql

연결을 위해 더 많은 psql 옵션이 필요할 수 있습니다. 역할, 포트, 비밀번호, ... 참조 :

psql 메타 명령이고 옵션 은 수동에 다음과 같은 단일 명령 을 기대 psql -c "SELECT ...\gexec"하므로 동일한 것을 호출 할 수 없습니다 .\gexec-c

command서버에서 완전히 구문 분석 할 수있는 명령 문자열 (즉, psql 특정 기능이 포함되지 않음)이거나 단일 백 슬래시 명령이어야합니다. 따라서 -c옵션 내에서 SQL 및 psql 메타 명령을 혼합 할 수 없습니다 .

Postgres 트랜잭션 내에서 해결 방법

당신은 dblink트랜잭션 블록 외부에서 실행되는 현재 데이터베이스에 연결을 . 따라서 효과도 롤백 할 수 없습니다.

이를 위해 추가 모듈 dblink를 설치합니다 (데이터베이스 당 한 번) :

그때:

DO
$do$
BEGIN
   IF EXISTS (SELECT FROM pg_database WHERE datname = 'mydb') THEN
      RAISE NOTICE 'Database already exists';  -- optional
   ELSE
      PERFORM dblink_exec('dbname=' || current_database()  -- current db
                        , 'CREATE DATABASE mydb');
   END IF;
END
$do$;

다시 말하지만 연결을 위해 더 많은 psql 옵션이 필요할 수 있습니다. Ortwin의 추가 답변을 참조하십시오.

dblink에 대한 자세한 설명 :

반복 사용을위한 기능으로 만들 수 있습니다.


원격에서 AWS RDS Postgres에 데이터베이스를 생성 할 때이 문제가 발생했습니다. RDS 마스터 사용자는 슈퍼 사용자가 아니므로 사용할 수 없습니다 dblink_connect.
Ondrej Burkert

수퍼 유저 권한이없는 경우 연결에 암호를 사용할 수 있습니다. 세부 정보 : dba.stackexchange.com/a/105186/3684
Erwin Brandstetter

Docker 컨테이너 내의 init.sql 스크립트 내에서 사용되는 매력처럼 작동합니다. 감사!
Micheal J. Roberts

\gexec쉘에서 첫 번째 쿼리를 실행할 때 를 삭제해야 했지만 작동했습니다.
FilBot3

117

또 다른 대안은 데이터베이스가 존재하지 않는 경우 데이터베이스를 생성하고 그렇지 않으면 그대로 유지하는 쉘 스크립트를 원할 경우입니다.

psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'my_db'" | grep -q 1 || psql -U postgres -c "CREATE DATABASE my_db"

동일한 인스턴스에서 여러 번 실행할 수있는 devops 프로비저닝 스크립트에이 기능이 도움이된다는 것을 알았습니다.


그것은 나를 위해 작동하지 않습니다. c:\Program Files\PostgreSQL\9.6\bin $ psql.exe -U admin -tc "SELECT 1 FROM pg_database WHERE datname = 'my_db'" | grep -q 1 || psql -U admin -c "CREATE DATABASE my_db" 'grep' is not recognized as an internal or external command, operable program or batch file.내가 뭘 잘못 했어?
Anton Anikeev

2
당신은 grep당신의 길에 있지 않습니다 . Windows에서는 grep기본적으로 설치되지 않습니다. gnu grep windowsWindows에서 작동 할 수있는 버전을 검색 하여 찾을 수 있습니다 .
Rod

Thx @Rod. grep을 설치 한 후이 스크립트가 저에게 효과적이었습니다.
Anton Anikeev

@AntonAnikeev : grep없이 단일 psql 호출로 수행 할 수 있습니다. 내 대답에 솔루션을 추가했습니다.
Erwin Brandstetter 2019 년

1
먼저 연결이 가능한지 확인하기 위해 pg_isready를 사용하는 것이 유용합니다. 연결을 사용할 수없는 경우 (잘못된 호스트 이름, 네트워크 다운 등) 스크립트는 데이터베이스 생성을 시도하고 혼란 스러울 수있는 오류 메시지와 함께 실패합니다
Oliver

8

@Erwin Brandstetter가 사용한 약간 확장 된 버전을 사용해야했습니다.

DO
$do$
DECLARE
  _db TEXT := 'some_db';
  _user TEXT := 'postgres_user';
  _password TEXT := 'password';
BEGIN
  CREATE EXTENSION IF NOT EXISTS dblink; -- enable extension 
  IF EXISTS (SELECT 1 FROM pg_database WHERE datname = _db) THEN
    RAISE NOTICE 'Database already exists';
  ELSE
    PERFORM dblink_connect('host=localhost user=' || _user || ' password=' || _password || ' dbname=' || current_database());
    PERFORM dblink_exec('CREATE DATABASE ' || _db);
  END IF;
END
$do$

dblink확장 기능 을 활성화하고 dblink에 대한 자격 증명을 제공해야했습니다. Postgres 9.4에서 작동합니다.


7

데이터에 신경 쓰지 않는다면 먼저 데이터베이스를 삭제 한 다음 다시 만들 수 있습니다.

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

매우 우아한 솔루션. 데이터에 관심이 있다면 먼저 데이터베이스를 백업하는 것을 잊지 마십시오 . 이것이 내가 선호하는 솔루션이지만 상황을 테스트하기 위해.
Laryx Decidua

6

PostgreSQL은 지원하지 않습니다 IF NOT EXISTS에 대한 CREATE DATABASE문. .NET에서만 지원됩니다 CREATE SCHEMA. 또한 CREATE DATABASE거래에서 발행 할 수 없으므로DO 예외 포착 블록에 .

언제 CREATE SCHEMA IF NOT EXISTS 발행 및 스키마가 이미 중복 객체 정보를 다음 예고 (안 오류)가 존재한다 발생합니다.

이러한 문제를 해결하려면 dblink데이터베이스 서버에 대한 새 연결을 열고 트랜잭션에 들어 가지 않고 쿼리를 실행하는 확장 을 사용해야 합니다. 빈 문자열을 제공하여 연결 매개 변수를 재사용 할 수 있습니다.

다음은에서 와 같은 동작으로 PL/pgSQL완전히 시뮬레이션하는 코드 CREATE DATABASE IF NOT EXISTS입니다 CREATE SCHEMA IF NOT EXISTS. , catch 예외 (데이터베이스가 이미 존재하는 경우 발행 됨) CREATE DATABASE를 통해 호출 하고 전파하는 알림으로 변환합니다 . 문자열 메시지는 같은 방식으로 추가 되었습니다 .dblinkduplicate_databaseerrcode, skippingCREATE SCHEMA IF NOT EXISTS

CREATE EXTENSION IF NOT EXISTS dblink;

DO $$
BEGIN
PERFORM dblink_exec('', 'CREATE DATABASE testdb');
EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
END
$$;

이 솔루션은 데이터베이스가 존재하는지 확인하고 자체 생성 사이에 외부 프로세스 (또는 동일한 스크립트의 다른 인스턴스)에 의해 데이터베이스를 생성 할 수있는 다른 답변과 같이 경쟁 조건이 없습니다.

또한 CREATE DATABASE데이터베이스 이외의 다른 오류로 인해 실패하면이 오류가 오류로 전파되고 자동으로 삭제되지 않습니다. duplicate_database오류 만 포착 할 수 있습니다. 그래서 그것은 정말로 IF NOT EXISTS해야하는 대로 작동 합니다.

이 코드를 자체 함수에 넣거나 직접 호출하거나 트랜잭션에서 호출 할 수 있습니다. 롤백 (삭제 된 데이터베이스 복원)은 작동하지 않습니다.

출력 테스트 (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=# CREATE EXTENSION IF NOT EXISTS dblink;
CREATE EXTENSION
postgres=# DO $$
postgres$# BEGIN
postgres$# PERFORM dblink_exec('', 'CREATE DATABASE testdb');
postgres$# EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
DO
postgres=# 
postgres=# CREATE EXTENSION IF NOT EXISTS dblink;
NOTICE:  42710: extension "dblink" already exists, skipping
LOCATION:  CreateExtension, extension.c:1539
CREATE EXTENSION
postgres=# DO $$
postgres$# BEGIN
postgres$# PERFORM dblink_exec('', 'CREATE DATABASE testdb');
postgres$# EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
NOTICE:  42P04: database "testdb" already exists, skipping
LOCATION:  exec_stmt_raise, pl_exec.c:3165
DO
postgres=# 
postgres=# CREATE DATABASE testdb;
ERROR:  42P04: database "testdb" already exists
LOCATION:  createdb, dbcommands.c:467

1
이것은 현재 여기에서 유일한 정답이며, 경합 상태를 겪지 않고 필요한 선택적 오류 처리를 사용합니다. 이 답변이 (완전히 정확하지 않음) 상위 답변이 70 점 이상을 모은 후에 나온 것은 정말 유감입니다.
vog

2
글쎄, 다른 답변은 발생할 수있는 모든 가능한 코너 케이스를 처리하기에 그렇게 정확하지 않습니다. 또한 내 PL / pgSQL 코드를 병렬로 더 많이 호출 할 수 있으며 실패하지 않습니다.
Pali

1

쉘을 사용할 수 있다면

psql -U postgres -c 'select 1' -d $DB &>dev/null || psql -U postgres -tc 'create database $DB'

나는 psql -U postgres -c "select 1" -d $DB더 쉽다고 생각 SELECT 1 FROM pg_database WHERE datname = 'my_db'하고 결합하기 쉬운 한 가지 유형의 견적 만 필요합니다.sh -c 합니다.

나는 내 ansible 작업에서 이것을 사용합니다.

- name: create service database
  shell: docker exec postgres sh -c '{ psql -U postgres -tc "SELECT 1" -d {{service_name}} &> /dev/null && echo -n 1; } || { psql -U postgres -c "CREATE DATABASE {{service_name}}"}'
  register: shell_result
  changed_when: "shell_result.stdout != '1'"

0

createdbCLI 도구를 사용하여 데이터베이스를 만드십시오 .

PGHOST="my.database.domain.com"
PGUSER="postgres"
PGDB="mydb"
createdb -h $PGHOST -p $PGPORT -U $PGUSER $PGDB

데이터베이스가있는 경우 오류를 반환합니다.

createdb: database creation failed: ERROR:  database "mydb" already exists

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.