PostgreSQLの存在しない場合、CREATE DATABASEをシミュレートしますか?


115

JDBCでは存在しないデータベースを作りたい。MySQLとは異なり、PostgreSQLはcreate if not exists構文をサポートしていません。これを達成するための最良の方法は何ですか?

アプリケーションは、データベースが存在するかどうかを認識しません。それはチェックする必要があり、データベースが存在する場合はそれを使用する必要があります。したがって、目的のデータベースに接続することは理にかなっており、データベースが存在しないために接続が失敗した場合は、(デフォルトのpostgresデータベースに接続することによって)新しいデータベースを作成する必要があります。Postgresから返されたエラーコードを確認しましたが、同じ種類の関連コードは見つかりませんでした。

これを実現する別の方法は、postgresデータベースに接続し、目的のデータベースが存在するかどうかを確認し、それに応じてアクションを実行することです。2つ目は、作業するのが少し面倒です。

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 -c "SELECT ...\gexec"ので、\gexecpsqlのメタコマンドで、-cオプションは単一期待するコマンドをそのためのマニュアルの状態:

commandサーバーによって完全に解析可能なコマンド文字列(つまり、psql固有の機能を含まない)または単一のバックスラッシュコマンドのいずれかでなければなりません。したがって、-cオプション内でSQLとpsqlメタコマンドを混在させることはできません。

Postgresトランザクション内からの回避策

dblinkトランザクションブロックの外部で実行されている現在のデータベースへの接続を使用できます。したがって、エフェクトをロールバックすることもできません。

このための追加モジュールdblinkをインストールします(データベースごとに1回)。

次に:

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 2018

スーパーユーザー権限がない場合は、接続にパスワードを使用できます。詳細:dba.stackexchange.com/a/105186/3684
Erwin Brandstetter 2018

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.私は何を間違えましたか?
アントン

2
あなたはgrepあなたの道にありません。Windowsでは、grepはデフォルトではインストールされません。gnu grep windowsWindowsで動作するバージョンを検索して見つけることができます。
ロッド

Thx @Rod。grepをインストールした後、このスクリプトはうまくいきました。
アントン・アニケエフ2017

@AntonAnikeev:grepなしで単一のpsql呼び出しで実行できます。解答を追加しました。
Erwin Brandstetter

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。でのみサポートされていCREATE SCHEMAます。さらにCREATE DATABASE、トランザクション内で発行することはできないため、DO例外をキャッチしてブロックにすることはできません。

いつ CREATE SCHEMA IF NOT EXISTS発行され、スキーマがすでに重複するオブジェクト情報と、その後の通知(ないエラー)が存在する場合に発生します。

これらの問題を解決するにはdblink、データベースサーバーへの新しい接続を開き、トランザクションを開始せずにクエリを実行する拡張機能を使用する必要があります。空の文字列を指定すると、接続パラメーターを再利用できます。

以下は、のような同じ動作PL/pgSQLで完全にシミュレーションCREATE DATABASE IF NOT EXISTSするコードですCREATE SCHEMA IF NOT EXISTSです。CREATE DATABASEviaを呼び出してdblinkduplicate_database例外をキャッチし(データベースが既に存在する場合に発行されます)、それを伝達して通知に変換しerrcodeます。文字列メッセージは, skipping、同じように追加されますCREATE 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を介して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=# 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ポイントを超えて収集した後に表示されたのは本当に残念です。
ヴォグ

2
まあ、他の答えは起こりうるすべてのありふれたケースを処理するにはそれほど正確ではありません。また、PL / pgSQLコードを複数回並行して呼び出すこともでき、失敗しません。
パリ

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'、1種類の見積もりの​​みが必要で、と組み合わせるのが簡単だと思います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.