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?
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 .)
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
\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.
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.
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é.
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.
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.
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
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;
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