You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PostgreSQL中声明可全局使用的相互依赖常量?

在PostgreSQL中实现相互依赖的全局常量

我有编程背景但刚接触数据库,希望像在编程语言里那样声明相互依赖的常量,示例如下:

MY_CONSTANT1 = 5
MY_CONSTANT2 = MY_CONSTANT1 * 20
MY_CONSTANT3 = 'insert regex capture group here'
MY_CONSTANT4 = 'insert regex capture group here'
MY_CONSTANT5 = '(' || MY_CONSTANT3 || '|' || MY_CONSTANT4 || ')'

这类常量彼此依赖、可相互计算,在全局场景中很有必要,比如存储正则捕获组、数学常量等不常变更且存在依赖关系的数据。

我目前用了一个临时的函数方案,期望借助IMMUTABLE属性让编译器优化:

CREATE OR REPLACE FUNCTION util.get_text_constant(IN p_constant_name text)
    RETURNS text
    LANGUAGE 'plpgsql'
    IMMUTABLE
    PARALLEL UNSAFE
    COST 100
    
AS $BODY$
declare
    v_constants jsonb := '{}';
begin
    v_constants := v_constants || jsonb_build_object(
        'RE_GROUP_NAME', '([A-Z][a-z]+( [A-Z][a-z]+)*( (?=[a-zA-Z])M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3}))?(?<! I))',
        'RE_GROUP_PHONE', '(07[0-9]{8}|\+(?!40)[0-9]{7,15})',
        'RE_GROUP_CODE', '(P[1-9][0-9]{4})',
        'RE_GROUP_LINK', '(https://[-a-zA-Z0-9\./\?\=\|\%]+)',
        'RE_GROUP_NICKNAME', '([0-9A-Z][0-9a-z]+( [0-9A-Z][0-9a-z]+)*)',
        
        'RE_GROUP_PARAGRAPH', '((?![[:space:]])[^\n]+(?<![[:space:]]))',
        
        'RE_GROUP_NUMBER', '((0|[1-9][0-9]{0,2}( [0-9]{3})*)(\.[0-9]+)?(K|M)?)',
        
        'VAT_STANDARD', '0.19'
    );
    if v_constants ? p_constant_name then
        return v_constants ->> p_constant_name;
    end if;
    
    v_constants := v_constants || jsonb_build_object(
        'RE_GROUP_COUNTERVALUE', '(' || util.get_text_constant('RE_GROUP_NUMBER') || '(\%| FIX)( \+ TVA)?)',
        'RE_GROUP_IDENTIFICATOR',
        ('('
            || 'I - ' || util.get_text_constant('RE_GROUP_CODE')
            || '|I - ' || util.get_text_constant('RE_GROUP_LINK')
            || '|(C|I) - ' || util.get_text_constant('RE_GROUP_PHONE')
            || '|(C|I) - ' || util.get_text_constant('RE_GROUP_NAME')
            || '|(C|I) - ' || util.get_text_constant('RE_GROUP_NAME') || ' - ' || util.get_text_constant('RE_GROUP_NICKNAME')
        || ')')
    );
    if v_constants ? p_constant_name then
        return v_constants ->> p_constant_name;
    end if;
    
    raise exception 'Constant does not exist "%"', p_constant_name;
end
$BODY$;

但这个方案有两个明显问题:仅支持text类型,且随着依赖层级增加,代码会迅速变得杂乱。

我不能用表存储这些常量,因为需要在类型定义、不可变函数等场景中把它们当常量用,比如:

CREATE DOMAIN util.phone_text AS text
    CONSTRAINT phone_text_check1 CHECK ((VALUE ~ util.unique_trimmed_group(util.get_text_constant('RE_GROUP_PHONE'::text))));

ALTER DOMAIN util.phone_text OWNER TO postgres;

我疑惑PostgreSQL这类高级数据库为什么没有全局相互依赖常量功能,想请教正确的实现方式。


可行的实现方式

PostgreSQL本身没有内置的全局相互依赖常量机制,但可以通过IMMUTABLE函数模拟,既能满足类型定义、不可变函数等场景的使用要求,又支持多类型和依赖关系:

1. 单个常量对应单个IMMUTABLE函数(推荐)

这种方式最清晰,每个常量用独立的IMMUTABLE函数实现,支持任意数据类型,依赖关系直接通过函数调用实现:

-- 基础常量:正则捕获组
CREATE OR REPLACE FUNCTION util.RE_GROUP_PHONE()
RETURNS text
LANGUAGE sql
IMMUTABLE PARALLEL SAFE COST 1
RETURN '(07[0-9]{8}|\+(?!40)[0-9]{7,15})';

CREATE OR REPLACE FUNCTION util.RE_GROUP_CODE()
RETURNS text
LANGUAGE sql
IMMUTABLE PARALLEL SAFE COST 1
RETURN '(P[1-9][0-9]{4})';

CREATE OR REPLACE FUNCTION util.RE_GROUP_NAME()
RETURNS text
LANGUAGE sql
IMMUTABLE PARALLEL SAFE COST 1
RETURN '([A-Z][a-z]+( [A-Z][a-z]+)*( (?=[a-zA-Z])M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3}))?(?<! I))';

-- 依赖其他常量的组合常量
CREATE OR REPLACE FUNCTION util.RE_GROUP_IDENTIFICATOR()
RETURNS text
LANGUAGE sql
IMMUTABLE PARALLEL SAFE COST 1
RETURN '(' ||
    'I - ' || util.RE_GROUP_CODE() ||
    '|I - ' || util.RE_GROUP_LINK() ||
    '|(C|I) - ' || util.RE_GROUP_PHONE() ||
    '|(C|I) - ' || util.RE_GROUP_NAME() ||
    '|(C|I) - ' || util.RE_GROUP_NAME() || ' - ' || util.RE_GROUP_NICKNAME() ||
')';

-- 数值类型常量
CREATE OR REPLACE FUNCTION util.VAT_STANDARD()
RETURNS numeric
LANGUAGE sql
IMMUTABLE PARALLEL SAFE COST 1
RETURN 0.19;

使用时直接调用函数即可,性能和直接用常量一致:

CREATE DOMAIN util.phone_text AS text
CONSTRAINT phone_text_check1 CHECK (VALUE ~ util.unique_trimmed_group(util.RE_GROUP_PHONE()));

2. 支持多类型的统一管理函数

如果希望用单个函数管理所有常量,可以通过多态函数支持不同类型返回:

CREATE OR REPLACE FUNCTION util.get_constant(p_constant_name text, OUT result anyelement)
RETURNS anyelement
LANGUAGE plpgsql
IMMUTABLE PARALLEL SAFE COST 100
AS $BODY$
DECLARE
    v_base_constants jsonb := jsonb_build_object(
        'RE_GROUP_PHONE', '(07[0-9]{8}|\+(?!40)[0-9]{7,15})',
        'RE_GROUP_CODE', '(P[1-9][0-9]{4})',
        'RE_GROUP_NAME', '([A-Z][a-z]+( [A-Z][a-z]+)*( (?=[a-zA-Z])M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3}))?(?<! I))',
        'VAT_STANDARD', 0.19
    );
    v_dependent_constants jsonb := jsonb_build_object(
        'RE_GROUP_IDENTIFICATOR', '(' ||
            'I - ' || util.get_constant('RE_GROUP_CODE', NULL::text) ||
            '|(C|I) - ' || util.get_constant('RE_GROUP_PHONE', NULL::text) ||
            '|(C|I) - ' || util.get_constant('RE_GROUP_NAME', NULL::text) ||
        ')'
    );
BEGIN
    IF v_base_constants ? p_constant_name THEN
        result := v_base_constants ->> p_constant_name;
        RETURN;
    ELSIF v_dependent_constants ? p_constant_name THEN
        result := v_dependent_constants ->> p_constant_name;
        RETURN;
    END IF;
    RAISE EXCEPTION 'Constant does not exist "%"', p_constant_name;
END;
$BODY$;

使用时需要指定返回类型:

-- 获取数值类型常量
SELECT util.get_constant('VAT_STANDARD', NULL::numeric);

-- 在约束中使用
CREATE DOMAIN util.phone_text AS text
CONSTRAINT phone_text_check1 CHECK (VALUE ~ util.unique_trimmed_group(util.get_constant('RE_GROUP_PHONE', NULL::text)));

核心说明

  • IMMUTABLE属性是关键:PostgreSQL会缓存这类函数的调用结果,性能等同于直接使用常量,且能在CHECK约束、不可变函数、类型定义等场景中合法使用。
  • 单个常量对应单个函数的方式更易维护,依赖关系清晰,适合长期迭代的项目;统一函数的方式适合常量数量较少的场景。
  • 不能用表存储的原因确实是表数据属于可变资源,引用表数据的函数会被标记为STABLE甚至VOLATILE,无法满足IMMUTABLE场景的要求。

内容的提问来源于stack exchange,提问作者GT 77

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 20:30:56