我如何编写一个 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;
...但这不起作用 - IF
似乎在纯 SQL 中不受支持。
我有一个创建 PostgreSQL 9.1 数据库、角色和其他一些东西的批处理文件。它调用 psql.exe,传入要运行的 SQL 脚本的名称。到目前为止,所有这些脚本都是纯 SQL,如果可能的话,我想避免使用 PL/pgSQL 等。
最佳答案
简单脚本(提问)
基于 @a_horse_with_no_name的答案并改进了 @Gregory's comment :
DO
$do$
BEGIN
IF EXISTS (
SELECT FROM pg_catalog.pg_roles
WHERE rolname = 'my_user') THEN
RAISE NOTICE 'Role "my_user" already exists. Skipping.';
ELSE
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
END IF;
END
$do$;
例如,与 CREATE TABLE
不同CREATE ROLE
没有 IF NOT EXISTS
子句(至少 Postgres 14)。而且您不能在纯 SQL 中执行动态 DDL 语句。
除非使用另一个 PL,否则您“避免使用 PL/pgSQL”的请求是不可能的。 DO
statement使用 PL/pgSQL 作为默认过程语言:
DO [ LANGUAGE
lang_name
] code
...
lang_name
The name of the procedural language the code is written in. If omitted, the default isplpgsql
.
无竞争条件
上述简单的解决方案允许在查找角色和创建角色之间的微小时间范围内出现竞争条件。如果一个并发事务在两者之间创建角色,我们毕竟会得到一个异常。在大多数工作负载中,这永远不会发生,因为创建角色是管理员很少执行的操作。但是有很多有争议的工作负载,例如 @blubb mentioned .
@Pali 添加了一个捕获异常的解决方案。但是带有 EXCEPTION
子句的代码块是昂贵的。 The manual:
A block containing an
EXCEPTION
clause is significantly more expensive to enter and exit than a block without one. Therefore, don't useEXCEPTION
without need.
实际上引发异常(然后捕获它)的代价相对较高。所有这些只对经常执行它的工作负载很重要——这恰好是主要目标受众。优化:
DO
$do$
BEGIN
IF EXISTS (
SELECT FROM pg_catalog.pg_roles
WHERE rolname = 'my_user') THEN
RAISE NOTICE 'Role "my_user" already exists. Skipping.';
ELSE
BEGIN -- nested block
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
EXCEPTION
WHEN duplicate_object THEN
RAISE NOTICE 'Role "my_user" was just created by a concurrent transaction. Skipping.';
END;
END IF;
END
$do$;
便宜得多:
如果角色已经存在,我们永远不会进入昂贵的代码块。
如果我们输入昂贵的代码块,则只有在不太可能发生的竞争条件命中时,该角色才会存在。所以我们几乎从来没有真正引发异常(并捕获它)。
关于sql - 如果不存在则创建 PostgreSQL ROLE (user),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8092086/