60

有没有办法在 PostgreSQL 查询中定义一个命名常量?例如:

MY_ID = 5;
SELECT * FROM users WHERE id = MY_ID;
4

5 回答 5

53

这个问题之前已经问过(How do you use script variables in PostgreSQL?)。但是,有时我会使用一个技巧进行查询:

with const as (
    select 1 as val
)
select . . .
from const cross join
     <more tables>

也就是说,我定义了一个名为 const 的 CTE,其中定义了常量。然后我可以将它交叉加入到我的查询中,在任何级别上任意次数。当我处理日期并且需要跨许多子查询处理日期常量时,我​​发现这特别有用。

于 2012-11-09T22:58:43.310 回答
46

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';

更多选项的相关答案:

于 2012-11-09T23:40:48.960 回答
10

除了 Gordon 和 Erwin 已经提到的明智选项(临时表、常量返回函数、CTE 等),您还可以(ab)使用 PostgreSQL GUC 机制来创建全局、会话和事务级别的变量。

请参阅此之前的帖子,其中详细显示了该方法。

我不建议将其用于一般用途,但它在链接问题中提到的狭窄情况下可能很有用,在这种情况下,发帖人想要一种方法来为触发器和函数提供应用程序级用户名。

于 2012-11-10T03:07:33.650 回答
6

我找到了这个解决方案:

with vars as (
    SELECT * FROM (values(5)) as t(MY_ID)
)
SELECT * FROM users WHERE id = (SELECT MY_ID FROM vars)
于 2017-05-04T14:27:18.313 回答
6

我发现混合可用的方法是最好的:

  • 将变量存储在表中:
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
于 2019-01-23T08:18:36.843 回答