172

我想通过 JDBC 创建一个不存在的数据库。与 MySQL 不同,PostgreSQL 不支持create if not exists语法。实现这一目标的最佳方法是什么?

应用程序不知道数据库是否存在。它应该检查数据库是否存在,应该使用它。因此,连接到所需的数据库是有意义的,如果由于数据库不存在而导致连接失败,它应该创建新数据库(通过连接到默认postgres数据库)。我检查了 Postgres 返回的错误代码,但找不到任何相同的相关代码。

实现此目的的另一种方法是连接到postgres数据库并检查所需的数据库是否存在并采取相应的措施。第二个工作起来有点乏味。

有没有办法在 Postgres 中实现这个功能?

4

11 回答 11

168

限制

您可以询问系统目录pg_database- 可从同一数据库集群中的任何数据库访问。棘手的部分是CREATE DATABASE只能作为单个语句执行。手册:

CREATE DATABASE不能在事务块内执行。

所以它不能直接在函数或DO语句中运行,它会隐式在事务块中。随 Postgres 11 引入的 SQL 过程也无济于事

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 的功能),或者是单个反斜杠命令。因此,您不能在一个-c选项中混合使用 SQL 和 psql 元命令。

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 选项来进行连接。请参阅 Ortwin 添加的答案:

dblink详解:

您可以将其设为重复使用的功能。

于 2013-08-22T19:25:39.843 回答
137

另一种选择,以防万一您想要一个 shell 脚本,如果数据库不存在则创建数据库,否则保持原样:

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 = run command in database session, command is given in string
-t = skip header and footer
-q = silent mode for grep 
|| = logical OR, if grep fails to find match run the subsequent command
于 2016-04-13T07:47:51.520 回答
31

如果您不关心数据,可以先删除数据库,然后重新创建它:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;
于 2019-03-11T15:36:36.993 回答
17

PostgreSQL 不支持IF NOT EXISTSforCREATE DATABASE语句。它仅在CREATE SCHEMA. 此外CREATE DATABASE,不能在事务中发出,因此它不能在DO异常捕获的情况下阻塞。

CREATE SCHEMA IF NOT EXISTS发出并且模式已经存在时,会引发带有重复对象信息的通知(不是错误)。

要解决这些问题,您需要使用dblink扩展来打开与数据库服务器的新连接并在不进入事务的情况下执行查询。您可以通过提供空字符串重用连接参数。

下面是PL/pgSQL完全模拟CREATE DATABASE IF NOT EXISTS与 in 相同行为的代码CREATE SCHEMA IF NOT EXISTSCREATE DATABASE它通过调用dblink,捕获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
于 2019-05-02T10:29:11.543 回答
11

我不得不使用@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。

于 2016-03-25T11:12:59.680 回答
5

如果可以使用shell,请尝试

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.

我在我的 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'"
于 2020-01-06T18:57:01.350 回答
0

在阅读完所有这些我认为复杂的解决方案后,由于缺少用于创建 postgres 用户的 IF NOT EXIST 选项,这些解决方案非常糟糕,我几乎忘记了有一种简单的方法可以在 shell 级别处理它。尽管这可能不是某些人想要的,但我认为很多人想要简单,而不是创建程序和复杂的结构。

我正在使用 docker,以下是我的 bash 脚本中的重要片段,它们在 devsetup 中加载数据:

execute_psql_command_pipe () {
         $DOCKER_COMMAND exec -it $POSTGRES_CONTAINER bash -c "echo \"$1\"| psql -h localhost -U postgres || echo psql command failed - object likely exists"
}

read -r -d '' CREATE_USER_COMMANDS << EOM
create user User1 WITH PASSWORD 'password';
create user User2 WITH PASSWORD 'password';
EOM

execute_psql_command_pipe "$CREATE_USER_COMMANDS"

它有一些问题,但这是我能找到的让它做我想做的最简单的方法:在脚本的第一遍创建,在存在时继续第二遍。顺便说一句,回显输出没有显示,但命令继续,因为回显命令以 0 退出。

任何命令(如 db create)都可以这样做。对于可能发生的任何其他错误,这显然会失败(或成功,取决于视角),但是您会获得 psql 输出打印机,因此可以添加更多处理。

于 2021-06-03T14:12:56.820 回答
-1

最好的方法就是运行 SQL。

CREATE DATABASE MY_DATABASE; 

如果数据库已经存在,它会抛出“数据库已经存在错误”,你可以做任何你想做的事情,否则它会创建数据库。我认为它不会在您的数据库之上创建一个新数据库。:D

于 2021-06-28T22:50:17.537 回答
-2

我最终使用的一种简单干净的方法:

createdb $DATABASE 2> /dev/null || echo "database already exists"

如果您预计其他错误database "x" already exists显然不会起作用(例如权限被拒绝)。无论如何,如果这是一个问题,人们总是可以在此之前执行此类检查。

不要忘记为 设置值DATABASE,并为createdb命令传递所需的开关。最好你也可以这样做:

export PGHOST=localhost
export PGUSER=user
export PGPASSWORD=p455w0rd
...
于 2021-08-03T21:16:31.567 回答
-2

只需使用createdbCLI 工具创建数据库:

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
于 2019-08-12T16:41:10.010 回答
-18

升级到 PostgreSQL 9.5 或更高版本。如果(不)存在是在 9.5 版中引入的。

于 2018-10-24T12:05:38.767 回答