【问题标题】:How to declare a variable in a PostgreSQL query如何在 PostgreSQL 查询中声明变量
【发布时间】:2010-12-02 05:36:52
【问题描述】:

如何声明用于 PostgreSQL 8.3 查询的变量?

在 MS SQL Server 中,我可以这样做:

DECLARE @myvar INT
SET @myvar = 5

SELECT *
FROM somewhere
WHERE something = @myvar

我如何在 PostgreSQL 中做同样的事情?根据文档,变量被简单地声明为“名称类型;”,但这给了我一个语法错误:

myvar INTEGER;

有人能给我一个正确语法的例子吗?

【问题讨论】:

标签: sql postgresql postgresql-8.3


【解决方案1】:

我通过使用WITH clause 实现了相同的目标,它远没有那么优雅,但可以做同样的事情。虽然对于这个例子来说它真的是矫枉过正。我也不特别推荐这个。

WITH myconstants (var1, var2) as (
   values (5, 'foo')
)
SELECT *
FROM somewhere, myconstants
WHERE something = var1
   OR something_else = var2;

【讨论】:

  • 这适用于大多数需要变量的情况。但是,如果您想为 LIMIT 使用变量(不能包含变量),那么您需要按照 Shahriar Aghajani 的回答中的建议使用 \set
  • 当我有一个要导入一些关系数据的迁移脚本时,这是理想的选择。显然我不会知道给定关系数据的序列号。
  • 我只是尝试了这种方法,发现了一个可能更好的方法:JOIN myconstants ON true,然后就不需要进行子选择了。
  • 这仅适用于单个查询,您不能在事务中的查询之间共享WITH CTE。
  • 老问题,但这里有一个变体:WITH constants AS (SELECT 5 AS var) SELECT * FROM somewhere CROSS JOIN constants WHERE someting=var;。 CROSS JOIN 是一个单行表表达式,实际上复制了真实表中所有行的数据,并简化了表达式。
【解决方案2】:

PostgreSQL 中没有这样的功能。您只能在 pl/PgSQL(或其他 pl/*)中执行此操作,但不能在普通 SQL 中执行。

一个例外是WITH () 查询,它可以作为一个变量,甚至tuple 的变量。它允许您返回一个临时值表。

WITH master_user AS (
    SELECT
      login,
      registration_date
    FROM users
    WHERE ...
)

SELECT *
FROM users
WHERE master_login = (SELECT login
                      FROM master_user)
      AND (SELECT registration_date
           FROM master_user) > ...;

【讨论】:

  • 我尝试了这种将 CTE 用作变量的方法。但是我很快遇到了一个问题,即 CTE 中不同的数据修改查询不能保证看到彼此的效果。我必须使用多个 CTE,因为我需要在多个查询中使用该变量。
【解决方案3】:

你也可以在 PLPGSQL 中试试这个:

DO $$
DECLARE myvar integer;
BEGIN
    SELECT 5 INTO myvar;

    DROP TABLE IF EXISTS tmp_table;
    CREATE TABLE tmp_table AS
    SELECT * FROM yourtable WHERE   id = myvar;
END $$;

SELECT * FROM tmp_table;

以上要求 Postgres 9.0 或更高版本。

【讨论】:

  • DO 语句是在 PostgreSQL 9.0 中添加的,在 8.3 中不起作用。
  • 使用创建临时表或创建临时表,而不是创建表。但其他都很好。
【解决方案4】:

动态配置设置

您可以为此“滥用”动态配置设置:

-- choose some prefix that is unlikely to be used by postgres
set session my.vars.id = '1';

select *
from person 
where id = current_setting('my.vars.id')::int;

配置设置始终是 varchar 值,因此在使用它们时需要将它们转换为正确的数据类型。这适用于任何 SQL 客户端,而 \set 仅适用于 psql

以上要求 Postgres 9.2 或更高版本。

对于以前的版本,变量必须在使用之前在postgresql.conf 中声明,因此它在一定程度上限制了它的可用性。实际上不是完全变量,而是本质上是前缀的配置“类”。但是一旦定义了前缀,任何变量都可以在不改变postgresql.conf的情况下使用

【讨论】:

  • @BrijanElwadhi:是的,这是事务性的。
  • 附带说明:有些词是保留的,例如将set session my.vars.id = '1'; 更改为set session my.user.id = '1'; 将产生ERROR: syntax error at or near "user"
  • @BrijanElwadhi:要使变量事务具体化,您必须使用:SET LOCAL ...。只要您的连接有效,session 变量就会生效。 local 的范围为事务。
  • @dominik 您可以使用引号绕过该限制,例如,set session "my.user.id" = '1'; current_setting('my.user.id') 调用按预期工作。
  • 但似乎不可能处理会话变量中的日期时间值。像SET SESSION "vars.tomorrow" = CURRENT_DATE + '1 DAY'::interval; 这样的东西不起作用,即使转换为文本。在我看来,这是一个很大的限制。
【解决方案5】:

这取决于您的客户。

但是,如果您使用的是 psql 客户端,则可以使用以下内容:

my_db=> \set myvar 5
my_db=> SELECT :myvar  + 1 AS my_var_plus_1;
 my_var_plus_1 
---------------
             6

如果您使用的是文本变量,则需要引用。

\set myvar 'sometextvalue'
select * from sometable where name = :'myvar';

【讨论】:

  • \set 必须小写
  • db=# \set profile_id 102 db=# :profile_id;错误:“102”第 1 行或附近的语法错误:102; ^
  • @AlxVallejo 你必须在语句和 psql 控制台中使用它。 db=> \set someid 8292 db=> SELECT * FROM sometable WHERE id = :someid;
【解决方案6】:

这个方案是基于fei0x提出的方案,但是它的优点是在查询中不需要加入常量的值列表,并且可以在查询开始时方便的列出常量。它也适用于递归查询。

基本上,每个常量都是在 WITH 子句中声明的单值表,然后可以在查询的剩余部分的任何位置调用该表。

  • 具有两个常量的基本示例:
WITH
    constant_1_str AS (VALUES ('Hello World')),
    constant_2_int AS (VALUES (100))
SELECT *
FROM some_table
WHERE table_column = (table constant_1_str)
LIMIT (table constant_2_int)

或者,您可以使用SELECT * FROM constant_name 而不是TABLE constant_name,这对于不同于postgresql 的其他查询语言可能无效。

【讨论】:

  • 非常简洁,我会经常使用它。只是好奇 - TABLE 关键字在这种情况下做了什么?我没有运气搜索它,因为它是一个通用术语。
  • @user323094 等同于'select * from XX'
  • 它只工作一次。如果您编写选择查询以两次使用相同的值,则会出现错误提示“SQL 错误 [42P01]:错误:关系“constant_1_str”不存在位置:20”
  • @SatishPatro 是的,这是 CTE 方法的唯一缺点——它只存在于创建 CTE 之后的第一个查询中。这个例子可能是 CTE 变量方法的最佳版本,IMO
【解决方案7】:

在 pl/PgSQL 之外使用临时表

除了按照建议使用 pl/pgsql 或其他 pl/* 语言之外,这是我能想到的唯一其他可能性。

begin;
select 5::int as var into temp table myvar;
select *
  from somewhere s, myvar v
 where s.something = v.var;
commit;

【讨论】:

    【解决方案8】:

    我想提出对@DarioBarrionuevo's answer 的改进,以便更轻松地利用临时表。

    DO $$
        DECLARE myvar integer = 5;
    BEGIN
        CREATE TEMP TABLE tmp_table ON COMMIT DROP AS
            -- put here your query with variables:
            SELECT * 
            FROM yourtable
            WHERE id = myvar;
    END $$;
    
    SELECT * FROM tmp_table;
    

    【讨论】:

    • 解决 DO 块无法返回数据集的好方法!
    • 在 PostgreSQL 11.0 上,这样的查询返回 1(大概是行数)而不是 tmp_table 的内容。
    【解决方案9】:

    诚然,单值变量的声明并没有什么形象明确的方式,你能做的是

    with myVar as (select "any value really")
    

    然后,要访问存储在此构造中的值,您可以这样做

    (select * from myVar)
    

    例如

    with var as (select 123)    
    ... where id = (select * from var)
    

    【讨论】:

    • 我只能使用一次,第二次尝试使用它,它显示“SQL 错误 [42P01]:错误:关系“varName”不存在位置:143”
    【解决方案10】:

    这是一个使用PREPARE statements 的示例。你仍然不能使用?,但你可以使用$n 表示法:

    PREPARE foo(integer) AS
        SELECT  *
        FROM    somewhere
        WHERE   something = $1;
    EXECUTE foo(5);
    DEALLOCATE foo;
    

    【讨论】:

      【解决方案11】:

      您可以求助于工具的特殊功能。就像 DBeaver 自己的专有语法一样:

      @set name = 'me'
      SELECT :name;
      SELECT ${name};
      
      DELETE FROM book b
      WHERE b.author_id IN (SELECT a.id FROM author AS a WHERE a.name = :name);
      

      【讨论】:

      • 这更接近可用:我将研究 DBeaver 是否支持列表和循环:我需要将相同的 sql 应用于多个模式,列表将是应用它们的模式.
      【解决方案12】:

      在 DBeaver 中,您可以像在代码中一样在查询中使用参数,所以这将起作用:

      SELECT *
      FROM somewhere
      WHERE something = :myvar
      

      当您运行查询时,DBeaver 会询问您 :myvar 的值并运行查询。

      【讨论】:

        【解决方案13】:

        这是在 postges 终端中使用普通变量的代码段。我已经用过几次了。但需要想出更好的方法。在这里,我正在使用字符串变量。使用整数变量,您不需要三引号。三引号在查询时变成单引号;否则会出现语法错误。在使用字符串变量时,可能有一种方法可以消除三引号的需要。如果您找到改进的方法,请更新。

        \set strainname '''B.1.1.7'''
        
        select *
        from covid19strain
        where name = :strainname ;
        

        【讨论】:

          【解决方案14】:

          正如您从其他答案中了解到的那样,PostgreSQL 在直接 SQL 中没有这种机制,尽管您现在可以使用 anonymous block。但是,您可以使用公用表表达式 (CTE) 执行类似的操作:

          WITH vars AS (
              SELECT 5 AS myvar
          )
          SELECT *
          FROM somewhere,vars
          WHERE something = vars.myvar;
          

          当然,您可以拥有任意数量的变量,并且它们也可以派生。例如:

          WITH vars AS (
              SELECT
                  '1980-01-01'::date AS start,
                  '1999-12-31'::date AS end,
                  (SELECT avg(height) FROM customers) AS avg_height
          )
          SELECT *
          FROM customers,vars
          WHERE (dob BETWEEN vars.start AND vars.end) AND height<vars.avg_height;
          

          流程是:

          • 在没有表的情况下使用SELECT 生成单行cte(在Oracle 中,您需要包括FROM DUAL)。
          • 将 cte 与另一个表交叉连接。虽然有 CROSS JOIN 语法,但旧的逗号语法更易读。
          • 请注意,我已经确定了日期以避免SELECT 子句中可能出现的问题。我使用了 PostgreSQL 的较短语法,但您可以使用更正式的 CAST('1980-01-01' AS date) 来实现跨方言兼容性。

          通常,您希望避免交叉连接,但由于您只交叉连接单行,这具有简单地用可变数据扩大表的效果。

          在许多情况下,如果名称与其他表中的名称不冲突,则不需要包含 vars. 前缀。我把它包括在这里是为了说明这一点。

          此外,您还可以继续添加更多 CTE。

          这也适用于所有当前版本的 MSSQL 和 MySQL,它们支持变量,以及不支持变量的 SQLite,以及支持和不支持变量的 Oracle。

          【讨论】:

            猜你喜欢
            • 2017-04-21
            • 2011-02-09
            • 2018-12-31
            • 2011-03-10
            • 2023-03-28
            • 1970-01-01
            相关资源
            最近更新 更多