【问题标题】:Is there a way to define a named constant in a PostgreSQL query?有没有办法在 PostgreSQL 查询中定义一个命名常量?
【发布时间】:2012-10-30 06:52:30
【问题描述】:

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

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

【问题讨论】:

  • 多一点上下文怎么样?您是否在psql 中玩耍并试图避免一遍又一遍地记住某些东西或计算某些东西?或者您正在为某事编写 SQL 脚本?
  • 范围应该是什么?交易?会议?一个用户/所有用户/一个数据库/集群中的所有数据库?
  • 我真的只是在寻找一种简单的方法来让我的 SQL 查询在未来更容易更改。我宁愿只定义MY_ID,然后在需要时更改它。

标签: sql postgresql


【解决方案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';

更多选项的相关答案:

【讨论】:

    【解决方案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 表。
      猜你喜欢
      • 1970-01-01
      • 2011-01-15
      • 2016-07-09
      • 1970-01-01
      • 2010-09-11
      • 2013-08-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多