如何在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
相关产品推荐
相关产品推荐

