有没有办法在 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 查询中定义一个命名常量?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在熊猫数据框中运行 postgresql 查询?

来自 csv 文件的 PostgreSQL 查询 [重复]

获取 django 在 postgresql 上运行的所有查询

节点 PostgreSQL 超时客户端查询

如何(以编程方式)知道何时在 PostgreSQL/Amazon Redshift 上完成查询?

显示查询结果列类型 (PostgreSQL)