Simula CREATE DATABASE SE NON ESISTE per PostgreSQL?


115

Voglio creare un database che non esiste tramite JDBC. A differenza di MySQL, PostgreSQL non supporta la create if not existssintassi. Qual è il modo migliore per farlo?

L'applicazione non sa se il database esiste o meno. Dovrebbe controllare e se il database esiste dovrebbe essere utilizzato. Quindi ha senso connettersi al database desiderato e se la connessione fallisce a causa della non esistenza del database dovrebbe creare un nuovo database (connettendosi al postgresdatabase predefinito ). Ho controllato il codice di errore restituito da Postgres ma non sono riuscito a trovare alcun codice rilevante per la stessa specie.

Un altro metodo per ottenere ciò sarebbe connettersi al postgresdatabase e controllare se il database desiderato esiste e agire di conseguenza. Il secondo è un po 'noioso da risolvere.

C'è un modo per ottenere questa funzionalità in Postgres?

Risposte:


111

restrizioni

È possibile richiedere il catalogo di sistema pg_database, accessibile da qualsiasi database nello stesso cluster di database. La parte difficile è che CREATE DATABASEpuò essere eseguita solo come una singola istruzione. Il manuale:

CREATE DATABASE non può essere eseguito all'interno di un blocco di transazione.

Quindi non può essere eseguito direttamente all'interno di una funzione o di DOun'istruzione, dove sarebbe implicitamente all'interno di un blocco di transazione.

(Anche le procedure SQL, introdotte con Postgres 11, non possono essere d'aiuto .)

Soluzione alternativa dall'interno di psql

Puoi aggirarlo dall'interno di psql eseguendo l'istruzione DDL in modo condizionale:

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

Il manuale:

\gexec

Invia il buffer della query corrente al server, quindi tratta ogni colonna di ogni riga dell'output della query (se presente) come un'istruzione SQL da eseguire.

Soluzione alternativa dalla shell

Con \gexecdevi solo chiamare psql una volta :

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

Potresti aver bisogno di più opzioni psql per la tua connessione; ruolo, porta, password, ... Vedi:

Non è possibile richiamare lo stesso con psql -c "SELECT ...\gexec"poiché \gexecè un meta-comando psql e l' -copzione prevede un singolo comando per il quale il manuale afferma:

commanddeve essere una stringa di comando completamente analizzabile dal server (ovvero, non contiene funzioni specifiche di psql) o un singolo comando backslash. Pertanto non è possibile combinare meta-comandi SQL e psql all'interno di -cun'opzione.

Soluzione dall'interno della transazione Postgres

È possibile utilizzare una dblinkconnessione al database corrente, che viene eseguito al di fuori del blocco delle transazioni. Pertanto, anche gli effetti non possono essere annullati.

Installa il modulo aggiuntivo dblink per questo (una volta per database):

Poi:

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

Di nuovo, potresti aver bisogno di più opzioni psql per la connessione. Vedi la risposta aggiunta di Ortwin:

Spiegazione dettagliata per dblink:

Puoi renderlo una funzione per un uso ripetuto.


Ho riscontrato questo problema durante la creazione di un database su AWS RDS Postgres da remoto. L'utente master RDS non è un super utente e quindi non è autorizzato a utilizzarlo dblink_connect.
Ondrej Burkert

Se non disponi dei privilegi di superutente, puoi utilizzare una password per la connessione. Dettagli: dba.stackexchange.com/a/105186/3684
Erwin Brandstetter

Ha funzionato come un fascino, utilizzato all'interno di uno script init.sql all'interno del contenitore Docker. Grazie!
Micheal J. Roberts

Ho dovuto eliminare \gexecquando ho eseguito la prima query dalla shell, ma ha funzionato.
FilBot3

117

un'altra alternativa, nel caso in cui si desideri avere uno script di shell che crea il database se non esiste e altrimenti lo mantiene così com'è:

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

Ho trovato questo utile negli script di provisioning devops, che potresti voler eseguire più volte sulla stessa istanza.


Non funziona per me. 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.Cos'ho fatto di sbagliato ?
Anton Anikeev

2
Non hai grepsul tuo cammino. Su Windows, grepnon è installato per impostazione predefinita. Puoi cercare per gnu grep windowstrovare una versione che potrebbe funzionare su Windows.
Rod

Grazie @Rod. Dopo aver installato grep, questo script ha funzionato per me.
Anton Anikeev

@AntonAnikeev: può essere eseguito con una singola chiamata psql senza grep. Ho aggiunto soluzioni alla mia risposta.
Erwin Brandstetter

1
Trovo utile prima usare pg_isready per verificare che sia possibile una connessione; se una connessione non è disponibile (nome host sbagliato, rete inattiva, ecc.), lo script tenterà di creare il database e non riuscirà con un messaggio di errore che potrebbe confondere
Oliver

8

Ho dovuto usare una versione leggermente estesa usata da @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$

Ho dovuto abilitare l' dblinkestensione, inoltre ho dovuto fornire le credenziali per dblink. Funziona con Postgres 9.4.


7

Se non ti interessano i dati, puoi prima rilasciare il database e poi ricrearlo:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

Soluzione molto elegante. Basta non dimenticare di eseguire il backup del database prima se si fa attenzione sui dati. Per le situazioni di test però questa è la mia soluzione preferita.
Laryx Decidua

6

PostgreSQL non supporta IF NOT EXISTSper la CREATE DATABASEdichiarazione. È supportato solo in CREATE SCHEMA. Inoltre CREATE DATABASEnon può essere emesso in transazione quindi non può essere in DOblocco con cattura eccezioni.

quando CREATE SCHEMA IF NOT EXISTS viene emesso e lo schema esiste già, viene generato un avviso (non un errore) con informazioni sull'oggetto duplicato.

Per risolvere questi problemi è necessario utilizzare l' dblinkestensione che apre una nuova connessione al server di database ed esegue la query senza entrare in transazione. È possibile riutilizzare i parametri di connessione fornendo una stringa vuota.

Di seguito è riportato il PL/pgSQLcodice che simula completamente CREATE DATABASE IF NOT EXISTScon lo stesso comportamento come in CREATE SCHEMA IF NOT EXISTS. Chiama CREATE DATABASEtramite dblink, catch duplicate_databaseexception (che viene emesso quando il database esiste già) e lo converte in avviso con la propagazione errcode. Il messaggio stringa è stato aggiunto , skippingnello stesso modo in cui lo fa 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
$$;

Questa soluzione è priva di condizioni di competizione come in altre risposte, dove il database può essere creato da un processo esterno (o altra istanza dello stesso script) tra il controllo dell'esistenza del database e la sua creazione.

Inoltre, quando CREATE DATABASEfallisce con un errore diverso da quello del database già esistente, questo errore viene propagato come errore e non viene eliminato silenziosamente. C'è solo un duplicate_databaseerrore. Quindi si comporta davvero come IF NOT EXISTSdovrebbe.

Puoi inserire questo codice nella propria funzione, chiamarlo direttamente o dalla transazione. Il solo rollback (ripristino del database eliminato) non funzionerebbe.

Test dell'output (chiamato due volte tramite DO e poi direttamente):

$ 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
Questa è attualmente l'unica risposta corretta qui, che non soffre di condizioni di gara e utilizza la necessaria gestione selettiva degli errori. È davvero un peccato che questa risposta sia apparsa dopo che la risposta principale (non completamente corretta) ha raccolto più di 70 punti.
vog

2
Ebbene, altre risposte non sono così precise da gestire tutti i possibili casi d'angolo che possono accadere. Puoi anche chiamare il mio codice PL / pgSQL più volte in parallelo e non fallisce.
Pali

1

Se puoi usare shell, prova

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

Penso che psql -U postgres -c "select 1" -d $DBsia più facile SELECT 1 FROM pg_database WHERE datname = 'my_db'e abbia bisogno di un solo tipo di citazione, più facile da combinaresh -c .

Lo uso nel mio compito ansible

- 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

Basta creare il database utilizzando lo createdbstrumento CLI:

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

Se il database esiste, restituirà un errore:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.