web-dev-qa-db-ja.com

PostgreSQLのCREATE DATABASE IF NOT EXISTSをシミュレートしますか?

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

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

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

Postgresでこの機能を実現する方法はありますか?

87

制限事項

システムカタログを要求できます 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 onceを呼び出すだけで済みます。

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

接続にはさらに多くのpsqlオプションが必要になる場合があります。役割、ポート、パスワード、...

psql -c "SELECT ...\gexec"はpsqlメタコマンドであり、\gexecオプションは単一のcommandを必要とするため、-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の詳細な説明:

これを繰り返し使用するための関数にすることができます。

70

別の選択肢として、データベースが存在しない場合はデータベースを作成し、それ以外の場合はそのまま保持するシェルスクリプトが必要な場合に備えて、次のようにします。

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プロビジョニングスクリプトで役立つことがわかりました。同じインスタンスで複数回実行することをお勧めします。

107
andreasl

@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で動作します。

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コードです。 dblinkを介してCREATE DATABASEを呼び出し、duplicate_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
2
Pali

データを気にしない場合は、最初にデータベースを削除してから再作成できます。

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;
1
Andrey Semakin

createdb CLIツールを使用してデータベースを作成するだけです。

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
0
James Wierzba