PostgreSQL için MEVCUT DEĞİLSE VERİTABANI OLUŞTURMA Simülasyonu?


115

JDBC aracılığıyla var olmayan bir veritabanı oluşturmak istiyorum. MySQL'den farklı olarak, PostgreSQL create if not existssözdizimini desteklemez . Bunu başarmanın en iyi yolu nedir?

Uygulama, veritabanının var olup olmadığını bilmez. Kontrol edilmeli ve veri tabanı var mı kullanılmalıdır. Bu nedenle, istenen veritabanına bağlanmak mantıklıdır ve veritabanının olmaması nedeniyle bağlantı başarısız olursa, yeni veritabanı oluşturmalıdır (varsayılan postgresveritabanına bağlanarak ). Postgres tarafından döndürülen hata kodunu kontrol ettim ancak aynı türden ilgili herhangi bir kod bulamadım.

Bunu başarmanın başka bir yöntemi de postgresveritabanına bağlanmak ve istenen veritabanının var olup olmadığını kontrol etmek ve buna göre işlem yapmaktır. İkincisi, çalışmak biraz sıkıcı.

Postgres'te bu işlevi elde etmenin herhangi bir yolu var mı?

Yanıtlar:


111

Kısıtlamalar

pg_databaseAynı veritabanı kümesindeki herhangi bir veritabanından erişilebilen sistem kataloğuna sorabilirsiniz . İşin zor yanı, CREATE DATABASEyalnızca tek bir ifade olarak yürütülebilmesidir. Kullanım kılavuzu:

CREATE DATABASE bir işlem bloğu içinde yürütülemez.

Dolayısıyla DO, dolaylı olarak bir işlem bloğunun içinde olacağı bir işlev veya deyim içinde doğrudan çalıştırılamaz .

(Postgres 11 ile tanıtılan SQL prosedürleri de bu konuda yardımcı olamaz .)

Psql içinden geçici çözüm

DDL ifadesini koşullu olarak çalıştırarak psql içinden bu sorunu çözebilirsiniz:

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

Kullanım kılavuzu:

\gexec

Geçerli sorgu arabelleğini sunucuya gönderir, ardından sorgunun çıktısının her satırının her sütununu (varsa) yürütülecek bir SQL ifadesi olarak değerlendirir.

Kabuktan geçici çözüm

İle \gexecyalnızca çağrı psql'in gerekir kez :

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

Bağlantınız için daha fazla psql seçeneğine ihtiyacınız olabilir; rol, bağlantı noktası, şifre, ... Bakınız:

Aynı ile çağrılamaz psql -c "SELECT ...\gexec"çünkü \gexecbir psql'in meta komuttur ve -copsiyon tek beklediği komutu hangi manuel devletler:

commandya sunucu tarafından tamamen ayrıştırılabilen bir komut dizesi (yani, psql'ye özgü özellik içermeyen) ya da tek bir ters eğik çizgi komutu olmalıdır. Bu nedenle, SQL ve psql meta komutlarını bir -cseçenek içinde karıştıramazsınız .

Postgres işlemi içinden geçici çözüm

dblinkİşlem bloğunun dışında çalışan mevcut veritabanına bir bağlantı kullanabilirsiniz . Bu nedenle efektler geri alınamaz.

Bunun için ek modül dblink'ini kurun (veritabanı başına bir kez):

Sonra:

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

Yine, bağlantı için daha fazla psql seçeneğine ihtiyacınız olabilir. Ortwin'in eklediği cevaba bakın:

Dblink için ayrıntılı açıklama:

Bunu tekrarlanan kullanım için bir işlev yapabilirsiniz.


AWS RDS Postgres üzerinde uzaktan bir veritabanı oluştururken bu sorunla karşılaştım. RDS ana kullanıcısı bir süper kullanıcı değildir ve bu nedenle kullanmasına izin verilmez.dblink_connect .
Ondrej Burkert

Süper kullanıcı ayrıcalıklarına sahip değilseniz, bağlantı için bir parola kullanabilirsiniz. Detaylar: dba.stackexchange.com/a/105186/3684
Erwin Brandstetter

Docker kapsayıcısı içindeki init.sql betiğinde kullanılan bir cazibe gibi çalıştı. Teşekkürler!
Micheal J. Roberts

\gexecİlk sorguyu kabuktan çalıştırdığımda bırakmak zorunda kaldım , ama işe yaradı.
FilBot3

117

başka bir alternatif, eğer mevcut değilse veritabanını oluşturan ve aksi halde olduğu gibi tutan bir kabuk betiğine sahip olmak istemeniz durumunda:

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

Bunu, aynı örnek üzerinde birden çok kez çalıştırmak isteyebileceğiniz devops ön hazırlık betikleri için yararlı buldum.


Benim için çalışmıyor. 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.Neyi yanlış yaptım ?
Anton Anikeev

2
Yolunda yok grep. Windows'ta grepvarsayılan olarak yüklenmez. gnu grep windowsWindows'ta çalışabilecek bir sürüm bulmak için arama yapabilirsiniz .
Rod

Thx @Rod. Grep'i yükledikten sonra bu komut dosyası benim için çalıştı.
Anton Anikeev

@AntonAnikeev: Grep olmadan tek bir psql çağrısı ile yapılabilir. Cevabıma çözümler ekledim.
Erwin Brandstetter

1
Önce pg_isready bir bağlantının mümkün olup olmadığını kontrol etmeyi yararlı buluyorum; bir bağlantı mevcut değilse (yanlış ana bilgisayar adı, ağ kapalı vb.), komut dosyası veritabanını oluşturmaya çalışır ve muhtemelen kafa karıştırıcı bir hata mesajı vererek başarısız olur
Oliver,

8

@Erwin Brandstetter'ın biraz daha genişletilmiş bir sürümünü kullanmak zorunda kaldım:

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$

dblinkUzantıyı etkinleştirmem gerekiyordu , ayrıca dblink için kimlik bilgilerini sağlamam gerekiyordu. Postgres 9.4 ile çalışır.


7

Veriler umurunuzda değilse, önce veritabanını bırakabilir ve ardından yeniden oluşturabilirsiniz:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

Çok zarif çözüm. Sadece eğer ilk veritabanını yedeklemek unutmayın yapmak veriler hakkında bakım. Test durumları için tercih ettiğim çözüm bu olsa da.
Laryx Desidua

6

PostgreSQL desteklemediği IF NOT EXISTSiçin CREATE DATABASEdeyimi. Yalnızca içinde desteklenir CREATE SCHEMA. Üstelik CREATE DATABASEişlemde ihraç edilemez, dolayısıyla DOistisnai yakalama dışında blok halinde olamaz .

Ne zaman CREATE SCHEMA IF NOT EXISTS yayınlandı ve şema zaten mevcutsa, yinelenen nesne bilgileriyle ilgili bir uyarı (hata değil) ortaya çıkar.

Bu sorunları çözmek dblinkiçin, veritabanı sunucusuna yeni bir bağlantı açan ve işleme girmeden sorgu yürüten uzantıyı kullanmanız gerekir . Boş dizge sağlayarak bağlantı parametrelerini yeniden kullanabilirsiniz.

Aşağıda, içindeki gibi aynı davranışı PL/pgSQLtamamen simüle eden kod bulunmaktadır . Aracılığıyla , catch istisnasını çağırır (veritabanı zaten mevcut olduğunda verilir) ve bunu yayarak bildirime dönüştürür . Dize mesajı eklenmiş olan öyle nasıl aynı şekilde .CREATE DATABASE IF NOT EXISTSCREATE SCHEMA IF NOT EXISTSCREATE DATABASEdblinkduplicate_databaseerrcode, skippingCREATE 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
$$;

Bu çözüm, diğer yanıtlarda olduğu gibi herhangi bir yarış koşulu içermez; burada veritabanı, veritabanının var olup olmadığını kontrol etmekle kendi yaratımı arasında harici işlem (veya aynı komut dosyasının başka bir örneği) tarafından oluşturulabilir.

Ayrıca, CREATE DATABASEveri tabanı dışındaki başka bir hatayla başarısız olduğunda, bu hata hata olarak yayılır ve sessizce atılmaz. Sadece duplicate_databasehata için yakalama var . Yani gerçekten IF NOT EXISTSolması gerektiği gibi davranıyor .

Bu kodu kendi işlevine koyabilir, doğrudan veya işlemden çağırabilirsiniz. Sadece geri alma (bırakılan veritabanını geri yükleme) çalışmaz.

Test çıkışı (DO aracılığıyla iki kez ve ardından doğrudan çağrılır):

$ 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
Bu şu anda buradaki tek doğru cevaptır, yarış koşullarından etkilenmez ve gerekli seçici hata işlemeyi kullanır. Bu cevabın (tam olarak doğru değil) en iyi cevabın 70 puandan fazla toplandıktan sonra ortaya çıkması gerçekten üzücü.
vog

2
Diğer yanıtlar, olabilecek tüm olası köşe durumlarını ele alacak kadar kesin değildir. Ayrıca PL / pgSQL kodumu paralel olarak daha çok kez çağırabilirsiniz ve bu başarısız olmaz.
Pali

1

Shell kullanabiliyorsanız, deneyin

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

bence psql -U postgres -c "select 1" -d $DB daha kolay SELECT 1 FROM pg_database WHERE datname = 'my_db've sadece bir tür alıntıya ihtiyaç var, birleştirmesi daha kolay sh -c.

Bunu cevaplanabilir görevimde kullanıyorum

- 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

Sadece createdbCLI aracını kullanarak veritabanını oluşturun :

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

Veritabanı mevcutsa, bir hata döndürür:

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.