有没有办法在 PostgreSQL 查询中定义一个命名常量?例如:
MY_ID = 5;
SELECT * FROM users WHERE id = MY_ID;
有没有办法在 PostgreSQL 查询中定义一个命名常量?例如:
MY_ID = 5;
SELECT * FROM users WHERE id = MY_ID;
这个问题之前已经问过(How do you use script variables in PostgreSQL?)。但是,有时我会使用一个技巧进行查询:
with const as (
select 1 as val
)
select . . .
from const cross join
<more tables>
也就是说,我定义了一个名为 const 的 CTE,其中定义了常量。然后我可以将它交叉加入到我的查询中,在任何级别上任意次数。当我处理日期并且需要跨许多子查询处理日期常量时,我发现这特别有用。
PostgreSQL 没有内置方法来定义(全局)变量,如 MySQL 或 Oracle。(使用“自定义选项”的解决方法有限)。根据您的确切需求,还有其他方法:
您可以在CTE中的查询顶部提供值,例如@Gordon already provided。
您可以为此创建一个简单的IMMUTABLE
函数:
CREATE FUNCTION public.f_myid()
RETURNS int LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT 5';
(并行安全设置仅适用于 Postgres 9.6 或更高版本。)
它必须存在于当前用户可见的模式中,即在各自的search_path
. 就像 schema 一样public
,默认情况下。如果安全是一个问题,请确保它是search_path
您的调用中的第一个架构或架构限定它:
SELECT public.f_myid();
对数据库中的所有用户可见(允许访问 schema public
)。
CREATE TEMP TABLE val (val_id int PRIMARY KEY, val text);
INSERT INTO val(val_id, val) VALUES
( 1, 'foo')
, ( 2, 'bar')
, (317, 'baz');
CREATE FUNCTION f_val(_id int)
RETURNS text LANGUAGE sql STABLE PARALLEL RESTRICTED AS
'SELECT val FROM val WHERE val_id = $1';
SELECT f_val(2); -- returns 'baz'
由于 plpgsql 在创建时检查表的存在,因此您需要在创建val
函数之前创建一个(临时)表 - 即使在会话结束时删除了临时表而函数仍然存在。如果在调用时未找到基础表,该函数将引发异常。
search_path
临时对象的当前模式在您默认的其余部分之前- 如果没有明确指示。您不能从 中排除临时架构search_path
,但可以先放置其他架构。
夜晚的邪恶生物(具有必要的特权)可能会修补search_path
并将另一个同名对象放在前面:
CREATE TABLE myschema.val (val_id int PRIMARY KEY, val text);
INSERT INTO val(val_id, val) VALUES (2, 'wrong');
SET search_path = myschema, pg_temp;
SELECT f_val(2); -- returns 'wrong'
这不是什么大威胁,因为只有特权用户才能更改全局设置。其他用户只能为自己的会话执行此操作。考虑手册中有关使用SECURITY DEFINER
.
硬连线模式通常更简单、更快:
CREATE FUNCTION f_val(_id int)
RETURNS text LANGUAGE sql STABLE PARALLEL RESTRICTED AS
'SELECT val FROM pg_temp.val WHERE val_id = $1';
更多选项的相关答案:
除了 Gordon 和 Erwin 已经提到的明智选项(临时表、常量返回函数、CTE 等),您还可以(ab)使用 PostgreSQL GUC 机制来创建全局、会话和事务级别的变量。
请参阅此之前的帖子,其中详细显示了该方法。
我不建议将其用于一般用途,但它在链接问题中提到的狭窄情况下可能很有用,在这种情况下,发帖人想要一种方法来为触发器和函数提供应用程序级用户名。
我找到了这个解决方案:
with vars as (
SELECT * FROM (values(5)) as t(MY_ID)
)
SELECT * FROM users WHERE id = (SELECT MY_ID FROM vars)
我发现混合可用的方法是最好的:
CREATE TABLE vars (
id INT NOT NULL PRIMARY KEY DEFAULT 1,
zipcode INT NOT NULL DEFAULT 90210,
-- etc..
CHECK (id = 1)
);
CREATE FUNCTION generate_var_getter()
RETURNS VOID AS $$
DECLARE
var_name TEXT;
var_value TEXT;
new_rows TEXT[];
new_sql TEXT;
BEGIN
FOR var_name IN (
SELECT columns.column_name
FROM information_schema.columns
WHERE columns.table_schema = 'public'
AND columns.table_name = 'vars'
ORDER BY columns.ordinal_position ASC
) LOOP
EXECUTE
FORMAT('SELECT %I FROM vars LIMIT 1', var_name)
INTO var_value;
new_rows := ARRAY_APPEND(
new_rows,
FORMAT('(''%s'', %s)', var_name, var_value)
);
END LOOP;
new_sql := FORMAT($sql$
CREATE OR REPLACE FUNCTION var_get(key_in TEXT)
RETURNS TEXT AS $config$
DECLARE
result NUMERIC;
BEGIN
result := (
SELECT value FROM (VALUES %s)
AS vars_tmp (key, value)
WHERE key = key_in
);
RETURN result;
END;
$config$ LANGUAGE plpgsql IMMUTABLE;
$sql$, ARRAY_TO_STRING(new_rows, ','));
EXECUTE new_sql;
RETURN;
END;
$$ LANGUAGE plpgsql;
generate_var_getter()
调用,并var_get()
重新创建不可变函数。CREATE FUNCTION vars_regenerate_update()
RETURNS TRIGGER AS $$
BEGIN
PERFORM generate_var_getter();
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_vars_regenerate_change
AFTER INSERT OR UPDATE ON vars
EXECUTE FUNCTION vars_regenerate_update();
现在,您可以轻松地将变量保存在表格中,而且还可以获得对它们的极快的不可变访问。两全其美的:
INSERT INTO vars DEFAULT VALUES;
-- INSERT 0 1
SELECT var_get('zipcode')::INT;
-- 90210
UPDATE vars SET zipcode = 84111;
-- UPDATE 1
SELECT var_get('zipcode')::INT;
-- 84111