Simulieren Sie CREATE DATABASE, wenn es für PostgreSQL nicht existiert?


115

Ich möchte eine Datenbank erstellen, die nicht über JDBC existiert. Im Gegensatz zu MySQL unterstützt PostgreSQL keine create if not existsSyntax. Was ist der beste Weg, um dies zu erreichen?

Die Anwendung weiß nicht, ob die Datenbank vorhanden ist oder nicht. Es sollte prüfen, ob die Datenbank vorhanden ist, und sie sollte verwendet werden. Daher ist es sinnvoll, eine Verbindung zur gewünschten Datenbank herzustellen. Wenn die Verbindung aufgrund fehlender Datenbank fehlschlägt, sollte eine neue Datenbank erstellt werden (indem eine Verbindung zur Standarddatenbank hergestellt wird postgres). Ich habe den von Postgres zurückgegebenen Fehlercode überprüft, konnte jedoch keinen relevanten Code für dieselbe Art finden.

Eine andere Methode, um dies zu erreichen, besteht darin, eine Verbindung zur postgresDatenbank herzustellen und zu überprüfen, ob die gewünschte Datenbank vorhanden ist, und entsprechende Maßnahmen zu ergreifen. Der zweite ist etwas mühsam zu trainieren.

Gibt es eine Möglichkeit, diese Funktionalität in Postgres zu erreichen?

Antworten:


110

Beschränkungen

Sie können den Systemkatalog anfordern, pg_databaseauf den von jeder Datenbank im selben Datenbankcluster aus zugegriffen werden kann. Der schwierige Teil ist, dass CREATE DATABASEnur als einzelne Anweisung ausgeführt werden kann. Das Handbuch:

CREATE DATABASE kann nicht innerhalb eines Transaktionsblocks ausgeführt werden.

Es kann also nicht direkt in einer Funktion oder DOAnweisung ausgeführt werden, wo es sich implizit in einem Transaktionsblock befindet.

(SQL-Prozeduren, die mit Postgres 11 eingeführt wurden, können auch hier nicht helfen .)

Problemumgehung innerhalb von psql

Sie können dies in psql umgehen, indem Sie die DDL-Anweisung unter bestimmten Bedingungen ausführen:

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

Das Handbuch:

\gexec

Sendet den aktuellen Abfragepuffer an den Server und behandelt dann jede Spalte jeder Zeile der Ausgabe der Abfrage (falls vorhanden) als auszuführende SQL-Anweisung.

Problemumgehung von der Shell

Mit müssen \gexecSie psql nur einmal aufrufen :

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

Möglicherweise benötigen Sie weitere psql-Optionen für Ihre Verbindung. Rolle, Port, Passwort, ... Siehe:

Dasselbe kann nicht aufgerufen werden, psql -c "SELECT ...\gexec"da \gexeces sich um einen psql-Meta-Befehl handelt und die -cOption einen einzelnen Befehl erwartet, für den im Handbuch Folgendes angegeben ist:

commandmuss entweder eine Befehlszeichenfolge sein, die vom Server vollständig analysiert werden kann (dh keine psql-spezifischen Funktionen enthält), oder ein einzelner Backslash-Befehl. Daher können Sie keine SQL- und psql-Metabefehle innerhalb einer -cOption mischen .

Problemumgehung innerhalb der Postgres-Transaktion

Sie können eine dblinkVerbindung zurück zur aktuellen Datenbank verwenden, die außerhalb des Transaktionsblocks ausgeführt wird. Effekte können daher auch nicht zurückgesetzt werden.

Installieren Sie dazu das zusätzliche Modul dblink (einmal pro Datenbank):

Dann:

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

Auch hier benötigen Sie möglicherweise mehr psql-Optionen für die Verbindung. Siehe Ortwins hinzugefügte Antwort:

Detaillierte Erklärung für dblink:

Sie können dies zu einer Funktion für die wiederholte Verwendung machen.


Beim Erstellen einer Datenbank auf AWS RDS Postgres von remote aus trat ein Problem auf. Der RDS-Hauptbenutzer ist kein Superuser und darf daher nicht verwendet werden dblink_connect.
Ondrej Burkert

Wenn Sie keine Superuser-Berechtigungen haben, können Sie ein Kennwort für die Verbindung verwenden. Details: dba.stackexchange.com/a/105186/3684
Erwin Brandstetter

Funktionierte wie ein Zauber, der in einem init.sql-Skript im Docker-Container verwendet wurde. Vielen Dank!
Micheal J. Roberts

Ich musste die löschen, \gexecals ich die erste Abfrage von der Shell ausführte, aber es funktionierte.
FilBot3

117

Eine weitere Alternative, nur für den Fall, dass Sie ein Shell-Skript haben möchten, das die Datenbank erstellt, wenn sie nicht vorhanden ist, und sie ansonsten einfach so lässt, wie sie ist:

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

Ich fand dies hilfreich bei der Bereitstellung von Skripten durch Entwickler, die Sie möglicherweise mehrmals über dieselbe Instanz ausführen möchten.


Es funktioniert nicht bei mir. 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.Was habe ich falsch gemacht ?
Anton Anikeev

2
Du hast nicht grepauf deinem Weg. Ist unter Windows grepnicht standardmäßig installiert. Sie können gnu grep windowsnach einer Version suchen, die unter Windows funktioniert.
Rod

Thx @ Rod. Nachdem ich grep installiert hatte, funktionierte dieses Skript für mich.
Anton Anikeev

@AntonAnikeev: Kann mit einem einzigen psql-Aufruf ohne grep durchgeführt werden. Ich habe meiner Antwort Lösungen hinzugefügt.
Erwin Brandstetter

1
Ich finde es nützlich, zuerst pg_isready zu überprüfen, ob eine Verbindung möglich ist; Wenn keine Verbindung verfügbar ist (falscher Hostname, Netzwerkausfall usw.), versucht das Skript, die Datenbank zu erstellen, und schlägt mit möglicherweise verwirrender Fehlermeldung fehl
Oliver

8

Ich musste eine leicht erweiterte Version verwenden @Erwin Brandstetter verwendet:

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$

Ich musste die dblinkErweiterung aktivieren und die Anmeldeinformationen für dblink angeben. Funktioniert mit Postgres 9.4.


8

Wenn Sie sich nicht für die Daten interessieren, können Sie die Datenbank zuerst löschen und dann neu erstellen:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

Sehr elegante Lösung. Nur vergessen Sie nicht zuerst die Datenbank zu sichern , wenn Sie tun die Datenpflege zu. Für Testsituationen ist dies jedoch meine bevorzugte Lösung.
Laryx Decidua

6

PostgreSQL unterstützt nicht IF NOT EXISTSfür CREATE DATABASEAnweisung. Es wird nur in unterstützt CREATE SCHEMA. Darüber hinaus CREATE DATABASEkann es nicht in einer Transaktion ausgegeben werden, daher kann es DOmit Ausnahme des Fangens nicht blockiert werden.

Wenn CREATE SCHEMA IF NOT EXISTSausgegeben wird und das Schema bereits vorhanden ist, wird ein Hinweis (kein Fehler) mit doppelten Objektinformationen ausgelöst.

Um diese Probleme zu lösen, müssen Sie die dblinkErweiterung verwenden, die eine neue Verbindung zum Datenbankserver herstellt und die Abfrage ausführt, ohne eine Transaktion abzuschließen. Sie können Verbindungsparameter wiederverwenden, indem Sie eine leere Zeichenfolge angeben.

Unten finden Sie PL/pgSQLCode, der CREATE DATABASE IF NOT EXISTSmit demselben Verhalten wie in vollständig simuliert CREATE SCHEMA IF NOT EXISTS. Es ruft CREATE DATABASEvia dblink, catch- duplicate_databaseAusnahme auf (die ausgegeben wird, wenn die Datenbank bereits vorhanden ist) und konvertiert sie mit Propagierung in Benachrichtigung errcode. Die String-Nachricht wurde , skippingauf die gleiche Weise angehängt wie sie 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
$$;

Diese Lösung ist ohne Race-Bedingung wie in anderen Antworten, bei denen die Datenbank durch einen externen Prozess (oder eine andere Instanz desselben Skripts) zwischen der Überprüfung, ob eine Datenbank vorhanden ist, und ihrer eigenen Erstellung erstellt werden kann.

Wenn ein CREATE DATABASEFehler mit einem anderen Fehler als der Datenbank bereits vorliegt, wird dieser Fehler als Fehler weitergegeben und nicht stillschweigend verworfen. Es gibt nur duplicate_databaseFehler. Es verhält sich also wirklich so, wie es IF NOT EXISTSsollte.

Sie können diesen Code in eine eigene Funktion einfügen, direkt oder über eine Transaktion aufrufen. Nur ein Rollback (wiederhergestellte Datenbank wiederherstellen) würde nicht funktionieren.

Testausgabe (zweimal über DO und dann direkt aufgerufen):

$ 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
Dies ist derzeit die einzig richtige Antwort hier, die nicht unter Rennbedingungen leidet und die notwendige selektive Fehlerbehandlung verwendet. Es ist wirklich schade, dass diese Antwort erschien, nachdem die (nicht vollständig korrekte) Top-Antwort mehr als 70 Punkte gesammelt hatte.
Vog

2
Nun, andere Antworten sind nicht so präzise, ​​um alle möglichen Eckfälle zu behandeln, die auftreten können. Sie können meinen PL / pgSQL-Code auch mehrmals parallel aufrufen, ohne dass dies fehlschlägt.
Pali

1

Wenn Sie Shell verwenden können, versuchen Sie es

psql -U postgres -c 'select 1' -d $DB &>dev/null || psql -U postgres -tc 'create database $DB'

Ich denke, es psql -U postgres -c "select 1" -d $DBist einfacher als SELECT 1 FROM pg_database WHERE datname = 'my_db'und braucht nur eine Art von Zitat, mit dem man leichter kombinieren kann sh -c.

Ich benutze dies in meiner ansiblen Aufgabe

- 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

Erstellen Sie einfach die Datenbank mit dem createdbCLI-Tool:

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

Wenn die Datenbank vorhanden ist, wird ein Fehler zurückgegeben:

createdb: database creation failed: ERROR:  database "mydb" already exists

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.