如何编写SQL脚本以在PostgreSQL 9.1中创建ROLE,但如果它已经存在则不会引发错误?
当前脚本只有:
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
如果用户已存在,则会失败。我喜欢这样的东西:
IF NOT EXISTS (SELECT * FROM pg_user WHERE username = 'my_user')
BEGIN
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
END;
...但是这不起作用 - 纯SQL中似乎不支持IF
。
我有一个批处理文件,可以创建PostgreSQL 9.1数据库,角色和其他一些东西。它调用psql.exe,传入要运行的SQL脚本的名称。到目前为止,所有这些脚本都是纯SQL,我想尽可能避免使用PL / pgSQL等。
答案 0 :(得分:118)
以与您的想法类似的方式简化:
DO
$do$
BEGIN
IF NOT EXISTS (
SELECT -- SELECT list can stay empty for this
FROM pg_catalog.pg_roles
WHERE rolname = 'my_user') THEN
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
END IF;
END
$do$;
(以@a_horse_with_no_name的答案为基础,并在@Gregory's comment后改进。)
例如,与CREATE TABLE
不同,CREATE ROLE
({尚未)没有IF NOT EXISTS
子句。并且不能在纯SQL中执行动态DDL语句。
除了使用另一个PL之外,你的“避免PL / pgSQL”的请求是不可能的。 DO
statement使用plpgsql作为默认过程语言。语法允许省略显式声明:
DO [ LANGUAGE
lang_name
] code
...
lang_name
代码编写的过程语言的名称。如果 省略,默认为plpgsql
。
答案 1 :(得分:39)
或者,如果角色不是任何数据库对象的所有者,则可以使用:
DROP ROLE IF EXISTS my_user;
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
但是只有放弃这个用户才不会造成任何伤害。
答案 2 :(得分:13)
如果在同一Postgres群集(数据库服务器)上同时执行两个此类脚本,则接受的答案会遇到竞争条件,这在持续集成环境中常见的
尝试创建角色并在创建角色时优雅地处理问题通常更安全:
DO $$
BEGIN
CREATE ROLE my_role WITH NOLOGIN;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'not creating role my_role -- it already exists';
END
$$;
答案 3 :(得分:11)
Bash 替代方案(针对 Bash脚本):
psql -h localhost -U postgres -tc "SELECT 1 FROM pg_user WHERE usename = 'my_user'" | grep -q 1 || psql -h localhost -U postgres -c "CREATE ROLE my_user LOGIN PASSWORD 'my_password';"
(这不是问题的答案!只适用于那些可能有用的人)
答案 4 :(得分:8)
这是使用plpgsql的通用解决方案:
CREATE OR REPLACE FUNCTION create_role_if_not_exists(rolename NAME) RETURNS TEXT AS
$$
BEGIN
IF NOT EXISTS (SELECT * FROM pg_roles WHERE rolname = rolename) THEN
EXECUTE format('CREATE ROLE %I', rolename);
RETURN 'CREATE ROLE';
ELSE
RETURN format('ROLE ''%I'' ALREADY EXISTS', rolename);
END IF;
END;
$$
LANGUAGE plpgsql;
用法:
posgres=# SELECT create_role_if_not_exists('ri');
create_role_if_not_exists
---------------------------
CREATE ROLE
(1 row)
posgres=# SELECT create_role_if_not_exists('ri');
create_role_if_not_exists
---------------------------
ROLE 'ri' ALREADY EXISTS
(1 row)
答案 5 :(得分:7)
当您使用9.x时,可以将其包装到DO语句中:
do
$body$
declare
num_users integer;
begin
SELECT count(*)
into num_users
FROM pg_user
WHERE usename = 'my_user';
IF num_users = 0 THEN
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
END IF;
end
$body$
;
答案 6 :(得分:7)
我的团队遇到了一个服务器上有多个数据库的情况,这取决于你连接到哪个数据库,SELECT * FROM pg_catalog.pg_user
没有返回有问题的ROLE,正如@ erwin-brandstetter和@a_horse_with_no_name所提出的那样。执行条件块,然后我们点击role "my_user" already exists
。
不幸的是我们不确定具体的条件,但这个解决方案解决了这个问题:
DO
$body$
BEGIN
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
EXCEPTION WHEN others THEN
RAISE NOTICE 'my_user role exists, not re-creating';
END
$body$
可能更具体地排除其他例外。
答案 7 :(得分:3)
您可以通过解析输出:
在批处理文件中执行此操作SELECT * FROM pg_user WHERE usename = 'my_user'
然后再次运行psql.exe
如果角色不存在。
答案 8 :(得分:1)
建议使用模式的一些答案:检查角色是否不存在,如果不存在,则发出CREATE ROLE
命令。这有一个缺点:竞赛条件。如果其他人在检查和发出CREATE ROLE
命令之间创建了新角色,则CREATE ROLE
显然会失败,并出现致命错误。
为解决上述问题,已经提到了PL/pgSQL
的用法的其他更多答案,无条件发出CREATE ROLE
,然后从该调用中捕获异常。这些解决方案只有一个问题。他们会静默删除任何错误,包括那些由于角色已经存在而不会产生的错误。 CREATE ROLE
还会引发其他错误,并且模拟IF NOT EXISTS
应该仅在角色已经存在时使错误静音。
CREATE ROLE
引发duplicate_object
错误。并且异常处理程序应该只捕获这一错误。正如其他答案所述,将致命错误转换为简单通知是一个好主意。其他PostgreSQL IF NOT EXISTS
命令在其消息中添加了, skipping
,因此为了保持一致,我也在此处添加了它。
这里是用于模拟CREATE ROLE IF NOT EXISTS
的完整SQL代码,具有正确的异常和sqlstate传播:
DO $$
BEGIN
CREATE ROLE test;
EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
END
$$;
测试输出(通过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=# DO $$
postgres$# BEGIN
postgres$# CREATE ROLE test;
postgres$# EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
DO
postgres=#
postgres=# DO $$
postgres$# BEGIN
postgres$# CREATE ROLE test;
postgres$# EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
NOTICE: 42710: role "test" already exists, skipping
LOCATION: exec_stmt_raise, pl_exec.c:3165
DO
postgres=#
postgres=# CREATE ROLE test;
ERROR: 42710: role "test" already exists
LOCATION: CreateRole, user.c:337
答案 9 :(得分:0)
与Simulate CREATE DATABASE IF NOT EXISTS for PostgreSQL?相同的解决方案应该起作用-向CREATE USER …
发送\gexec
。
SELECT 'CREATE USER my_user'
WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec
echo "SELECT 'CREATE USER my_user' WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec" | psql
有关更多详细信息,请参见accepted answer there。
答案 10 :(得分:0)
如果您可以访问 shell,则可以执行此操作。
psql -tc "SELECT 1 FROM pg_user WHERE usename = 'some_use'" | grep -q 1 || psql -c "CREATE USER some_user"
对于那些想要解释的人:
-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