184 votes

Simuler CREATE DATABASE IF NOT EXISTS pour PostgreSQL ?

Je veux 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. Elle doit vérifier et si la base de données existe, elle doit être utilisée. Il est donc logique de se connecter à la base de données souhaitée et si la connexion échoue en raison 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 base de données). J'ai vérifié le code d'erreur renvoyé par Postgres mais je n'ai pas pu trouver de code pertinent de la même espèce.

Une autre méthode pour y parvenir consisterait à se connecter à l'application postgres et vérifier si la base de données souhaitée existe et prendre des mesures en conséquence. La seconde est un peu fastidieuse à mettre en œuvre.

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

180voto

Erwin Brandstetter Points 110228

Restrictions

Vous pouvez demander au catalogue du système pg_database - accessible depuis n'importe quelle base de données du même cluster de bases de données. Le point délicat est que CREATE DATABASE ne peut être exécutée que sous la forme d'une seule déclaration. Le manuel :

CREATE DATABASE ne peut pas être exécuté à l'intérieur d'un bloc de transaction.

Il ne peut donc pas être exécuté directement à l'intérieur d'une fonction ou d'un programme. DO où il se trouverait implicitement à l'intérieur d'un bloc de transaction. Les procédures SQL, introduites avec Postgres 11, Je ne peux pas non plus vous aider dans ce domaine. .

Solution de contournement à partir de psql

Vous pouvez le contourner à partir de psql en exécutant l'instruction DDL de manière conditionnelle :

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

Le manuel :

\gexec

Envoie le tampon de la 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.

Solution de contournement à partir du shell

Avec \gexec vous avez seulement besoin d'appeler psql une fois :

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

Vous pouvez avoir 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" depuis \gexec est une métacommande psql et l'option -c s'attend à un seul commande pour laquelle le manuel indique :

command doit être soit une chaîne de commande qui est complètement analysable par le serveur (c'est-à-dire qu'elle ne contient aucune caractéristique spécifique à psql), soit une commande à simple backslash. Ainsi, vous ne pouvez pas mélanger les méta-commandes SQL et psql dans une commande -c option.

Solution de contournement à partir d'une transaction Postgres

Vous pourriez utiliser un dblink à 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 pouvez avoir besoin de plus d'options psql pour la connexion. Voir la réponse ajoutée par Ortwin :

Explication détaillée pour dblink :

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

145voto

andreasl Points 1459

Une autre alternative, juste au cas où vous voulez avoir un shell script 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 le provisionnement devops scripts, que vous pourriez vouloir exécuter plusieurs fois sur la même instance.

Pour ceux d'entre vous qui souhaitent une explication :

-c = run command in database session, command is given in string
-t = skip header and footer
-q = silent mode for grep 
|| = logical OR, if grep fails to find match run the subsequent command

37voto

Andrey Semakin Points 307

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;

20voto

Pali Points 91

PostgreSQL ne supporte pas IF NOT EXISTS para CREATE DATABASE déclaration. Elle n'est prise en charge que dans CREATE SCHEMA . De plus, CREATE DATABASE ne peut pas être émis en transaction, il ne peut donc pas être en DO avec capture des exceptions.

Cuando CREATE SCHEMA IF NOT EXISTS est émis et que le schéma existe déjà, un avis (et non une erreur) contenant des informations sur les objets en double est émis.

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

Vous trouverez ci-dessous PL/pgSQL qui simule entièrement CREATE DATABASE IF NOT EXISTS avec le même comportement que dans CREATE SCHEMA IF NOT EXISTS . Il appelle CREATE DATABASE via dblink , attrape duplicate_database (qui est émise lorsque la base de données existe déjà) et la convertit en avis avec propagation de l'information. errcode . Le message String a été ajouté , skipping de la même manière qu'il le fait 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 race condition comme dans les 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 de la base de données et sa propre création.

De plus, lorsque CREATE DATABASE échoue avec une autre erreur que celle de la base de données déjà existante, alors cette erreur est propagée comme une erreur et n'est pas ignorée silencieusement. Il n'y a qu'une seule solution pour duplicate_database erreur. Il se comporte donc réellement comme IF NOT EXISTS devrait.

Vous pouvez mettre ce code dans votre propre fonction, l'appeler directement ou depuis une transaction. Un simple rollback (restauration d'une base de données abandonnée) ne fonctionnerait pas.

Test de sortie (appelé deux fois via DO et ensuite 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

12voto

Ortwin Angermeier Points 2520

J'ai dû utiliser une version légèrement étendue que @Erwin Brandstetter a utilisée :

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$

J'ai dû activer le dblink de plus, j'ai dû fournir les informations d'identification pour dblink. Fonctionne avec Postgres 9.4.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X