在PostgreSQL中定义全局变量是否可行?

14

我正在使用PostgreSQL 9.4,编写函数时我想使用自定义的错误代码(int)。但是,将来可能需要更改确切的数字值


例如
-1表示USER_NOT_FOUND。
-2表示用户没有权限。

我可以在表codes_table(code_name :: text,code_value :: integer)中定义它们,并在函数中使用它们,如下所示:

(SELECT codes_table.code_value FROM codes_table WHERE codes_table.code_name = 'USER_NOT_FOUND')

有没有其他的方法。也许是全局变量?


1
坚持使用表格。如果必须的话,可以将其封装在一个简单的SQL函数中。但实际上,我认为改变这些值没有什么意义,为什么不将它们设为静态的呢? - Craig Ringer
@CraigRinger 我们正在开发一个新系统,并将为错误代码设置格式。例如,[-9 -1] 用于与权限相关的错误,[-19,-10] 用于检查约束错误。但是目前还没有确定该格式。也许一旦确定了,以后就会重新设置。因此我想做好准备。 - Nuri Tasdemir
这些不是错误代码,你可以使用 RAISE EXCEPTION 返回错误代码,并且较新版本允许您指定要引发的代码。 - Jasen
6个回答

15

Postgres没有全局变量。但是你可以定义自定义配置参数。为了保持清晰,使用给定前缀(例如glb)定义自己的参数。

这个简单的函数将使在查询中放置参数更容易:

create or replace function glb(code text)
returns integer language sql as $$
    select current_setting('glb.' || code)::integer;
$$;

set glb.user_not_found to -1;
set glb.user_does_not_have_permission to -2;

select glb('user_not_found'), glb('user_does_not_have_permission');

用户定义的参数是会话中的局部变量,因此这些参数应在每个会话的开始处定义。


这些配置参数可以完成工作。但是有没有一种方法使它们永久(直到我改变它们)? - Nuri Tasdemir
1
@Nuri:请参阅9.4版中的ALTER SYSTEM - Nick Barnes

12

@klin的答案基础上,有几种方法可以使配置参数在当前会话之外持久化。请注意,这些方法需要超级用户权限。

要为连接到特定数据库的所有连接设置值:

ALTER DATABASE db SET abc.xyz = 1;
您还可以使用在9.4版本中添加的ALTER SYSTEM命令来设置服务器范围内的值。如果用户定义的参数已经在当前会话中进行了SET设置,则似乎仅适用于这些参数。请注意,这需要重新加载配置才能生效。
SET abc.xyz = 1;
ALTER SYSTEM SET abc.xyz = 1;
SELECT pg_reload_conf();

在9.4之前,您可以通过将参数添加到服务器的 postgresql.conf 文件来实现相同的功能。在9.1及更早版本中,您还需要注册一个自定义变量类


我可以通过SHOW abc.xyz看到这个值。但是我无法在SELECT语句中使用它。如何使用它的值? - Nuri Tasdemir
1
使用我回答中的函数(当然你应该更改前缀“glb”)。 - klin
配置参数的大小是否有限制,或者在开始影响系统性能之前存在实际限制?例如,它可以达到100MB(在100GB服务器上)吗? - Dinesh

4
你可以使用这个

最初的回答


CREATE OR REPLACE FUNCTION globals.maxCities()
  RETURNS integer AS
  $$SELECT 100 $$ LANGUAGE sql IMMUTABLE;

最初的回答:请在代码中直接使用globals.maxCities()。

1
您可以使用一个技巧,将变量声明为一行CTE,然后与其余部分进行CROSS JOIN。请参见以下示例:
WITH
variables AS (
    SELECT 'value1'::TEXT AS var1, 10::INT AS var2
)
SELECT t.*, v.*
FROM
    my_table AS t
    CROSS JOIN variables AS v
WHERE t.random_int_column = var2;

2
你可以将 variables CTE 简化为 with variables (var1, var2, trip_type_var as (values ('value1', 'value2', 'one')) ... - user330315

1
PostgreSQL在数据库级别上不支持全局变量。为什么不添加它:
CREATE TABLE global_variables (
  key text not null PRIMARY KEY
  value text
);

INSERT INTO global_variables (key, value) VALUES ('error_code_for_spaceship_engine', '404');

如果值可以是不同类型的,考虑将 JSON 视为 value 的类型,但是需要为每种类型编写反序列化代码。

0
请注意,如果您使用数据库级别的设置,例如@nick-barnes的答案中所述,当用户连接到该数据库时,这些设置将被设置为会话默认值,但可以在该会话(或事务)中覆盖。
如果您希望强制执行数据库设置,可以直接访问pg_db_role_setting系统目录,就像我pg_safer_settings扩展中的此函数所做的那样(在此处复制并获得了我的明确许可)。
create function pg_db_setting(pg_setting_name$ text, pg_role$ regrole = 0)
    returns text
    stable
--    security definer
    return (
        select
            regexp_replace(expanded_settings.raw_setting, E'^[^=]+=', '')
        from
            pg_catalog.pg_db_role_setting
        inner join
            pg_catalog.pg_database
            on pg_database.oid = pg_db_role_setting.setdatabase
        cross join lateral
            unnest(pg_db_role_setting.setconfig) as expanded_settings(raw_setting)
        where
            pg_database.datname = current_database()
            and pg_db_role_setting.setrole = coalesce(
                pg_role$,
                0  -- 0 means “not role-specific”
            )
            and expanded_settings.raw_setting like pg_setting_name$ || '=%'
        limit 1
    );

这里有一个例子来阐述 pg_catalog.current_setting() 函数和我的 pg_db_role_setting() 函数语义上的差异:
CREATE DATABASE mydb;
CONNECT TO mydb
CREATE ROLE myrole;
ALTER DATABASE mydb
    SET app.settings.bla = 1::text;
ALTER ROLE myrole
    IN DATABASE mydb
    SET app.settings.bla = 2::text;
SET ROLE myrole;
SET app.settings.bla TO 3::text;
SELECT current_setting('app.settings.bla', true);  -- '3'
SELECT pg_db_role_setting('app.settings.bla');  -- '1'
SELECT pg_db_role_setting('app.settings.bla', current_user);  -- '2'

另一种常见的模式是将设置存储为不可变的函数或表格。如果您将它们存储在表中,请帮我一个忙,并将它们存储为列,而不是行。我的 pg_safer_settings 扩展功能包括一个 pg_safer_settings_table,它结合了这两种范例,允许您将设置存储为列,并自动维护每个列的 current_<col_name>() 函数。(current_ 前缀可配置。)

1
出于好奇,为什么将它们存储为列而不是行呢?在我看来,当存储在单独的行中时,锁定的可能性要小得多,而不是将所有内容都存储在同一行中。很想听听您的理由。 - Thomas Heijtink
1
好问题,@ThomasHeijtink。选择使用列是出于希望拥有尽可能强大的类型和约束的动机。我个人没有涉及频繁并行设置更改的用例,这就是为什么我不担心锁定,只关注数据完整性。我喜欢能够添加一个像default_billing_interval这样的设置,并将其作为实际的interval。我本可以选择行并添加一个setting_regtype列。但是,那样我感觉有点像在DB语义之上重新实现SQL语义。 - BigSmoke
@ThomasHeijtink,如果你制作一个将设置存储在行而不是列中的扩展程序,我完全可以在pg_safer_settingsREADME.md中链接到它。 - BigSmoke
受到你的启发,@ThomasHeijtink,我现在已经在README.md中记录了这个设计选择:https://github.com/bigsmoke/pg_safer_settings#table-pg_safer_settings_table - BigSmoke
谢谢你的澄清。是的,那确实有道理。不同的需求需要不同的方法。谢谢你的解释。 - Thomas Heijtink

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接