Имитировать СОЗДАТЬ БАЗУ ДАННЫХ, ЕСЛИ НЕ СУЩЕСТВУЕТ для PostgreSQL?

115

Я хочу создать базу данных, которой нет через JDBC. В отличие от MySQL, PostgreSQL не поддерживает create if not existsсинтаксис. Как лучше всего этого добиться?

Приложение не знает, существует база данных или нет. Он должен проверить, существует ли база данных, и ее следует использовать. Таким образом, имеет смысл подключиться к желаемой базе данных, и если соединение не удается из-за отсутствия базы данных, он должен создать новую базу данных (подключившись к postgresбазе данных по умолчанию ). Я проверил код ошибки, возвращаемый Postgres, но не смог найти ни одного соответствующего кода, который бы совпадал.

Другой способ добиться этого - подключиться к postgresбазе данных и проверить, существует ли нужная база данных, и принять соответствующие меры. Второй вариант немного утомителен.

Есть ли способ добиться этой функциональности в Postgres?

Аман Дип Гаутам
источник

Ответы:

112

ограничения

Вы можете запросить системный каталог 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.
Ondrej Burkert
Если у вас нет прав суперпользователя, вы можете использовать пароль для подключения. Подробности: 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"

Я обнаружил, что это полезно в сценариях подготовки DevOps, которые вы можете запускать несколько раз на одном и том же экземпляре.

Андреасл
источник
У меня это не работает. 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.
Род
Спасибо @Rod. После того, как я установил grep, этот скрипт у меня заработал.
Антон Аникеев
@AntonAnikeev: Это можно сделать с помощью одного вызова psql без grep. Я добавил решения к своему ответу.
Эрвин Брандштеттер
1
Я считаю полезным сначала использовать pg_isready, чтобы проверить, возможно ли соединение; если соединение недоступно (неправильное имя хоста, сеть не работает и т. д.), сценарий попытается создать базу данных и завершится ошибкой с возможным запутывающим сообщением об ошибке
Оливер
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 EXISTSfor 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 DATABASEvia dblink, catch duplicate_database(которое выдается, когда база данных уже существует) и преобразует его в уведомление с распространением 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 баллов.
ВОГ
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
Джеймс Вежба
источник