模拟CREATE DATABASE如果没有为PostgreSQL提供?

时间:2013-08-22 19:21:09

标签: sql database postgresql jdbc ddl

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

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

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

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

11 个答案:

答案 0 :(得分:102)

另一个替代方案,以防万一你希望有一个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配置脚本​​很有用,您可能希望在同一个实例上多次运行。

答案 1 :(得分:60)

限制

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

  

CREATE DATABASE无法在事务块中执行。

所以它不能直接在函数或DO语句中运行,它会隐含在事务块中。

(SQL程序,Postgres 11引入,cannot help with this either。)

psql中的解决方法

您可以通过有条件地执行DDL语句在psql中解决它:

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

The manual:

  

\gexec

     

将当前查询缓冲区发送到服务器,然后将查询输出的每一行的每一列(如果有)视为要执行的SQL语句。

shell的解决方法

使用\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选项需要一个命令 {{3} }}

  

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的详细说明:

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

答案 2 :(得分:5)

我不得不使用稍微扩展的版本@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。

答案 3 :(得分:3)

如果可以使用shell,请尝试

customers

我认为> customers $ Car [1,2] > [1,2] 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'

答案 4 :(得分:2)

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异常(当数据库已经存在时发出),并通过传播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

答案 5 :(得分:1)

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

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

答案 6 :(得分: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

答案 7 :(得分: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"

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

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

答案 8 :(得分:0)

最好的方法就是运行 SQL。

CREATE DATABASE MY_DATABASE; 

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

答案 9 :(得分:0)

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

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

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

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

export PGHOST=localhost
export PGUSER=user
export PGPASSWORD=p455w0rd
...

答案 10 :(得分:-7)

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