我想创建一个通过JDBC不存在的数据库。与MySQL不同,PostgreSQL不支持create if not exists
语法。实现这一目标的最佳方法是什么?
应用程序不知道数据库是否存在。它应该检查,如果数据库存在,应该使用它。因此,连接到所需的数据库是有意义的,如果连接因数据库不存在而失败,则应创建新的数据库(通过连接到默认的postgres
数据库)。我检查了Postgres返回的错误代码,但是我找不到任何相同的代码。
实现此目的的另一种方法是连接到postgres
数据库并检查所需的数据库是否存在并相应地采取措施。第二个是锻炼有点乏味。
有没有办法在Postgres中实现这个功能?
答案 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。)
您可以通过有条件地执行DDL语句在psql中解决它:
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
选项需要一个命令 {{3} }}
command
必须是服务器可完全解析的命令字符串(即,它不包含psql特定的功能)或单个反斜杠命令。因此,您不能在-c
选项中混合使用SQL和psql元命令。
您可以使用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版中引入的。