有没有办法在 PostgreSQL 查询中定义一个命名常量?
Posted
技术标签:
【中文标题】有没有办法在 PostgreSQL 查询中定义一个命名常量?【英文标题】:Is there a way to define a named constant in a PostgreSQL query? 【发布时间】:2012-10-30 06:52:30 【问题描述】:有没有办法在 PostgreSQL 查询中定义命名常量?例如:
MY_ID = 5;
SELECT * FROM users WHERE id = MY_ID;
【问题讨论】:
多一点上下文怎么样?您是否在psql
中玩耍并试图避免一遍又一遍地记住某些东西或计算某些东西?或者您正在为某事编写 SQL 脚本?
范围应该是什么?交易?会议?一个用户/所有用户/一个数据库/集群中的所有数据库?
我真的只是在寻找一种简单的方法来让我的 SQL 查询在未来更容易更改。我宁愿只定义MY_ID
,然后在需要时更改它。
【参考方案1】:
之前有人问过这个问题 (How do you use script variables in PostgreSQL?)。但是,我有时会在查询中使用一个技巧:
with const as (
select 1 as val
)
select . . .
from const cross join
<more tables>
也就是说,我定义了一个名为 const 的 CTE,其中定义了常量。然后我可以将它交叉加入到我的查询中,在任何级别上任意次数。当我处理日期并且需要跨许多子查询处理日期常量时,我发现这特别有用。
【讨论】:
这行得通。但它真的是 Postgres 中最好的吗?我正在使用只读数据库,所以我无法编写函数。如果我使用这个解决方案,我将不得不为复杂的查询编写许多交叉连接。我只想设置一个变量然后忘记! @samthebrand 。 . .cross join
不应影响性能。 CTE 只有一行。
如何使用此方法获取常量列表。类似于:``` with const as ((1,4,3,6,517) as vals)``` 我试过了,但还是不行。
@bappak 。 . . with const as (select 1 as val1, 4 as val2, . . . )
.
谢谢@GordonLinoff。这对我不起作用,因为用例需要整个列表的单个名称,以便可以在主查询中使用该列表,例如:select ... where foobar in (select vals from const)
【参考方案2】:
PostgreSQL 没有内置方法来定义(全局)变量,如 mysql 或 Oracle。 (使用"customized options" 的解决方法有限)。根据您的具体要求,还有其他方法:
对于一个查询
您可以在CTE 中的查询顶部提供值,例如@Gordon already provided。
全局持久常量:
您可以为此创建一个简单的 IMMUTABLE
函数:
CREATE FUNCTION public.f_myid()
RETURNS int LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT 5';
(Parallel safety 设置仅适用于 Postgres 9.6 或更高版本。)
它必须存在于对当前用户可见的架构中,即在各自的search_path
中。默认情况下,类似于架构 public
。如果安全是一个问题,请确保它是 search_path
中的第一个架构或在您的调用中对其进行架构限定:
SELECT public.f_myid();
对数据库中的所有用户可见(允许访问架构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'
威胁不大,因为只有特权用户才能更改全局设置。其他用户只能为自己的会话执行此操作。考虑creating functions with 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';
更多选项的相关答案:
How to test my ad-hoc SQL with parameters in Postgres query window
Passing user id to PostgreSQL triggers
【讨论】:
【参考方案3】:除了 Gordon 和 Erwin 已经提到的明智选项(临时表、常量返回函数、CTE 等),您还可以(ab)使用 PostgreSQL GUC 机制来创建全局、会话和事务级别变量。
请参阅this prior post,其中详细显示了该方法。
我不建议将其用于一般用途,但它可能在链接问题中提到的狭窄情况下很有用,在这种情况下,发帖人想要一种方法来为触发器和函数提供应用程序级用户名。
【讨论】:
【参考方案4】:我找到了这个解决方案:
with vars as (
SELECT * FROM (values(5)) as t(MY_ID)
)
SELECT * FROM users WHERE id = (SELECT MY_ID FROM vars)
【讨论】:
with vars as (SELECT * FROM (values(5)) as t(MY_ID) )
可以简化为with vars (my_id) as (values(5))
【参考方案5】:
我发现各种可用的方法都是最好的:
将变量存储在表格中:CREATE TABLE vars (
id INT NOT NULL PRIMARY KEY DEFAULT 1,
zipcode INT NOT NULL DEFAULT 90210,
-- etc..
CHECK (id = 1)
);
创建一个动态函数,该函数加载表的内容,并使用它来:
重新/创建另一个单独的静态不可变 getter 函数。
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
【讨论】:
非常好。对于生产用途,我们还可以添加适当的授权以确保不会意外更新 vars 表。或者也许为打算在设计时修复的常量实现一个只读标志 - 不过这需要一个多行 vars 表。以上是关于有没有办法在 PostgreSQL 查询中定义一个命名常量?的主要内容,如果未能解决你的问题,请参考以下文章
获取 django 在 postgresql 上运行的所有查询