Simulasikan BUAT DATABASE JIKA TIDAK ADA untuk PostgreSQL?


115

Saya ingin membuat database yang tidak ada melalui JDBC. Tidak seperti MySQL, PostgreSQL tidak mendukung create if not existssintaks. Apa cara terbaik untuk mencapai ini?

Aplikasi tidak mengetahui apakah database tersebut ada atau tidak. Itu harus memeriksa dan jika database ada itu harus digunakan. Jadi masuk akal untuk menghubungkan ke database yang diinginkan dan jika koneksi gagal karena tidak adanya database, maka harus membuat database baru (dengan menghubungkan ke postgresdatabase default ). Saya memeriksa kode kesalahan yang dikembalikan oleh Postgres tetapi saya tidak dapat menemukan kode relevan yang memiliki spesies yang sama.

Metode lain untuk mencapai ini adalah dengan menghubungkan ke postgresdatabase dan memeriksa apakah database yang diinginkan ada dan mengambil tindakan yang sesuai. Yang kedua agak membosankan untuk dikerjakan.

Apakah ada cara untuk mencapai fungsi ini di Postgres?

Jawaban:


111

Batasan

Anda dapat meminta katalog sistem pg_database- dapat diakses dari database mana pun di cluster database yang sama. Bagian yang sulit adalah yang CREATE DATABASEhanya dapat dijalankan sebagai satu pernyataan. Manualnya:

CREATE DATABASE tidak dapat dieksekusi di dalam blok transaksi.

Jadi tidak bisa dijalankan langsung di dalam fungsi atau DO pernyataan, di mana itu akan berada di dalam blok transaksi secara implisit.

(Prosedur SQL, diperkenalkan dengan Postgres 11, tidak dapat membantu dengan ini juga .)

Solusi dari dalam psql

Anda dapat mengatasinya dari dalam psql dengan menjalankan pernyataan DDL secara bersyarat:

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

Manualnya:

\gexec

Mengirim buffer kueri saat ini ke server, kemudian memperlakukan setiap kolom dari setiap baris dari output kueri (jika ada) sebagai pernyataan SQL yang akan dieksekusi.

Solusi dari shell

Dengan \gexecAnda hanya perlu memanggil psql sekali :

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

Anda mungkin membutuhkan lebih banyak opsi psql untuk koneksi Anda; role, port, password, ... Lihat:

Hal yang sama tidak dapat dipanggil dengan psql -c "SELECT ...\gexec"karena \gexecadalah perintah psql meta ‑ dan -copsi mengharapkan satu perintah yang dinyatakan manual:

commandharus berupa string perintah yang dapat diurai sepenuhnya oleh server (yaitu, tidak berisi fitur khusus psql), atau satu perintah garis miring terbalik. Dengan demikian Anda tidak dapat mencampur meta-command SQL dan psql dalam sebuah -copsi.

Solusi dari dalam transaksi Postgres

Anda bisa menggunakan dblinkkoneksi kembali ke database saat ini, yang berjalan di luar blok transaksi. Oleh karena itu, Efek juga tidak dapat diputar kembali.

Instal dblink modul tambahan untuk ini (sekali per database):

Kemudian:

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

Sekali lagi, Anda mungkin membutuhkan lebih banyak opsi psql untuk koneksi tersebut. Lihat jawaban tambahan Ortwin:

Penjelasan mendetail untuk dblink:

Anda dapat membuat fungsi ini untuk penggunaan berulang.


Saya mengalami masalah dengan ini saat membuat database di AWS RDS Postgres dari jarak jauh. Pengguna master RDS bukan pengguna super dan karenanya tidak diizinkan untuk menggunakan dblink_connect.
Ondrej Burkert

Jika Anda tidak memiliki hak superuser, Anda dapat menggunakan kata sandi untuk koneksi tersebut. Detail: dba.stackexchange.com/a/105186/3684
Erwin Brandstetter

Bekerja seperti pesona, digunakan dalam skrip init.sql di dalam container Docker. Terima kasih!
Micheal J. Roberts

Saya harus melepaskannya \gexecsaat menjalankan kueri pertama dari shell, tetapi berhasil.
FilBot3

117

alternatif lain, untuk berjaga-jaga jika Anda ingin memiliki skrip shell yang membuat database jika tidak ada dan jika tidak, simpan saja apa adanya:

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

Saya menemukan ini berguna dalam skrip penyediaan devops, yang mungkin ingin Anda jalankan beberapa kali dalam contoh yang sama.


Itu tidak berhasil untuk saya. 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.Apa kesalahan yang telah aku perbuat ?
Anton Anikeev

2
Anda tidak grepmenghalangi jalan Anda. Di Windows, greptidak diinstal secara default. Anda dapat mencari untuk gnu grep windowsmenemukan versi yang dapat bekerja di Windows.
Batang

Terima kasih @Rod. Setelah saya menginstal grep, skrip ini berfungsi untuk saya.
Anton Anikeev

@AntonAnikeev: Dapat dilakukan dengan satu panggilan psql tanpa grep. Saya menambahkan solusi untuk jawaban saya.
Erwin Brandstetter

1
Saya merasa berguna untuk pertama-tama kami pg_isready untuk memeriksa bahwa koneksi dimungkinkan; jika koneksi tidak tersedia (salah nama host, jaringan down dll), skrip akan mencoba membuat database dan akan gagal dengan pesan kesalahan yang mungkin membingungkan
Oliver

8

Saya harus menggunakan versi yang sedikit diperpanjang @Erwin Brandstetter digunakan:

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$

Saya harus mengaktifkan dblinkekstensi, ditambah lagi saya harus memberikan kredensial untuk dblink. Bekerja dengan Postgres 9.4.


7

Jika Anda tidak peduli dengan datanya, Anda dapat melepaskan database terlebih dahulu, lalu membuatnya kembali:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

Solusi yang sangat elegan. Tapi jangan lupa untuk membuat cadangan database terlebih dahulu jika Anda benar-benar peduli dengan datanya. Untuk situasi pengujian meskipun ini adalah solusi pilihan saya.
Laryx Decidua

6

PostgreSQL tidak mendukung IF NOT EXISTSuntuk CREATE DATABASEpernyataan. Ini hanya didukung di CREATE SCHEMA. Apalagi CREATE DATABASEtidak bisa dikeluarkan dalam transaksi sehingga tidak bisa di DOblok dengan exception catching.

Kapan CREATE SCHEMA IF NOT EXISTS dikeluarkan dan skema sudah ada maka pemberitahuan (bukan kesalahan) dengan informasi objek duplikat dimunculkan.

Untuk mengatasi masalah ini, Anda perlu menggunakan dblinkekstensi yang membuka koneksi baru ke server database dan menjalankan kueri tanpa melakukan transaksi. Anda dapat menggunakan kembali parameter koneksi dengan menyediakan string kosong.

Di bawah ini adalah PL/pgSQLkode yang sepenuhnya disimulasikan CREATE DATABASE IF NOT EXISTSdengan perilaku yang sama seperti di CREATE SCHEMA IF NOT EXISTS. Ini memanggil CREATE DATABASEmelalui dblink, catch duplicate_databaseexception (yang dikeluarkan ketika database sudah ada) dan mengubahnya menjadi notice dengan menyebarkan errcode. Pesan string telah ditambahkan , skippingdengan cara yang sama seperti yang dilakukannya 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
$$;

Solusi ini tanpa kondisi balapan seperti di jawaban lain, di mana database dapat dibuat dengan proses eksternal (atau contoh lain dari skrip yang sama) antara memeriksa apakah database ada dan pembuatannya sendiri.

Selain itu ketika CREATE DATABASEgagal dengan kesalahan selain database sudah ada maka kesalahan ini disebarkan sebagai kesalahan dan tidak diam-diam dibuang. Hanya ada menangkap duplicate_databasekesalahan. Jadi benar-benar berperilaku sebagaimana IF NOT EXISTSmestinya.

Anda dapat memasukkan kode ini ke dalam fungsi sendiri, memanggilnya langsung atau dari transaksi. Hanya rollback (mengembalikan database yang jatuh) tidak akan berfungsi.

Menguji keluaran (dipanggil dua kali melalui DO dan kemudian secara langsung):

$ 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
Saat ini ini adalah satu-satunya jawaban yang benar di sini, yang tidak terpengaruh oleh kondisi balapan, dan menggunakan penanganan kesalahan selektif yang diperlukan. Sangat disayangkan bahwa jawaban ini muncul setelah jawaban teratas (tidak sepenuhnya benar) mengumpulkan lebih dari 70 poin.
vog

2
Jawaban lain tidak terlalu tepat untuk menangani semua kemungkinan kasus sudut yang mungkin terjadi. Anda juga dapat memanggil kode PL / pgSQL saya lebih banyak secara paralel dan tidak gagal.
Pali

1

Jika Anda bisa menggunakan shell, coba

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

Menurut saya psql -U postgres -c "select 1" -d $DBlebih mudah daripada SELECT 1 FROM pg_database WHERE datname = 'my_db', dan hanya membutuhkan satu jenis kutipan, lebih mudah digabungkan sh -c.

Saya menggunakan ini dalam tugas saya yang mungkin

- 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

Cukup buat database menggunakan createdbalat CLI:

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

Jika database ada, itu akan mengembalikan kesalahan:

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.