如果不存在PostgreSQL,可以模拟CREATE DATABASE吗?


115

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

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

实现此目的的另一种方法是连接到postgres数据库,并检查所需的数据库是否存在,并采取相应的措施。第二个要解决一些麻烦。

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

Answers:


111

限制条件

您可以询问系统目录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的详细说明:

您可以将此功能重复使用。


从远程在AWS RDS Postgres上创建数据库时,我遇到了问题。RDS主用户不是超级用户,因此不允许使用dblink_connect
Ondrej Burkert

如果您没有超级用户特权,则可以使用密码进行连接。详细信息:dba.stackexchange.com/a/105186/3684
Erwin Brandstetter

在Docker容器内的init.sql脚本中使用时,就像是一种魅力。谢谢!
Micheal J. Roberts

\gexec当我从外壳程序运行第一个查询时,我不得不删除,但是它起作用了。
FilBot3

117

另一种选择,以防万一您想拥有一个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:\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:可以通过一个没有grep的psql调用来完成。我在答案中添加了解决方案。
欧文·布兰德斯特

1
我发现首先使用pg_isready来检查是否可以建立连接很有用;如果连接不可用(错误的主机名,网络断开等),该脚本将尝试创建数据库,并且将失败并显示可能引起混淆的错误消息
Oliver

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 EXISTSCREATE 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通过via dblink,catch duplicate_database异常(在数据库已经存在时发出)进行调用,并将其通过propagating转换为notice 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

如果可以使用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

我在忙碌的任务中使用它

- 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

只需使用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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.