web-dev-qa-db-fra.com

Simuler CREATE DATABASE SI PAS EXISTS pour PostgreSQL?

Je souhaite créer une base de données qui n’existe pas via JDBC. Contrairement à MySQL, PostgreSQL ne supporte pas create if not exists syntaxe. Quelle est la meilleure façon d'y parvenir?

L'application ne sait pas si la base de données existe ou non. Il devrait vérifier et si la base de données existe, elle devrait être utilisée. Il est donc logique de se connecter à la base de données souhaitée. Si la connexion échoue du fait de l’inexistence de la base de données, elle doit créer une nouvelle base de données (en se connectant à la base de données par défaut postgres). J'ai vérifié le code d'erreur renvoyé par Postgres, mais je n'ai trouvé aucun code pertinent qui soit identique.

Une autre méthode consiste à se connecter à la base de données postgres, à vérifier si la base de données souhaitée existe et à prendre les mesures qui s’imposent. Le second est un peu fastidieux à régler.

Existe-t-il un moyen de réaliser cette fonctionnalité dans Postgres?

87
Aman Deep Gautam

Les restrictions

Vous pouvez demander le catalogue système pg_database - accessible depuis n'importe quelle base de données du même cluster de bases de données. La partie délicate est que CREATE DATABASE ne peut être exécuté qu’en une seule déclaration. Le manuel:

CREATE DATABASE ne peut pas être exécuté dans un bloc de transaction.

Donc, il ne peut pas être exécuté directement dans une fonction ou une instruction DO , où il se trouverait implicitement dans un bloc de transaction.

(Procédures SQL introduites avec Postgres 11, ne peut pas l’aider non plus .)

Solution de contournement depuis psql

Vous pouvez contourner ce problème depuis psql en exécutant conditionnellement l'instruction DDL:

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

Le manuel:

\gexec

Envoie le tampon de requête en cours au serveur, puis traite chaque colonne de chaque ligne de la sortie de la requête (le cas échéant) comme une instruction SQL à exécuter.

Contournement depuis le shell

Avec \gexec il vous suffit d’appeler psql une fois:

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

Vous aurez peut-être besoin de plus d'options psql pour votre connexion; rôle, port, mot de passe, ... Voir:

La même chose ne peut pas être appelée avec psql -c "SELECT ...\gexec" puisque \gexec est une méta-commande psql et le -c Option attend un seul commande pour lequel le manuel indique:

command doit être une chaîne de commande complètement analysable par le serveur (c'est-à-dire ne contient aucune fonctionnalité spécifique à psql), ou une seule commande de barre oblique inversée. Ainsi, vous ne pouvez pas mélanger les méta-commandes SQL et psql dans un -c option.

Solution de contournement depuis la transaction Postgres

Vous pouvez utiliser une connexion dblink pour revenir à la base de données actuelle, qui s'exécute en dehors du bloc de transaction. Les effets ne peuvent donc pas non plus être annulés.

Installez le module supplémentaire dblink pour cela (une fois par base de données):

Ensuite:

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$;

Encore une fois, vous aurez peut-être besoin de plus d'options psql pour la connexion. Voir la réponse ajoutée par Ortwin:

Explication détaillée de dblink:

Vous pouvez en faire une fonction pour un usage répété.

70
Erwin Brandstetter

une autre alternative, juste au cas où vous voudriez avoir un script Shell qui crée la base de données si elle n’existe pas et sinon la garde telle quelle:

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

J'ai trouvé cela utile dans les scripts de provisioning de devops, que vous pouvez exécuter plusieurs fois sur la même instance.

107
andreasl

Je devais utiliser une version légèrement étendue @Erwin Brandstetter utilisé:

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$

Je devais activer l'extension dblink, plus je devais fournir les informations d'identification pour dblink. Fonctionne avec Postgres 9.4.

6
Ortwin Angermeier

PostgreSQL ne supporte pas IF NOT EXISTS pour CREATE DATABASE déclaration. Il est pris en charge uniquement dans CREATE SCHEMA. De plus CREATE DATABASE ne peut pas être émis en transaction et ne peut donc pas être dans un bloc DO avec interception d’exceptions.

Quand CREATE SCHEMA IF NOT EXISTS est émis et le schéma existe déjà, puis une notification (et non une erreur) avec les informations sur les objets en double est générée.

Pour résoudre ces problèmes, vous devez utiliser l'extension dblink qui ouvre une nouvelle connexion au serveur de base de données et exécuter la requête sans entrer en transaction. Vous pouvez réutiliser les paramètres de connexion en fournissant une chaîne vide.

Ci-dessous est PL/pgSQL code qui simule complètement CREATE DATABASE IF NOT EXISTS avec le même comportement que dans CREATE SCHEMA IF NOT EXISTS. Il appelle CREATE DATABASE via dblink, attraper duplicate_database exception (qui est émise lorsque la base de données existe déjà) et la convertit en notification avec propagation errcode. Le message de chaîne a été ajouté , skipping de la même manière que cela fonctionne 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
$$;

Cette solution est sans aucune condition de concurrence, comme dans d'autres réponses, où la base de données peut être créée par un processus externe (ou une autre instance du même script) entre la vérification de l'existence d'une base de données et sa propre création.

De plus quand CREATE DATABASE échoue avec une erreur autre que la base de données existe déjà, cette erreur est propagée en tant qu'erreur et n'est pas ignorée en mode silencieux. Il n'y a que des captures pour duplicate_database Erreur. Donc, ça se comporte vraiment comme IF NOT EXISTS devrait.

Vous pouvez mettre ce code dans sa propre fonction, l'appeler directement ou à partir d'une transaction. Une simple annulation (restauration de la base de données supprimée) ne fonctionnerait pas.

Test de la sortie (appelé deux fois via DO puis directement):

$ 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

Si vous ne vous souciez pas des données, vous pouvez d'abord supprimer la base de données, puis la recréer:

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

Il suffit de créer la base de données à l'aide de l'outil CLI createdb:

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

Si la base de données existe, une erreur sera renvoyée:

createdb: database creation failed: ERROR:  database "mydb" already exists
0
James Wierzba