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

时间:2012-11-09 22:10:34

标签: sql postgresql

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

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

5 个答案:

答案 0 :(得分:38)

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

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

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

答案 1 :(得分:37)

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

对于一个查询

您可以在CTE @Gordon already provided中的查询顶部提供值。

全局持久常数:

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

CREATE FUNCTION public.f_myid()
  RETURNS int IMMUTABLE LANGUAGE SQL AS
'SELECT 5';

它必须存在于当前用户可见的模式中,即位于相应的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 STABLE LANGUAGE SQL 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'

这不是一个威胁,因为只有特权用户才能改变全局设置。其他用户只能为自己的会话执行此操作。要防止这种情况发生 at all ,请为您的函数和架构设置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;

或者改为使用:

... SET search_path = pg_temp, param;

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

考虑creating functions with SECURITY DEFINER手册的相关章节。

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

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

更多选项的相关答案:

答案 2 :(得分:6)

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

请参阅详细显示方法的this prior post

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

答案 3 :(得分:3)

我发现最好的混合方法是

  • 将变量存储在表中
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

答案 4 :(得分:2)

我找到了这个解决方案:

with vars as (
    SELECT * FROM (values(5)) as t(MY_ID)
)
SELECT * FROM users WHERE id = (SELECT MY_ID FROM vars)