EMP*_*EMP 96 sql postgresql roles dynamic-sql
如何编写一个SQL脚本来在PostgreSQL 9.1中创建一个ROLE,但如果它已经存在则不会引发错误?
当前脚本只有:
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
Run Code Online (Sandbox Code Playgroud)
如果用户已存在,则会失败.我喜欢这样的东西:
IF NOT EXISTS (SELECT * FROM pg_user WHERE username = 'my_user')
BEGIN
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
END;
Run Code Online (Sandbox Code Playgroud)
...但是这不起作用 - IF在纯SQL中似乎不支持.
我有一个批处理文件,可以创建PostgreSQL 9.1数据库,角色和其他一些东西.它调用psql.exe,传入要运行的SQL脚本的名称.到目前为止,所有这些脚本都是纯SQL,我想尽可能避免使用PL/pgSQL等.
Erw*_*ter 137
以与您的想法类似的方式简化:
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$;
Run Code Online (Sandbox Code Playgroud)
(基于@a_horse_with_no_name的回答并在@Gregory 的评论之后得到改进.)
例如,与(还)CREATE TABLE没有IF NOT EXISTS条款不同CREATE ROLE.并且您无法在纯SQL中执行动态DDL语句.
除了使用另一个PL之外,你的"避免PL/pgSQL"的请求是不可能的.该DO语句使用plpgsql作为默认过程语言.语法允许省略显式声明:
DO [ LANGUAGElang_name] code
... 编写代码的过程语言的名称.如果省略,则默认为.
lang_nameplpgsql
Bor*_*rys 38
或者,如果角色不是任何数据库对象的所有者,则可以使用:
DROP ROLE IF EXISTS my_user;
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
Run Code Online (Sandbox Code Playgroud)
但只有放弃这个用户不会造成任何伤害.
blu*_*ubb 27
如果在同一Postgres集群(DB服务器)上同时执行两个这样的脚本,则接受的答案会受到竞争条件的影响,这在连续集成环境中很常见.
尝试创建角色并在创建时优雅地处理问题通常更安全:
DO $$
BEGIN
CREATE ROLE my_role WITH NOLOGIN;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'not creating role my_role -- it already exists';
END
$$;
Run Code Online (Sandbox Code Playgroud)
Pal*_*ali 13
一些答案建议使用模式:检查角色是否不存在,如果不存在则发出CREATE ROLE命令。这有一个缺点:竞争条件。如果其他人在检查和发出CREATE ROLE命令之间创建了一个新角色,那么CREATE ROLE显然会因致命错误而失败。
为了解决上述问题,更多其他答案已经提到了 的用法PL/pgSQL,CREATE ROLE无条件发出然后从该调用中捕获异常。这些解决方案只有一个问题。他们默默地删除任何错误,包括那些不是由角色已经存在的事实产生的错误。CREATE ROLE也可以抛出其他错误,IF NOT EXISTS当角色已经存在时,模拟应该只沉默错误。
CREATE ROLEduplicate_object当角色已经存在时抛出错误。并且异常处理程序应该只捕获这个错误。正如其他答案所提到的,将致命错误转换为简单通知是个好主意。其他 PostgreSQLIF NOT EXISTS命令会添加, skipping到它们的消息中,因此为了保持一致性,我也将其添加到此处。
这是用于模拟CREATE ROLE IF NOT EXISTS正确异常和 sqlstate 传播的完整 SQL 代码:
DO $$
BEGIN
CREATE ROLE test;
EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
END
$$;
Run Code Online (Sandbox Code Playgroud)
测试输出(通过 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
Run Code Online (Sandbox Code Playgroud)
Edu*_*omo 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';"
Run Code Online (Sandbox Code Playgroud)
(不是问题的答案!只适用于那些可能有用的人)
根据此处的其他答案,我希望能够psql对文件执行一次.sql以使其执行一组初始化操作。我还希望能够在执行时注入密码以支持 CI/CD 场景。
-- init.sql
CREATE OR REPLACE FUNCTION pg_temp.create_myuser(theUsername text, thePassword text)
RETURNS void AS
$BODY$
DECLARE
duplicate_object_message text;
BEGIN
BEGIN
EXECUTE format(
'CREATE USER %I WITH PASSWORD %L',
theUsername,
thePassword
);
EXCEPTION WHEN duplicate_object THEN
GET STACKED DIAGNOSTICS duplicate_object_message = MESSAGE_TEXT;
RAISE NOTICE '%, skipping', duplicate_object_message;
END;
END;
$BODY$
LANGUAGE 'plpgsql';
SELECT pg_temp.create_myuser(:'vUsername', :'vPassword');
Run Code Online (Sandbox Code Playgroud)
调用方式psql:
NEW_USERNAME="my_new_user"
NEW_PASSWORD="password with 'special' characters"
psql --no-psqlrc --single-transaction --pset=pager=off \
--tuples-only \
--set=ON_ERROR_STOP=1 \
--set=vUsername="$NEW_USERNAME" \
--set=vPassword="$NEW_PASSWORD" \
-f init.sql
Run Code Online (Sandbox Code Playgroud)
这将允许init.sql在本地运行或通过 CI/CD 管道运行。
笔记:
:vPassword直接在DO匿名函数中引用文件变量()的方法,因此需要完整FUNCTION传递参数。(参见@Clodoaldo Neto 的回答)EXECUTE而不能CREATE USER直接使用。EXCEPTION(这就是\gexec不推荐该方法的原因)。SELECT。使用命令中的-t/属性来清理日志输出,如@villy393 的回答中指出的。--tuples-onlypsql这是使用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;
Run Code Online (Sandbox Code Playgroud)
用法:
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)
Run Code Online (Sandbox Code Playgroud)
当您使用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$
;
Run Code Online (Sandbox Code Playgroud)
我的团队遇到了一个服务器上有多个数据库的情况,这取决于您连接到哪个数据库,有问题的ROLE没有被返回SELECT * FROM pg_catalog.pg_user,正如@ 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$
Run Code Online (Sandbox Code Playgroud)
它可能更具体,以排除其他例外.
如果您有权访问 shell,则可以执行此操作。
psql -tc "SELECT 1 FROM pg_user WHERE usename = 'some_use'" | grep -q 1 || psql -c "CREATE USER some_user"
Run Code Online (Sandbox Code Playgroud)
对于那些想要解释的人:
-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
Run Code Online (Sandbox Code Playgroud)
与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
Run Code Online (Sandbox Code Playgroud)
echo "SELECT 'CREATE USER my_user' WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec" | psql
Run Code Online (Sandbox Code Playgroud)
有关更多详细信息,请参阅那里的接受答案。