有没有办法在PostgreSQL查询中定义命名常量?

Aje*_*i32 41 sql postgresql

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

MY_ID = 5;
SELECT * FROM users WHERE id = MY_ID;
Run Code Online (Sandbox Code Playgroud)

Gor*_*off 39

之前已经问过这个问题(如何在PostgreSQL中使用脚本变量?).但是,有时我会使用查询技巧:

with const as (
    select 1 as val
)
select . . .
from const cross join
     <more tables>
Run Code Online (Sandbox Code Playgroud)

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

  • 这有效.但是Postgres真的是最好的吗?我正在使用只读数据库,所以我无法编写函数.如果我使用这个解决方案,我将不得不为复杂的查询编写许多交叉连接.我只是想设置一个变量而忘了! (3认同)

Erw*_*ter 38

PostgreSQL没有内置的方法来定义(全局)变量,如MySQL或Oracle.(使用"自定义选项")的解决方法有限.根据您的需要,还有其他方法:

对于一个查询

你可以在一个查询的顶部提供值CTE喜欢已经提供@Gordon.

全球持久性常数:

您可以IMMUTABLE为此创建一个简单的函数:

CREATE FUNCTION public.f_myid()
  RETURNS int IMMUTABLE LANGUAGE SQL AS
'SELECT 5';
Run Code Online (Sandbox Code Playgroud)

它必须存在于当前用户可见的模式中,即在各自的模式中search_path.与模式一样public,默认情况下.如果安全性存在问题,请确保它是search_path或者架构中的第一个架构 - 在您的呼叫中限定它:

SELECT public.f_myid();
Run Code Online (Sandbox Code Playgroud)

对数据库中的所有用户(允许访问模式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 STABLE LANGUAGE SQL AS
'SELECT val FROM val WHERE val_id = $1';

SELECT f_val(2);  -- returns 'baz'
Run Code Online (Sandbox Code Playgroud)

由于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'
Run Code Online (Sandbox Code Playgroud)

它不是一个威胁,因为只有特权用户才能改变全局设置.其他用户只能为自己的会话执行此操作.为了防止这种情况发生在所有的,设置search_path在呼叫你的功能和架构来限定它:

CREATE FUNCTION f_val(_id int)
  RETURNS text STABLE LANGUAGE SQL AS
'SELECT val FROM val WHERE val_id = $1'
SET search_path = pg_temp;
Run Code Online (Sandbox Code Playgroud)

或者改为使用:

... SET search_path = pg_temp, param;
Run Code Online (Sandbox Code Playgroud)

这将允许您(或具有必要权限的任何人)在表中提供全局(持久)默认值param.val...

请考虑使用SECURITY DEFINER创建功能的手册的相关章节.

但是,这种超级安全的功能不能"内联",并且可能比使用硬连线架构的更简单的替代方案执行得更慢:

CREATE FUNCTION f_val(_id int)
  RETURNS text STABLE LANGUAGE SQL AS
'SELECT val FROM pg_temp.val WHERE val_id = $1';
Run Code Online (Sandbox Code Playgroud)

相关答案有更多选择:


Ale*_*rov 8

我找到了这个解决方案:

with vars as (
    SELECT * FROM (values(5)) as t(MY_ID)
)
SELECT * FROM users WHERE id = (SELECT MY_ID FROM vars)
Run Code Online (Sandbox Code Playgroud)

  • `with vars as (SELECT * FROM (values(5)) as t(MY_ID) )` 可以简化为 `with vars (my_id) as (values(5))` (13认同)

Cra*_*ger 6

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

请参阅此前的帖子,其中详细介绍了该方法.

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


小智 6

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

  • 将变量存储在表中:
CREATE TABLE vars (
  id INT NOT NULL PRIMARY KEY DEFAULT 1,
  zipcode INT NOT NULL DEFAULT 90210,
  -- etc..
  CHECK (id = 1)
);
Run Code Online (Sandbox Code Playgroud)
  • 创建一个动态函数,用于加载表的内容,并将其用于:
    • 重新/创建另一个单独的静态不可变 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;
Run Code Online (Sandbox Code Playgroud)
  • 向您的表中添加一个更新触发器,以便在您更改其中一个变量后generate_var_getter()调用,并var_get()重新创建不可变函数。
CREATE FUNCTION vars_regenerate_update()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM generate_var_getter();
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
CREATE TRIGGER trigger_vars_regenerate_change
  AFTER INSERT OR UPDATE ON vars
  EXECUTE FUNCTION vars_regenerate_update();
Run Code Online (Sandbox Code Playgroud)

现在,您可以轻松地将变量保存在表中,而且还可以快速访问它们。两全其美的:

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
Run Code Online (Sandbox Code Playgroud)