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

cwdobuhd  于 2023-03-01  发布在  PostgreSQL
关注(0)|答案(7)|浏览(283)

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

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

kulphzqa1#

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

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

也就是说,我定义了一个名为const的CTE,其中包含了定义的常量。然后,我可以在任何级别将其交叉连接到查询中,次数不限。我发现,当我处理日期时,这一点特别有用,因为我需要跨许多子查询处理日期常量。

to94eoyn

to94eoyn2#

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

对于 * 一个 * 查询

您可以在CTE中的查询顶部提供值,如已经提供的@Gordon。

全局持久常量:

您可以为此创建一个简单的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'

这并不是一个很大的威胁,因为只有特权用户可以修改全局设置,其他用户只能在他们自己的会话中这样做,请参考手册中关于使用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';

包含更多选项的相关答案:

  • 如何在Postgres查询窗口中使用参数测试我的ad-hoc SQL
  • 将用户ID传递到PostgreSQL触发器
vsikbqxv

vsikbqxv3#

除了Gordon和Erwin已经提到的合理选项(临时表、常量返回函数、CTE等)之外,您还可以(ab)使用PostgreSQL GUC机制来创建全局、会话和事务级变量。
请参见this prior post,其中详细说明了该方法。
我不建议将此用于一般用途,但它可能在小范围的情况下有用,如链接问题中提到的情况,即发帖者希望找到一种向触发器和函数提供应用程序级别用户名的方法。

mhd8tkvw

mhd8tkvw4#

我找到了这个解决方案:

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

zdwk9cvp5#

我已经发现了一种混合的可用方法是最好的:

  • 将变量存储在表中:
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()函数。

一个二个一个一个
现在,您可以轻松地将变量保存在表中,同时还可以对它们进行快速的不可变访问。

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
bvjxkvbb

bvjxkvbb6#

当查询使用“GROUP BY”时:

WITH const AS (
     select 5                 as MY_ID,
            '2022-03-1'::date as MY_DAY)

SELECT u.user_group,
       COUNT(*),
       const.MY_DAY
FROM users u
CROSS JOIN const

WHERE 1=1
GROUP BY u.user_group, const.MY_ID, const.MY_DAY

示例包含的字段比OP多,但这有助于更多的访问者查找主题。

不带分组依据:

WITH const AS (
     select 5 as MY_ID)

SELECT u.* FROM users u
CROSS JOIN const

WHERE u.id = const.MY_ID

感谢@GordonLinoff

没有GROUP BY且没有列名冲突:

WITH const AS (
     select 5 as MY_ID)

SELECT users.* FROM users
CROSS JOIN const

WHERE id = MY_ID
elcex8rz

elcex8rz7#

如果你想在会话中保持常量,并且不想使用表,你可以为一个特定的数据库或角色设置自定义设置,这样的设置可以在会话或事务级别被覆盖,但是直接访问pg_db_role_settings系统目录很容易理解:

create function pg_db_setting(pg_setting_name$ text, pg_role$ regrole = 0)
    returns text
    stable
--    security definer
    return (
        select
            regexp_replace(expanded_settings.raw_setting, E'^[^=]+=', '')
        from
            pg_catalog.pg_db_role_setting
        inner join
            pg_catalog.pg_database
            on pg_database.oid = pg_db_role_setting.setdatabase
        cross join lateral
            unnest(pg_db_role_setting.setconfig) as expanded_settings(raw_setting)
        where
            pg_database.datname = current_database()
            and pg_db_role_setting.setrole = coalesce(
                pg_role$,
                0  -- 0 means “not role-specific”
            )
            and expanded_settings.raw_setting like pg_setting_name$ || '=%'
        limit 1
    );

这个函数是😉从我的pg_safer_settings扩展中复制粘贴过来的。
下面是一个示例(摘自pg_safer_settings自述文件),说明如何使用它:

CREATE DATABASE mydb;
CONNECT TO mydb
CREATE ROLE myrole;
ALTER DATABASE mydb
    SET app.settings.bla = 1::text;
ALTER ROLE myrole
    IN DATABASE mydb
    SET app.settings.bla = 2::text;
SET ROLE myrole;
SET app.settings.bla TO 3::text;
SELECT current_setting('app.settings.bla', true);  -- '3'
SELECT pg_db_role_setting('app.settings.bla');  -- '1'
SELECT pg_db_role_setting('app.settings.bla', current_user);  -- '2'

如果您愿意,测试例程可以更详细一些:

CREATE OR REPLACE PROCEDURE ext.test__pg_db_setting()
 LANGUAGE plpgsql
 SET "plpgsql.check_asserts" TO 'true'
 SET "pg_readme.include_this_routine_definition" TO 'true'
AS $procedure$
begin
    execute 'ALTER DATABASE ' || current_database()
        || ' SET pg_safer_settings.test_pg_db_setting = ''foo''';
    assert pg_db_setting('pg_safer_settings.test_pg_db_setting') = 'foo';

    set pg_safer_settings.settings.test_pg_db_setting = 'bar';
    assert pg_db_setting('pg_safer_settings.test_pg_db_setting') = 'foo';

    assert pg_db_setting('pg_safer_settings.unknown_setting') is null;

    create role __test_role;
    execute 'ALTER ROLE __test_role IN DATABASE ' || current_database()
        || ' SET pg_safer_settings.test_pg_db_setting = ''foobar''';
    assert pg_db_setting('pg_safer_settings.test_pg_db_setting', '__test_role') = 'foobar';
    assert pg_db_setting('pg_safer_settings.test_pg_db_setting') = 'foo';

    raise transaction_rollback;
exception
    when transaction_rollback then
end;
$procedure$

pg_safer_settings还提供了@Brev his answer提出的模式的更复杂的变体。

相关问题