Імітувати СТВОРИТИ ДАНІ, ЯКЩО НЕ ІСНУЄТЬСЯ для PostgreSQL?


115

Я хочу створити базу даних, якої не існує через JDBC. На відміну від MySQL, PostgreSQL не підтримує create if not existsсинтаксис. Який найкращий спосіб досягти цього?

Програма не знає, існує база даних чи ні. Він повинен перевірити, і чи існує база даних, її слід використовувати. Тому є сенс підключитися до потрібної бази даних, і якщо з’єднання не вдалося через відсутність бази даних, воно повинно створити нову базу даних (підключившись до postgresбази даних за замовчуванням ). Я перевірив код помилки, який повернув Postgres, але не зміг знайти відповідного коду, який є таким самим.

Іншим методом для досягнення цього є підключення до postgresбази даних та перевірка наявності бажаної бази даних та вжиття відповідних заходів. Другий - трохи стомлюючи, щоб відпрацювати.

Чи є якийсь спосіб досягти цієї функціональності в Postgres?

Відповіді:


111

Обмеження

Ви можете запитати системний каталог pg_database- доступний з будь-якої бази даних в одному кластері баз даних. Хитра частина полягає в тому, що CREATE DATABASEможе бути виконана лише як одне твердження. Посібник:

CREATE DATABASE не може бути виконано всередині блоку транзакцій.

Таким чином, його не можна запустити безпосередньо у функції чи DOоператорі, де це було б всередині блоку транзакцій неявно.

(Процедури SQL, запроваджені з Postgres 11, також не можуть допомогти у цьому .)

Обхід зсередини psql

Ви можете обійти його зсередини psql, умовно виконавши оператор DDL:

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

Посібник:

\gexec

Надсилає поточний буфер запитів на сервер, а потім розглядає кожен стовпець кожного рядка виводу запиту (якщо такий є) як оператор SQL, який потрібно виконати.

Обхід із оболонки

З \gexecвам потрібно тільки PSQL виклику один раз :

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

Вам може знадобитися більше опцій psql для вашого з'єднання; роль, порт, пароль, ... Дивіться:

Те ж саме не можна викликати, psql -c "SELECT ...\gexec"оскільки \gexecце мета-команда psql, і -cопція очікує єдиної команди, для якої в інструкції зазначено:

commandповинна бути або командним рядком, який повністю аналізується сервером (тобто не містить специфічних для psql функцій), або однією командою зворотної косої риски. Таким чином, ви не можете змішувати метакоманди SQL та psql в -cопції.

Вирішення в рамках трансакції Postgres

Ви можете використовувати dblinkз'єднання назад до поточної бази даних, яка працює поза блоком транзакцій. Таким чином, ефекти також не можна повернути назад.

Встановіть для цього додатковий модуль dblink (один раз на базу даних):

Тоді:

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

Знову вам може знадобитися більше опцій psql для з'єднання. Див. Додану відповідь Ортвіна:

Детальне пояснення для dblink:

Ви можете зробити цю функцію для багаторазового використання.


Я зіткнувся з проблемою при створенні бази даних на AWS RDS Postgres з віддаленого. Головний користувач RDS не надто корисний користувач, тому його не можна використовувати dblink_connect.
Ондрей Беркерт

Якщо у вас немає привілеїв суперпользователя, ви можете використовувати пароль для з'єднання. Детальніше: dba.stackexchange.com/a/105186/3684
Ервін

Працював як шарм, використовується в скрипті init.sql всередині контейнера Docker. Дякую!
Мішель Дж. Робертс

Мені довелося скинути те, \gexecколи я запустив перший запит із оболонки, але він спрацював.
FilBot3

117

інша альтернатива, на всякий випадок, якщо ви хочете мати сценарій оболонки, який створює базу даних, якщо її не існує, інакше вона просто зберігає її як є:

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

Я виявив, що це корисно в сценаріях надання завдань, які ви можете запустити кілька разів в одному і тому ж екземплярі.


Це не працює для мене. 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.Що я зробив не так?
Антон Анікеєв

2
У вас немає grepна вашому шляху. У Windows grepне встановлено за замовчуванням. Ви можете шукати gnu grep windowsверсію, яка могла б працювати в Windows.
Прут

Thx @ Род. Після того, як я встановив grep, цей сценарій працював на мене.
Антон Анікеєв

@AntonAnikeev: Це можна зробити за допомогою одного дзвінка psql без grep. Я додав рішення у свою відповідь.
Ервін

1
Мені здається корисним спершу використати pg_ вже, щоб перевірити, чи можливе з'єднання; якщо з'єднання недоступне (неправильне ім’я хоста, вимкнено мережу тощо), сценарій спробує створити базу даних і не вдасться, можливо, заплутавши повідомлення про помилку
Олівер

8

Мені довелося використовувати трохи розширену версію @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$

Мені довелося ввімкнути dblinkрозширення, плюс мені довелося надати облікові дані для dblink. Працює з Postgres 9.4.


7

Якщо дані не хвилюються, ви можете спочатку скинути базу даних, а потім відтворити їх:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

Дуже елегантне рішення. Тільки не забудьте зробити резервну копію бази даних першої , якщо ви робите турботу про дані. Для тестування ситуацій, хоча це є моїм кращим рішенням.
Laryx Decidua

6

PostgreSQL не підтримує IF NOT EXISTSдля CREATE DATABASEзаяви. Він підтримується лише в CREATE SCHEMA. Більше того, CREATE DATABASEне може бути видано транзакцією, тому воно не може бути в DOблоці з вилученням виключень.

Коли CREATE SCHEMA IF NOT EXISTSвипущено і схема вже існує, то виникає повідомлення (не помилка) з подвійною інформацією про об'єкт.

Для вирішення цих проблем вам потрібно використовувати dblinkрозширення, яке відкриває нове підключення до сервера баз даних та виконувати запит без укладання транзакцій. Ви можете повторно використовувати параметри з'єднання, подаючи порожню рядок.

Нижче наведено PL/pgSQLкод, який повністю імітує CREATE DATABASE IF NOT EXISTSтаку саму поведінку, як у CREATE SCHEMA IF NOT EXISTS. Він викликає CREATE DATABASEчерез dblink, duplicate_databaseвиключення catch (який видається, коли база даних вже існує) і перетворює його в повідомлення з поширенням errcode. Повідомлення рядка додається , skippingтак само, як це робиться 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
$$;

Це рішення не має жодних умов перегонів, як в інших відповідях, де база даних може бути створена зовнішнім процесом (або іншим екземпляром того ж сценарію) між перевіркою наявності бази даних та її власним створенням.

Більше того, коли CREATE DATABASEпомилки з іншою помилкою, ніж база даних вже існує, ця помилка поширюється як помилка, а не мовчки відкидається. Є лише улов на duplicate_databaseпомилку. Тож воно справді поводиться як IF NOT EXISTSслід.

Ви можете ввести цей код у власну функцію, зателефонувати йому безпосередньо або з транзакції. Просто відкат (відновлення випалої бази даних) не буде працювати.

Тестовий вихід (викликається два рази через DO, а потім безпосередньо):

$ 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
На даний момент тут єдина правильна відповідь, яка не страждає від перегонових умов і використовує необхідні вибіркові поводження з помилками. Дуже шкода, що ця відповідь з’явилася після того, як (не повністю коректна) відповідь набрала більше 70 балів.
vog

2
Ну, а інші відповіді не такі точні, щоб вирішувати всі можливі кутові випадки, які можуть статися. Ви також можете дзвонити мій код PL / pgSQL більше разів паралельно, і він не виходить з ладу.
Палій

1

Якщо ви можете використовувати оболонку, спробуйте

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

Я думаю psql -U postgres -c "select 1" -d $DB, що простіше, ніж SELECT 1 FROM pg_database WHERE datname = 'my_db'і потрібен лише один тип цитат, простіше поєднувати sh -c.

Я використовую це у своєму відповідальному завданні

- 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

Просто створіть базу даних за допомогою createdbінструмента CLI:

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

Якщо база даних існує, вона поверне помилку:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.